diff --git a/.codecov.yml b/.codecov.yml index cf409a6b6..1551f2276 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,3 +1,5 @@ comment: off ignore: - "cbor_gen.go" +github_checks: + annotations: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 128bc0ec6..ccefeda10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,45 @@ # Lotus changelog +# 0.5.10 / 2020-09-03 + +This patch includes a crucial fix to the message pool selection logic, strongly disfavouring messages that might cause a miner penalty. + +## Changes + +- Fix calculation of GasReward in messagepool (https://github.com/filecoin-project/lotus/pull/3528) + +# 0.5.9 / 2020-09-03 + +This patch includes a hotfix to the `GasEstimateFeeCap` method, capping the estimated fee to a reasonable level by default. + +## Changes + +- Added target height to sync wait (https://github.com/filecoin-project/lotus/pull/3502) +- Disable codecov annotations (https://github.com/filecoin-project/lotus/pull/3514) +- Cap fees to reasonable level by default (https://github.com/filecoin-project/lotus/pull/3516) +- Add APIs and command to inspect bandwidth usage (https://github.com/filecoin-project/lotus/pull/3497) +- Track expected nonce in mpool, ignore messages with large nonce gaps (https://github.com/filecoin-project/lotus/pull/3450) + +# 0.5.8 / 2020-09-02 + +This patch includes some bugfixes to the sector sealing process, and updates go-fil-markets. It also improves the performance of blocksync, adds a method to export chain state trees, and improves chainwatch. + +## Changes + +- Upgrade markets to v0.5.9 (https://github.com/filecoin-project/lotus/pull/3496) +- Improve blocksync to load fewer messages: (https://github.com/filecoin-project/lotus/pull/3494) +- Fix a panic in the ffi-wrapper's `ReadPiece` (https://github.com/filecoin-project/lotus/pull/3492/files) +- Fix a deadlock in the sealing scheduler (https://github.com/filecoin-project/lotus/pull/3489) +- Add test vectors for tipset tests (https://github.com/filecoin-project/lotus/pull/3485/files) +- Improve the advance-block debug command (https://github.com/filecoin-project/lotus/pull/3476) +- Add toggle for message processing to Lotus PCR (https://github.com/filecoin-project/lotus/pull/3470) +- Allow exporting recent chain state trees (https://github.com/filecoin-project/lotus/pull/3463) +- Remove height from chain rand (https://github.com/filecoin-project/lotus/pull/3458) +- Disable GC on chain badger datastore (https://github.com/filecoin-project/lotus/pull/3457) +- Account for `GasPremium` in `GasEstimateFeeCap` (https://github.com/filecoin-project/lotus/pull/3456) +- Update go-libp2p-pubsub to `master` (https://github.com/filecoin-project/lotus/pull/3455) +- Chainwatch improvements (https://github.com/filecoin-project/lotus/pull/3442) + # 0.5.7 / 2020-08-31 This patch release includes some bugfixes and enhancements to the sector lifecycle and message pool logic. diff --git a/api/api_common.go b/api/api_common.go index 69b2df17a..f8fcbe8c5 100644 --- a/api/api_common.go +++ b/api/api_common.go @@ -5,8 +5,10 @@ import ( "fmt" "github.com/filecoin-project/go-jsonrpc/auth" + metrics "github.com/libp2p/go-libp2p-core/metrics" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" + protocol "github.com/libp2p/go-libp2p-core/protocol" "github.com/filecoin-project/lotus/build" ) @@ -28,6 +30,19 @@ type Common interface { NetFindPeer(context.Context, peer.ID) (peer.AddrInfo, error) NetPubsubScores(context.Context) ([]PubsubScore, error) NetAutoNatStatus(context.Context) (NatInfo, error) + NetAgentVersion(ctx context.Context, p peer.ID) (string, error) + + // NetBandwidthStats returns statistics about the nodes total bandwidth + // usage and current rate across all peers and protocols. + NetBandwidthStats(ctx context.Context) (metrics.Stats, error) + + // NetBandwidthStatsByPeer returns statistics about the nodes bandwidth + // usage and current rate per peer + NetBandwidthStatsByPeer(ctx context.Context) (map[string]metrics.Stats, error) + + // NetBandwidthStatsByProtocol returns statistics about the nodes bandwidth + // usage and current rate per protocol + NetBandwidthStatsByProtocol(ctx context.Context) (map[protocol.ID]metrics.Stats, error) // MethodGroup: Common diff --git a/api/api_full.go b/api/api_full.go index ffceaccb6..f913483b3 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -421,6 +421,7 @@ type FullNode interface { PaychGet(ctx context.Context, from, to address.Address, amt types.BigInt) (*ChannelInfo, error) PaychGetWaitReady(context.Context, cid.Cid) (address.Address, error) + PaychAvailableFunds(from, to address.Address) (*ChannelAvailableFunds, error) PaychList(context.Context) ([]address.Address, error) PaychStatus(context.Context, address.Address) (*PaychStatus, error) PaychSettle(context.Context, address.Address) (cid.Cid, error) @@ -429,7 +430,7 @@ type FullNode interface { PaychNewPayment(ctx context.Context, from, to address.Address, vouchers []VoucherSpec) (*PaymentInfo, error) PaychVoucherCheckValid(context.Context, address.Address, *paych.SignedVoucher) error PaychVoucherCheckSpendable(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (bool, error) - PaychVoucherCreate(context.Context, address.Address, types.BigInt, uint64) (*paych.SignedVoucher, error) + PaychVoucherCreate(context.Context, address.Address, types.BigInt, uint64) (*VoucherCreateResult, error) PaychVoucherAdd(context.Context, address.Address, *paych.SignedVoucher, []byte, types.BigInt) (types.BigInt, error) PaychVoucherList(context.Context, address.Address) ([]*paych.SignedVoucher, error) PaychVoucherSubmit(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (cid.Cid, error) @@ -538,6 +539,23 @@ type ChannelInfo struct { WaitSentinel cid.Cid } +type ChannelAvailableFunds struct { + Channel *address.Address + // ConfirmedAmt is the amount of funds that have been confirmed on-chain + // for the channel + ConfirmedAmt types.BigInt + // PendingAmt is the amount of funds that are pending confirmation on-chain + PendingAmt types.BigInt + // PendingWaitSentinel can be used with PaychGetWaitReady to wait for + // confirmation of pending funds + PendingWaitSentinel *cid.Cid + // QueuedAmt is the amount that is queued up behind a pending request + QueuedAmt types.BigInt + // VoucherRedeemedAmt is the amount that is redeemed by vouchers on-chain + // and in the local datastore + VoucherReedeemedAmt types.BigInt +} + type PaymentInfo struct { Channel address.Address WaitSentinel cid.Cid @@ -553,6 +571,16 @@ type VoucherSpec struct { Extra *paych.ModVerifyParams } +// VoucherCreateResult is the response to calling PaychVoucherCreate +type VoucherCreateResult struct { + // Voucher that was created, or nil if there was an error or if there + // were insufficient funds in the channel + Voucher *paych.SignedVoucher + // Shortfall is the additional amount that would be needed in the channel + // in order to be able to create the voucher + Shortfall types.BigInt +} + type MinerPower struct { MinerPower power.Claim TotalPower power.Claim diff --git a/api/api_storage.go b/api/api_storage.go index 14aa5ff97..48f6e9e45 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -73,7 +73,7 @@ type StorageMiner interface { MarketImportDealData(ctx context.Context, propcid cid.Cid, path string) error MarketListDeals(ctx context.Context) ([]storagemarket.StorageDeal, error) MarketListRetrievalDeals(ctx context.Context) ([]retrievalmarket.ProviderDealState, error) - MarketGetDealUpdates(ctx context.Context, d cid.Cid) (<-chan storagemarket.MinerDeal, error) + MarketGetDealUpdates(ctx context.Context) (<-chan storagemarket.MinerDeal, error) MarketListIncompleteDeals(ctx context.Context) ([]storagemarket.MinerDeal, error) MarketSetAsk(ctx context.Context, price types.BigInt, verifiedPrice types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error MarketGetAsk(ctx context.Context) (*storagemarket.SignedStorageAsk, error) diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index ad8c5d40f..dff614001 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -6,8 +6,10 @@ import ( "time" "github.com/ipfs/go-cid" + metrics "github.com/libp2p/go-libp2p-core/metrics" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" + protocol "github.com/libp2p/go-libp2p-core/protocol" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-fil-markets/piecestore" @@ -42,14 +44,18 @@ type CommonStruct struct { AuthVerify func(ctx context.Context, token string) ([]auth.Permission, error) `perm:"read"` AuthNew func(ctx context.Context, perms []auth.Permission) ([]byte, error) `perm:"admin"` - NetConnectedness func(context.Context, peer.ID) (network.Connectedness, error) `perm:"read"` - NetPeers func(context.Context) ([]peer.AddrInfo, error) `perm:"read"` - NetConnect func(context.Context, peer.AddrInfo) error `perm:"write"` - NetAddrsListen func(context.Context) (peer.AddrInfo, error) `perm:"read"` - NetDisconnect func(context.Context, peer.ID) error `perm:"write"` - NetFindPeer func(context.Context, peer.ID) (peer.AddrInfo, error) `perm:"read"` - NetPubsubScores func(context.Context) ([]api.PubsubScore, error) `perm:"read"` - NetAutoNatStatus func(context.Context) (api.NatInfo, error) `perm:"read"` + NetConnectedness func(context.Context, peer.ID) (network.Connectedness, error) `perm:"read"` + NetPeers func(context.Context) ([]peer.AddrInfo, error) `perm:"read"` + NetConnect func(context.Context, peer.AddrInfo) error `perm:"write"` + NetAddrsListen func(context.Context) (peer.AddrInfo, error) `perm:"read"` + NetDisconnect func(context.Context, peer.ID) error `perm:"write"` + NetFindPeer func(context.Context, peer.ID) (peer.AddrInfo, error) `perm:"read"` + NetPubsubScores func(context.Context) ([]api.PubsubScore, error) `perm:"read"` + NetAutoNatStatus func(context.Context) (api.NatInfo, error) `perm:"read"` + NetBandwidthStats func(ctx context.Context) (metrics.Stats, error) `perm:"read"` + NetBandwidthStatsByPeer func(ctx context.Context) (map[string]metrics.Stats, error) `perm:"read"` + NetBandwidthStatsByProtocol func(ctx context.Context) (map[protocol.ID]metrics.Stats, error) `perm:"read"` + NetAgentVersion func(ctx context.Context, p peer.ID) (string, error) `perm:"read"` ID func(context.Context) (peer.ID, error) `perm:"read"` Version func(context.Context) (api.Version, error) `perm:"read"` @@ -203,6 +209,7 @@ type FullNodeStruct struct { PaychGet func(ctx context.Context, from, to address.Address, amt types.BigInt) (*api.ChannelInfo, error) `perm:"sign"` PaychGetWaitReady func(context.Context, cid.Cid) (address.Address, error) `perm:"sign"` + PaychAvailableFunds func(address.Address, address.Address) (*api.ChannelAvailableFunds, error) `perm:"sign"` PaychList func(context.Context) ([]address.Address, error) `perm:"read"` PaychStatus func(context.Context, address.Address) (*api.PaychStatus, error) `perm:"read"` PaychSettle func(context.Context, address.Address) (cid.Cid, error) `perm:"sign"` @@ -213,7 +220,7 @@ type FullNodeStruct struct { PaychVoucherCheckValid func(context.Context, address.Address, *paych.SignedVoucher) error `perm:"read"` PaychVoucherCheckSpendable func(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (bool, error) `perm:"read"` PaychVoucherAdd func(context.Context, address.Address, *paych.SignedVoucher, []byte, types.BigInt) (types.BigInt, error) `perm:"write"` - PaychVoucherCreate func(context.Context, address.Address, big.Int, uint64) (*paych.SignedVoucher, error) `perm:"sign"` + PaychVoucherCreate func(context.Context, address.Address, big.Int, uint64) (*api.VoucherCreateResult, error) `perm:"sign"` PaychVoucherList func(context.Context, address.Address) ([]*paych.SignedVoucher, error) `perm:"write"` PaychVoucherSubmit func(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (cid.Cid, error) `perm:"sign"` } @@ -235,7 +242,7 @@ type StorageMinerStruct struct { MarketImportDealData func(context.Context, cid.Cid, string) error `perm:"write"` MarketListDeals func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"` MarketListRetrievalDeals func(ctx context.Context) ([]retrievalmarket.ProviderDealState, error) `perm:"read"` - MarketGetDealUpdates func(ctx context.Context, d cid.Cid) (<-chan storagemarket.MinerDeal, error) `perm:"read"` + MarketGetDealUpdates func(ctx context.Context) (<-chan storagemarket.MinerDeal, error) `perm:"read"` MarketListIncompleteDeals func(ctx context.Context) ([]storagemarket.MinerDeal, error) `perm:"read"` MarketSetAsk func(ctx context.Context, price types.BigInt, verifiedPrice types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error `perm:"admin"` MarketGetAsk func(ctx context.Context) (*storagemarket.SignedStorageAsk, error) `perm:"read"` @@ -371,6 +378,22 @@ func (c *CommonStruct) NetAutoNatStatus(ctx context.Context) (api.NatInfo, error return c.Internal.NetAutoNatStatus(ctx) } +func (c *CommonStruct) NetBandwidthStats(ctx context.Context) (metrics.Stats, error) { + return c.Internal.NetBandwidthStats(ctx) +} + +func (c *CommonStruct) NetBandwidthStatsByPeer(ctx context.Context) (map[string]metrics.Stats, error) { + return c.Internal.NetBandwidthStatsByPeer(ctx) +} + +func (c *CommonStruct) NetBandwidthStatsByProtocol(ctx context.Context) (map[protocol.ID]metrics.Stats, error) { + return c.Internal.NetBandwidthStatsByProtocol(ctx) +} + +func (c *CommonStruct) NetAgentVersion(ctx context.Context, p peer.ID) (string, error) { + return c.Internal.NetAgentVersion(ctx, p) +} + // ID implements API.ID func (c *CommonStruct) ID(ctx context.Context) (peer.ID, error) { return c.Internal.ID(ctx) @@ -882,6 +905,10 @@ func (c *FullNodeStruct) PaychGetWaitReady(ctx context.Context, sentinel cid.Cid return c.Internal.PaychGetWaitReady(ctx, sentinel) } +func (c *FullNodeStruct) PaychAvailableFunds(from address.Address, to address.Address) (*api.ChannelAvailableFunds, error) { + return c.Internal.PaychAvailableFunds(from, to) +} + func (c *FullNodeStruct) PaychList(ctx context.Context) ([]address.Address, error) { return c.Internal.PaychList(ctx) } @@ -902,7 +929,7 @@ func (c *FullNodeStruct) PaychVoucherAdd(ctx context.Context, addr address.Addre return c.Internal.PaychVoucherAdd(ctx, addr, sv, proof, minDelta) } -func (c *FullNodeStruct) PaychVoucherCreate(ctx context.Context, pch address.Address, amt types.BigInt, lane uint64) (*paych.SignedVoucher, error) { +func (c *FullNodeStruct) PaychVoucherCreate(ctx context.Context, pch address.Address, amt types.BigInt, lane uint64) (*api.VoucherCreateResult, error) { return c.Internal.PaychVoucherCreate(ctx, pch, amt, lane) } @@ -1070,8 +1097,8 @@ func (c *StorageMinerStruct) MarketListRetrievalDeals(ctx context.Context) ([]re return c.Internal.MarketListRetrievalDeals(ctx) } -func (c *StorageMinerStruct) MarketGetDealUpdates(ctx context.Context, d cid.Cid) (<-chan storagemarket.MinerDeal, error) { - return c.Internal.MarketGetDealUpdates(ctx, d) +func (c *StorageMinerStruct) MarketGetDealUpdates(ctx context.Context) (<-chan storagemarket.MinerDeal, error) { + return c.Internal.MarketGetDealUpdates(ctx) } func (c *StorageMinerStruct) MarketListIncompleteDeals(ctx context.Context) ([]storagemarket.MinerDeal, error) { diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 5c74ef23b..c3b4962d5 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -14,8 +14,10 @@ import ( "github.com/ipfs/go-cid" "github.com/ipfs/go-filestore" + metrics "github.com/libp2p/go-libp2p-core/metrics" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" + protocol "github.com/libp2p/go-libp2p-core/protocol" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/multiformats/go-multiaddr" @@ -133,6 +135,22 @@ func init() { InvalidMessageDeliveries: 3, }, }) + addExample(map[string]metrics.Stats{ + "12D3KooWSXmXLJmBR1M7i9RW9GQPNUhZSzXKzxDHWtAgNuJAbyEJ": { + RateIn: 100, + RateOut: 50, + TotalIn: 174000, + TotalOut: 12500, + }, + }) + addExample(map[protocol.ID]metrics.Stats{ + "/fil/hello/1.0.0": { + RateIn: 100, + RateOut: 50, + TotalIn: 174000, + TotalOut: 12500, + }, + }) maddr, err := multiaddr.NewMultiaddr("/ip4/52.36.61.156/tcp/1347/p2p/12D3KooWFETiESTf1v4PGUvtnxMAcEFMzLZbJGg4tjWfGEimYior") if err != nil { diff --git a/api/test/deals.go b/api/test/deals.go index 00786e785..1dcc1c8d7 100644 --- a/api/test/deals.go +++ b/api/test/deals.go @@ -334,7 +334,7 @@ loop: func waitDealPublished(t *testing.T, ctx context.Context, miner TestStorageNode, deal *cid.Cid) { subCtx, cancel := context.WithCancel(ctx) defer cancel() - updates, err := miner.MarketGetDealUpdates(subCtx, *deal) + updates, err := miner.MarketGetDealUpdates(subCtx) if err != nil { t.Fatal(err) } @@ -343,18 +343,20 @@ func waitDealPublished(t *testing.T, ctx context.Context, miner TestStorageNode, case <-ctx.Done(): t.Fatal("context timeout") case di := <-updates: - switch di.State { - case storagemarket.StorageDealProposalRejected: - t.Fatal("deal rejected") - case storagemarket.StorageDealFailing: - t.Fatal("deal failed") - case storagemarket.StorageDealError: - t.Fatal("deal errored", di.Message) - case storagemarket.StorageDealFinalizing, storagemarket.StorageDealSealing, storagemarket.StorageDealActive: - fmt.Println("COMPLETE", di) - return + if deal.Equals(di.ProposalCid) { + switch di.State { + case storagemarket.StorageDealProposalRejected: + t.Fatal("deal rejected") + case storagemarket.StorageDealFailing: + t.Fatal("deal failed") + case storagemarket.StorageDealError: + t.Fatal("deal errored", di.Message) + case storagemarket.StorageDealFinalizing, storagemarket.StorageDealSealing, storagemarket.StorageDealActive: + fmt.Println("COMPLETE", di) + return + } + fmt.Println("Deal state: ", storagemarket.DealStates[di.State]) } - fmt.Println("Deal state: ", storagemarket.DealStates[di.State]) } } } diff --git a/api/test/paych.go b/api/test/paych.go index faa0bf8d9..b0ccc0a5c 100644 --- a/api/test/paych.go +++ b/api/test/paych.go @@ -96,18 +96,24 @@ func TestPaymentChannels(t *testing.T, b APIBuilder, blocktime time.Duration) { if err != nil { t.Fatal(err) } + if vouch1.Voucher == nil { + t.Fatal(fmt.Errorf("Not enough funds to create voucher: missing %d", vouch1.Shortfall)) + } vouch2, err := paymentCreator.PaychVoucherCreate(ctx, channel, abi.NewTokenAmount(2000), lane) if err != nil { t.Fatal(err) } - delta1, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouch1, nil, abi.NewTokenAmount(1000)) + if vouch2.Voucher == nil { + t.Fatal(fmt.Errorf("Not enough funds to create voucher: missing %d", vouch2.Shortfall)) + } + delta1, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouch1.Voucher, nil, abi.NewTokenAmount(1000)) if err != nil { t.Fatal(err) } if !delta1.Equals(abi.NewTokenAmount(1000)) { t.Fatal("voucher didn't have the right amount") } - delta2, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouch2, nil, abi.NewTokenAmount(1000)) + delta2, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouch2.Voucher, nil, abi.NewTokenAmount(1000)) if err != nil { t.Fatal(err) } diff --git a/build/version.go b/build/version.go index 5d29cd830..a3c5d1552 100644 --- a/build/version.go +++ b/build/version.go @@ -25,7 +25,7 @@ func buildType() string { } // BuildVersion is the local build version, set by build system -const BuildVersion = "0.5.7" +const BuildVersion = "0.5.10" func UserVersion() string { return BuildVersion + buildType() + CurrentCommit diff --git a/chain/blocksync/server.go b/chain/blocksync/server.go index 001f2e640..ffdf79ad0 100644 --- a/chain/blocksync/server.go +++ b/chain/blocksync/server.go @@ -221,37 +221,36 @@ func collectChainSegment( func gatherMessages(cs *store.ChainStore, ts *types.TipSet) ([]*types.Message, [][]uint64, []*types.SignedMessage, [][]uint64, error) { blsmsgmap := make(map[cid.Cid]uint64) secpkmsgmap := make(map[cid.Cid]uint64) - var secpkmsgs []*types.SignedMessage - var blsmsgs []*types.Message var secpkincl, blsincl [][]uint64 + var blscids, secpkcids []cid.Cid for _, block := range ts.Blocks() { - bmsgs, smsgs, err := cs.MessagesForBlock(block) + bc, sc, err := cs.ReadMsgMetaCids(block.Messages) if err != nil { return nil, nil, nil, nil, err } // FIXME: DRY. Use `chain.Message` interface. - bmi := make([]uint64, 0, len(bmsgs)) - for _, m := range bmsgs { - i, ok := blsmsgmap[m.Cid()] + bmi := make([]uint64, 0, len(bc)) + for _, m := range bc { + i, ok := blsmsgmap[m] if !ok { - i = uint64(len(blsmsgs)) - blsmsgs = append(blsmsgs, m) - blsmsgmap[m.Cid()] = i + i = uint64(len(blscids)) + blscids = append(blscids, m) + blsmsgmap[m] = i } bmi = append(bmi, i) } blsincl = append(blsincl, bmi) - smi := make([]uint64, 0, len(smsgs)) - for _, m := range smsgs { - i, ok := secpkmsgmap[m.Cid()] + smi := make([]uint64, 0, len(sc)) + for _, m := range sc { + i, ok := secpkmsgmap[m] if !ok { - i = uint64(len(secpkmsgs)) - secpkmsgs = append(secpkmsgs, m) - secpkmsgmap[m.Cid()] = i + i = uint64(len(secpkcids)) + secpkcids = append(secpkcids, m) + secpkmsgmap[m] = i } smi = append(smi, i) @@ -259,5 +258,15 @@ func gatherMessages(cs *store.ChainStore, ts *types.TipSet) ([]*types.Message, [ secpkincl = append(secpkincl, smi) } + blsmsgs, err := cs.LoadMessagesFromCids(blscids) + if err != nil { + return nil, nil, nil, nil, err + } + + secpkmsgs, err := cs.LoadSignedMessagesFromCids(secpkcids) + if err != nil { + return nil, nil, nil, nil, err + } + return blsmsgs, blsincl, secpkmsgs, secpkincl, nil } diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index 75573539b..e6238aab1 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -53,6 +53,8 @@ var minimumBaseFee = types.NewInt(uint64(build.MinimumBaseFee)) var MaxActorPendingMessages = 1000 +var MaxNonceGap = uint64(4) + var ( ErrMessageTooBig = errors.New("message too big") @@ -69,6 +71,7 @@ var ( ErrSoftValidationFailure = errors.New("validation failure") ErrRBFTooLowPremium = errors.New("replace by fee has too low GasPremium") ErrTooManyPendingMessages = errors.New("too many pending messages for actor") + ErrNonceGap = errors.New("unfulfilled nonce gap") ErrTryAgain = errors.New("state inconsistency while pushing message; please try again") ) @@ -154,19 +157,39 @@ type msgSet struct { requiredFunds *stdbig.Int } -func newMsgSet() *msgSet { +func newMsgSet(nonce uint64) *msgSet { return &msgSet{ msgs: make(map[uint64]*types.SignedMessage), + nextNonce: nonce, requiredFunds: stdbig.NewInt(0), } } -func (ms *msgSet) add(m *types.SignedMessage, mp *MessagePool, limit bool) (bool, error) { - if len(ms.msgs) == 0 || m.Message.Nonce >= ms.nextNonce { - ms.nextNonce = m.Message.Nonce + 1 +func (ms *msgSet) add(m *types.SignedMessage, mp *MessagePool, strict bool) (bool, error) { + nextNonce := ms.nextNonce + nonceGap := false + switch { + case m.Message.Nonce == nextNonce: + nextNonce++ + // advance if we are filling a gap + for _, fillGap := ms.msgs[nextNonce]; fillGap; _, fillGap = ms.msgs[nextNonce] { + nextNonce++ + } + + case strict && m.Message.Nonce > nextNonce+MaxNonceGap: + return false, xerrors.Errorf("message nonce has too big a gap from expected nonce (Nonce: %d, nextNonce: %d): %w", m.Message.Nonce, nextNonce, ErrNonceGap) + + case m.Message.Nonce > nextNonce: + nonceGap = true } + exms, has := ms.msgs[m.Message.Nonce] if has { + // refuse RBF if we have a gap + if strict && nonceGap { + return false, xerrors.Errorf("rejecting replace by fee because of nonce gap (Nonce: %d, nextNonce: %d): %w", m.Message.Nonce, nextNonce, ErrNonceGap) + } + if m.Cid() != exms.Cid() { // check if RBF passes minPrice := exms.Message.GasPremium @@ -182,17 +205,26 @@ func (ms *msgSet) add(m *types.SignedMessage, mp *MessagePool, limit bool) (bool m.Message.From, m.Message.Nonce, minPrice, m.Message.GasPremium, ErrRBFTooLowPremium) } + } else { + return false, xerrors.Errorf("message from %s with nonce %d already in mpool: %w", + m.Message.From, m.Message.Nonce, ErrSoftValidationFailure) } ms.requiredFunds.Sub(ms.requiredFunds, exms.Message.RequiredFunds().Int) //ms.requiredFunds.Sub(ms.requiredFunds, exms.Message.Value.Int) } - if !has && limit && len(ms.msgs) > MaxActorPendingMessages { + if !has && strict && len(ms.msgs) > MaxActorPendingMessages { log.Errorf("too many pending messages from actor %s", m.Message.From) return false, ErrTooManyPendingMessages } + if strict && nonceGap { + log.Warnf("adding nonce-gapped message from %s (nonce: %d, nextNonce: %d)", + m.Message.From, m.Message.Nonce, nextNonce) + } + + ms.nextNonce = nextNonce ms.msgs[m.Message.Nonce] = m ms.requiredFunds.Add(ms.requiredFunds, m.Message.RequiredFunds().Int) //ms.requiredFunds.Add(ms.requiredFunds, m.Message.Value.Int) @@ -200,12 +232,38 @@ func (ms *msgSet) add(m *types.SignedMessage, mp *MessagePool, limit bool) (bool return !has, nil } -func (ms *msgSet) rm(nonce uint64) { +func (ms *msgSet) rm(nonce uint64, applied bool) { m, has := ms.msgs[nonce] - if has { - ms.requiredFunds.Sub(ms.requiredFunds, m.Message.RequiredFunds().Int) - //ms.requiredFunds.Sub(ms.requiredFunds, m.Message.Value.Int) - delete(ms.msgs, nonce) + if !has { + if applied && nonce >= ms.nextNonce { + // we removed a message we did not know about because it was applied + // we need to adjust the nonce and check if we filled a gap + ms.nextNonce = nonce + 1 + for _, fillGap := ms.msgs[ms.nextNonce]; fillGap; _, fillGap = ms.msgs[ms.nextNonce] { + ms.nextNonce++ + } + } + return + } + + ms.requiredFunds.Sub(ms.requiredFunds, m.Message.RequiredFunds().Int) + //ms.requiredFunds.Sub(ms.requiredFunds, m.Message.Value.Int) + delete(ms.msgs, nonce) + + // adjust next nonce + if applied { + // we removed a (known) message because it was applied in a tipset + // we can't possibly have filled a gap in this case + if nonce >= ms.nextNonce { + ms.nextNonce = nonce + 1 + } + return + } + + // we removed a message because it was pruned + // we have to adjust the nonce if it creates a gap or rewinds state + if nonce < ms.nextNonce { + ms.nextNonce = nonce } } @@ -506,6 +564,40 @@ func (mp *MessagePool) addTs(m *types.SignedMessage, curTs *types.TipSet) error return mp.addLocked(m, true) } +func (mp *MessagePool) addLoaded(m *types.SignedMessage) error { + err := mp.checkMessage(m) + if err != nil { + return err + } + + mp.curTsLk.Lock() + defer mp.curTsLk.Unlock() + + curTs := mp.curTs + + snonce, err := mp.getStateNonce(m.Message.From, curTs) + if err != nil { + return xerrors.Errorf("failed to look up actor state nonce: %s: %w", err, ErrSoftValidationFailure) + } + + if snonce > m.Message.Nonce { + return xerrors.Errorf("minimum expected nonce is %d: %w", snonce, ErrNonceTooLow) + } + + mp.lk.Lock() + defer mp.lk.Unlock() + + if err := mp.verifyMsgBeforeAdd(m, curTs.Height()); err != nil { + return err + } + + if err := mp.checkBalance(m, curTs); err != nil { + return err + } + + return mp.addLocked(m, false) +} + func (mp *MessagePool) addSkipChecks(m *types.SignedMessage) error { mp.lk.Lock() defer mp.lk.Unlock() @@ -513,7 +605,7 @@ func (mp *MessagePool) addSkipChecks(m *types.SignedMessage) error { return mp.addLocked(m, false) } -func (mp *MessagePool) addLocked(m *types.SignedMessage, limit bool) error { +func (mp *MessagePool) addLocked(m *types.SignedMessage, strict bool) error { log.Debugf("mpooladd: %s %d", m.Message.From, m.Message.Nonce) if m.Signature.Type == crypto.SigTypeBLS { mp.blsSigCache.Add(m.Cid(), m.Signature) @@ -531,11 +623,16 @@ func (mp *MessagePool) addLocked(m *types.SignedMessage, limit bool) error { mset, ok := mp.pending[m.Message.From] if !ok { - mset = newMsgSet() + nonce, err := mp.getStateNonce(m.Message.From, mp.curTs) + if err != nil { + return xerrors.Errorf("failed to get initial actor nonce: %w", err) + } + + mset = newMsgSet(nonce) mp.pending[m.Message.From] = mset } - incr, err := mset.add(m, mp, limit) + incr, err := mset.add(m, mp, strict) if err != nil { log.Info(err) return err @@ -702,14 +799,14 @@ func (mp *MessagePool) PushWithNonce(ctx context.Context, addr address.Address, return msg, mp.api.PubSubPublish(build.MessagesTopic(mp.netName), msgb) } -func (mp *MessagePool) Remove(from address.Address, nonce uint64) { +func (mp *MessagePool) Remove(from address.Address, nonce uint64, applied bool) { mp.lk.Lock() defer mp.lk.Unlock() - mp.remove(from, nonce) + mp.remove(from, nonce, applied) } -func (mp *MessagePool) remove(from address.Address, nonce uint64) { +func (mp *MessagePool) remove(from address.Address, nonce uint64, applied bool) { mset, ok := mp.pending[from] if !ok { return @@ -732,22 +829,10 @@ func (mp *MessagePool) remove(from address.Address, nonce uint64) { // NB: This deletes any message with the given nonce. This makes sense // as two messages with the same sender cannot have the same nonce - mset.rm(nonce) + mset.rm(nonce, applied) if len(mset.msgs) == 0 { delete(mp.pending, from) - } else { - var max uint64 - for nonce := range mset.msgs { - if max < nonce { - max = nonce - } - } - if max < nonce { - max = nonce // we could have not seen the removed message before - } - - mset.nextNonce = max + 1 } } @@ -815,7 +900,7 @@ func (mp *MessagePool) HeadChange(revert []*types.TipSet, apply []*types.TipSet) rm := func(from address.Address, nonce uint64) { s, ok := rmsgs[from] if !ok { - mp.Remove(from, nonce) + mp.Remove(from, nonce, true) return } @@ -824,7 +909,7 @@ func (mp *MessagePool) HeadChange(revert []*types.TipSet, apply []*types.TipSet) return } - mp.Remove(from, nonce) + mp.Remove(from, nonce, true) } maybeRepub := func(cid cid.Cid) { @@ -1126,7 +1211,7 @@ func (mp *MessagePool) loadLocal() error { return xerrors.Errorf("unmarshaling local message: %w", err) } - if err := mp.Add(&sm); err != nil { + if err := mp.addLoaded(&sm); err != nil { if xerrors.Is(err, ErrNonceTooLow) { continue // todo: drop the message from local cache (if above certain confidence threshold) } diff --git a/chain/messagepool/messagepool_test.go b/chain/messagepool/messagepool_test.go index bab4b81e3..484c72746 100644 --- a/chain/messagepool/messagepool_test.go +++ b/chain/messagepool/messagepool_test.go @@ -352,6 +352,12 @@ func TestRevertMessages(t *testing.T) { } func TestPruningSimple(t *testing.T) { + oldMaxNonceGap := MaxNonceGap + MaxNonceGap = 1000 + defer func() { + MaxNonceGap = oldMaxNonceGap + }() + tma := newTestMpoolAPI() w, err := wallet.NewWallet(wallet.NewMemKeyStore()) diff --git a/chain/messagepool/pruning.go b/chain/messagepool/pruning.go index 143dd029e..d1290e386 100644 --- a/chain/messagepool/pruning.go +++ b/chain/messagepool/pruning.go @@ -98,7 +98,7 @@ keepLoop: // and remove all messages that are still in pruneMsgs after processing the chains log.Infof("Pruning %d messages", len(pruneMsgs)) for _, m := range pruneMsgs { - mp.remove(m.Message.From, m.Message.Nonce) + mp.remove(m.Message.From, m.Message.Nonce, false) } return nil diff --git a/chain/messagepool/repub.go b/chain/messagepool/repub.go index f23659eea..1c26e94f0 100644 --- a/chain/messagepool/repub.go +++ b/chain/messagepool/repub.go @@ -3,6 +3,7 @@ package messagepool import ( "context" "sort" + "time" "golang.org/x/xerrors" @@ -16,6 +17,8 @@ import ( const repubMsgLimit = 30 +var RepublishBatchDelay = 200 * time.Millisecond + func (mp *MessagePool) republishPendingMessages() error { mp.curTsLk.Lock() ts := mp.curTs @@ -132,6 +135,12 @@ func (mp *MessagePool) republishPendingMessages() error { } count++ + + if count < len(msgs) { + // this delay is here to encourage the pubsub subsystem to process the messages serially + // and avoid creating nonce gaps because of concurrent validation. + time.Sleep(RepublishBatchDelay) + } } if len(msgs) > 0 { diff --git a/chain/messagepool/repub_test.go b/chain/messagepool/repub_test.go index 28a69c92a..c89367f0e 100644 --- a/chain/messagepool/repub_test.go +++ b/chain/messagepool/repub_test.go @@ -12,6 +12,12 @@ import ( ) func TestRepubMessages(t *testing.T) { + oldRepublishBatchDelay := RepublishBatchDelay + RepublishBatchDelay = time.Microsecond + defer func() { + RepublishBatchDelay = oldRepublishBatchDelay + }() + tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() diff --git a/chain/messagepool/selection.go b/chain/messagepool/selection.go index 7f7babba0..5ba679d76 100644 --- a/chain/messagepool/selection.go +++ b/chain/messagepool/selection.go @@ -585,16 +585,18 @@ func (mp *MessagePool) getPendingMessages(curTs, ts *types.TipSet) (map[address. return result, nil } -func (mp *MessagePool) getGasReward(msg *types.SignedMessage, baseFee types.BigInt, ts *types.TipSet) *big.Int { +func (*MessagePool) getGasReward(msg *types.SignedMessage, baseFee types.BigInt) *big.Int { maxPremium := types.BigSub(msg.Message.GasFeeCap, baseFee) - if types.BigCmp(maxPremium, msg.Message.GasPremium) < 0 { + + if types.BigCmp(maxPremium, msg.Message.GasPremium) > 0 { maxPremium = msg.Message.GasPremium } + gasReward := abig.Mul(maxPremium, types.NewInt(uint64(msg.Message.GasLimit))) return gasReward.Int } -func (mp *MessagePool) getGasPerf(gasReward *big.Int, gasLimit int64) float64 { +func (*MessagePool) getGasPerf(gasReward *big.Int, gasLimit int64) float64 { // gasPerf = gasReward * build.BlockGasLimit / gasLimit a := new(big.Rat).SetInt(new(big.Int).Mul(gasReward, bigBlockGasLimit)) b := big.NewRat(1, gasLimit) @@ -672,7 +674,7 @@ func (mp *MessagePool) createMessageChains(actor address.Address, mset map[uint6 balance = new(big.Int).Sub(balance, value) } - gasReward := mp.getGasReward(m, baseFee, ts) + gasReward := mp.getGasReward(m, baseFee) rewards = append(rewards, gasReward) } @@ -776,7 +778,7 @@ func (mc *msgChain) Before(other *msgChain) bool { func (mc *msgChain) Trim(gasLimit int64, mp *MessagePool, baseFee types.BigInt, ts *types.TipSet) { i := len(mc.msgs) - 1 for i >= 0 && (mc.gasLimit > gasLimit || mc.gasPerf < 0) { - gasReward := mp.getGasReward(mc.msgs[i], baseFee, ts) + gasReward := mp.getGasReward(mc.msgs[i], baseFee) mc.gasReward = new(big.Int).Sub(mc.gasReward, gasReward) mc.gasLimit -= mc.msgs[i].Message.GasLimit if mc.gasLimit > 0 { diff --git a/chain/messagepool/selection_test.go b/chain/messagepool/selection_test.go index f22cd095d..a9ead3c01 100644 --- a/chain/messagepool/selection_test.go +++ b/chain/messagepool/selection_test.go @@ -2,6 +2,7 @@ package messagepool import ( "context" + "fmt" "math" "math/big" "math/rand" @@ -369,6 +370,12 @@ func TestMessageChainSkipping(t *testing.T) { } func TestBasicMessageSelection(t *testing.T) { + oldMaxNonceGap := MaxNonceGap + MaxNonceGap = 1000 + defer func() { + MaxNonceGap = oldMaxNonceGap + }() + mp, tma := makeTestMpool() // the actors @@ -1055,17 +1062,17 @@ func testCompetitiveMessageSelection(t *testing.T, rng *rand.Rand, getPremium fu greedyReward := big.NewInt(0) for _, m := range greedyMsgs { - greedyReward.Add(greedyReward, mp.getGasReward(m, baseFee, ts)) + greedyReward.Add(greedyReward, mp.getGasReward(m, baseFee)) } optReward := big.NewInt(0) for _, m := range optMsgs { - optReward.Add(optReward, mp.getGasReward(m, baseFee, ts)) + optReward.Add(optReward, mp.getGasReward(m, baseFee)) } bestTqReward := big.NewInt(0) for _, m := range bestMsgs { - bestTqReward.Add(bestTqReward, mp.getGasReward(m, baseFee, ts)) + bestTqReward.Add(bestTqReward, mp.getGasReward(m, baseFee)) } totalBestTQReward += float64(bestTqReward.Uint64()) @@ -1146,3 +1153,35 @@ func TestCompetitiveMessageSelectionZipf(t *testing.T) { t.Logf("Average reward boost across all seeds: %f", rewardBoost) t.Logf("Average reward of best ticket across all seeds: %f", tqReward) } + +func TestGasReward(t *testing.T) { + tests := []struct { + Premium uint64 + FeeCap uint64 + BaseFee uint64 + GasReward int64 + }{ + {Premium: 100, FeeCap: 200, BaseFee: 100, GasReward: 100}, + {Premium: 100, FeeCap: 200, BaseFee: 210, GasReward: -10}, + {Premium: 200, FeeCap: 250, BaseFee: 210, GasReward: 40}, + {Premium: 200, FeeCap: 250, BaseFee: 2000, GasReward: -1750}, + } + + mp := new(MessagePool) + for _, test := range tests { + test := test + t.Run(fmt.Sprintf("%v", test), func(t *testing.T) { + msg := &types.SignedMessage{ + Message: types.Message{ + GasLimit: 10, + GasFeeCap: types.NewInt(test.FeeCap), + GasPremium: types.NewInt(test.Premium), + }, + } + rew := mp.getGasReward(msg, types.NewInt(test.BaseFee)) + if rew.Cmp(big.NewInt(test.GasReward*10)) != 0 { + t.Errorf("bad reward: expected %d, got %s", test.GasReward*10, rew) + } + }) + } +} diff --git a/chain/store/store.go b/chain/store/store.go index 2cb68eca6..c608e76ed 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -49,6 +49,7 @@ var chainHeadKey = dstore.NewKey("head") var blockValidationCacheKeyPrefix = dstore.NewKey("blockValidation") var DefaultTipSetCacheSize = 8192 +var DefaultMsgMetaCacheSize = 2048 func init() { if s := os.Getenv("LOTUS_CHAIN_TIPSET_CACHE"); s != "" { @@ -58,6 +59,14 @@ func init() { } DefaultTipSetCacheSize = tscs } + + if s := os.Getenv("LOTUS_CHAIN_MSGMETA_CACHE"); s != "" { + mmcs, err := strconv.Atoi(s) + if err != nil { + log.Errorf("failed to parse 'LOTUS_CHAIN_MSGMETA_CACHE' env var: %s", err) + } + DefaultMsgMetaCacheSize = mmcs + } } // ReorgNotifee represents a callback that gets called upon reorgs. @@ -113,7 +122,7 @@ type ChainStore struct { } func NewChainStore(bs bstore.Blockstore, ds dstore.Batching, vmcalls vm.SyscallBuilder) *ChainStore { - c, _ := lru.NewARC(2048) + c, _ := lru.NewARC(DefaultMsgMetaCacheSize) tsc, _ := lru.NewARC(DefaultTipSetCacheSize) cs := &ChainStore{ bs: bs, @@ -857,7 +866,7 @@ type mmCids struct { secpk []cid.Cid } -func (cs *ChainStore) readMsgMetaCids(mmc cid.Cid) ([]cid.Cid, []cid.Cid, error) { +func (cs *ChainStore) ReadMsgMetaCids(mmc cid.Cid) ([]cid.Cid, []cid.Cid, error) { o, ok := cs.mmCache.Get(mmc) if ok { mmcids := o.(*mmCids) @@ -913,7 +922,7 @@ func (cs *ChainStore) GetPath(ctx context.Context, from types.TipSetKey, to type } func (cs *ChainStore) MessagesForBlock(b *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) { - blscids, secpkcids, err := cs.readMsgMetaCids(b.Messages) + blscids, secpkcids, err := cs.ReadMsgMetaCids(b.Messages) if err != nil { return nil, nil, err } diff --git a/chain/sub/incoming.go b/chain/sub/incoming.go index 017590463..5c28aa835 100644 --- a/chain/sub/incoming.go +++ b/chain/sub/incoming.go @@ -555,6 +555,8 @@ func (mv *MessageValidator) Validate(ctx context.Context, pid peer.ID, msg *pubs fallthrough case xerrors.Is(err, messagepool.ErrTooManyPendingMessages): fallthrough + case xerrors.Is(err, messagepool.ErrNonceGap): + fallthrough case xerrors.Is(err, messagepool.ErrNonceTooLow): return pubsub.ValidationIgnore default: diff --git a/cli/net.go b/cli/net.go index 6dd11d045..f3b5ae2e9 100644 --- a/cli/net.go +++ b/cli/net.go @@ -6,9 +6,12 @@ import ( "os" "sort" "strings" + "text/tabwriter" "github.com/libp2p/go-libp2p-core/peer" + protocol "github.com/libp2p/go-libp2p-core/protocol" + "github.com/dustin/go-humanize" "github.com/urfave/cli/v2" "github.com/filecoin-project/lotus/lib/addrutil" @@ -25,12 +28,20 @@ var netCmd = &cli.Command{ netFindPeer, netScores, NetReachability, + NetBandwidthCmd, }, } var NetPeers = &cli.Command{ Name: "peers", Usage: "Print peers", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "agent", + Aliases: []string{"a"}, + Usage: "Print agent name", + }, + }, Action: func(cctx *cli.Context) error { api, closer, err := GetAPI(cctx) if err != nil { @@ -48,7 +59,17 @@ var NetPeers = &cli.Command{ }) for _, peer := range peers { - fmt.Printf("%s, %s\n", peer.ID, peer.Addrs) + var agent string + if cctx.Bool("agent") { + agent, err = api.NetAgentVersion(ctx, peer.ID) + if err != nil { + log.Warnf("getting agent version: %s", err) + } else { + agent = ", " + agent + } + } + + fmt.Printf("%s, %s%s\n", peer.ID, peer.Addrs, agent) } return nil @@ -228,3 +249,88 @@ var NetReachability = &cli.Command{ return nil }, } + +var NetBandwidthCmd = &cli.Command{ + Name: "bandwidth", + Usage: "Print bandwidth usage information", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "by-peer", + Usage: "list bandwidth usage by peer", + }, + &cli.BoolFlag{ + Name: "by-protocol", + Usage: "list bandwidth usage by protocol", + }, + }, + Action: func(cctx *cli.Context) error { + api, closer, err := GetAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := ReqContext(cctx) + + bypeer := cctx.Bool("by-peer") + byproto := cctx.Bool("by-protocol") + + tw := tabwriter.NewWriter(os.Stdout, 4, 4, 2, ' ', 0) + + fmt.Fprintf(tw, "Segment\tTotalIn\tTotalOut\tRateIn\tRateOut\n") + + if bypeer { + bw, err := api.NetBandwidthStatsByPeer(ctx) + if err != nil { + return err + } + + var peers []string + for p := range bw { + peers = append(peers, p) + } + + sort.Slice(peers, func(i, j int) bool { + return peers[i] < peers[j] + }) + + for _, p := range peers { + s := bw[p] + fmt.Fprintf(tw, "%s\t%s\t%s\t%s/s\t%s/s\n", p, humanize.Bytes(uint64(s.TotalIn)), humanize.Bytes(uint64(s.TotalOut)), humanize.Bytes(uint64(s.RateIn)), humanize.Bytes(uint64(s.RateOut))) + } + } else if byproto { + bw, err := api.NetBandwidthStatsByProtocol(ctx) + if err != nil { + return err + } + + var protos []protocol.ID + for p := range bw { + protos = append(protos, p) + } + + sort.Slice(protos, func(i, j int) bool { + return protos[i] < protos[j] + }) + + for _, p := range protos { + s := bw[p] + if p == "" { + p = "" + } + fmt.Fprintf(tw, "%s\t%s\t%s\t%s/s\t%s/s\n", p, humanize.Bytes(uint64(s.TotalIn)), humanize.Bytes(uint64(s.TotalOut)), humanize.Bytes(uint64(s.RateIn)), humanize.Bytes(uint64(s.RateOut))) + } + } else { + + s, err := api.NetBandwidthStats(ctx) + if err != nil { + return err + } + + fmt.Fprintf(tw, "Total\t%s\t%s\t%s/s\t%s/s\n", humanize.Bytes(uint64(s.TotalIn)), humanize.Bytes(uint64(s.TotalOut)), humanize.Bytes(uint64(s.RateIn)), humanize.Bytes(uint64(s.RateOut))) + } + + return tw.Flush() + + }, +} diff --git a/cli/paych.go b/cli/paych.go index ff4d769da..11b550cc6 100644 --- a/cli/paych.go +++ b/cli/paych.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "sort" + "strings" "github.com/filecoin-project/lotus/paychmgr" @@ -21,7 +22,7 @@ var paychCmd = &cli.Command{ Name: "paych", Usage: "Manage payment channels", Subcommands: []*cli.Command{ - paychGetCmd, + paychAddFundsCmd, paychListCmd, paychVoucherCmd, paychSettleCmd, @@ -29,9 +30,9 @@ var paychCmd = &cli.Command{ }, } -var paychGetCmd = &cli.Command{ - Name: "get", - Usage: "Create a new payment channel or get existing one and add amount to it", +var paychAddFundsCmd = &cli.Command{ + Name: "add-funds", + Usage: "Add funds to the payment channel between fromAddress and toAddress. Creates the payment channel if it doesn't already exist.", ArgsUsage: "[fromAddress toAddress amount]", Action: func(cctx *cli.Context) error { if cctx.Args().Len() != 3 { @@ -79,6 +80,92 @@ var paychGetCmd = &cli.Command{ }, } +var paychStatusCmd = &cli.Command{ + Name: "status", + Usage: "Show the status of an outbound payment channel between fromAddress and toAddress", + ArgsUsage: "[fromAddress toAddress]", + Action: func(cctx *cli.Context) error { + if cctx.Args().Len() != 2 { + return ShowHelp(cctx, fmt.Errorf("must pass two arguments: ")) + } + + from, err := address.NewFromString(cctx.Args().Get(0)) + if err != nil { + return ShowHelp(cctx, fmt.Errorf("failed to parse from address: %s", err)) + } + + to, err := address.NewFromString(cctx.Args().Get(1)) + if err != nil { + return ShowHelp(cctx, fmt.Errorf("failed to parse to address: %s", err)) + } + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + + avail, err := api.PaychAvailableFunds(from, to) + if err != nil { + return err + } + + if avail.Channel == nil { + if avail.PendingWaitSentinel != nil { + fmt.Fprint(cctx.App.Writer, "Creating channel\n") + fmt.Fprintf(cctx.App.Writer, " From: %s\n", from) + fmt.Fprintf(cctx.App.Writer, " To: %s\n", to) + fmt.Fprintf(cctx.App.Writer, " Pending Amt: %d\n", avail.PendingAmt) + fmt.Fprintf(cctx.App.Writer, " Wait Sentinel: %s\n", avail.PendingWaitSentinel) + return nil + } + fmt.Fprint(cctx.App.Writer, "Channel does not exist\n") + fmt.Fprintf(cctx.App.Writer, " From: %s\n", from) + fmt.Fprintf(cctx.App.Writer, " To: %s\n", to) + return nil + } + + if avail.PendingWaitSentinel != nil { + fmt.Fprint(cctx.App.Writer, "Adding Funds to channel\n") + } else { + fmt.Fprint(cctx.App.Writer, "Channel exists\n") + } + + nameValues := [][]string{ + {"Channel", avail.Channel.String()}, + {"From", from.String()}, + {"To", to.String()}, + {"Confirmed Amt", fmt.Sprintf("%d", avail.ConfirmedAmt)}, + {"Pending Amt", fmt.Sprintf("%d", avail.PendingAmt)}, + {"Queued Amt", fmt.Sprintf("%d", avail.QueuedAmt)}, + {"Voucher Redeemed Amt", fmt.Sprintf("%d", avail.VoucherReedeemedAmt)}, + } + if avail.PendingWaitSentinel != nil { + nameValues = append(nameValues, []string{ + "Add Funds Wait Sentinel", + avail.PendingWaitSentinel.String(), + }) + } + fmt.Fprint(cctx.App.Writer, formatNameValues(nameValues)) + return nil + }, +} + +func formatNameValues(nameValues [][]string) string { + maxLen := 0 + for _, nv := range nameValues { + if len(nv[0]) > maxLen { + maxLen = len(nv[0]) + } + } + out := make([]string, len(nameValues)) + for i, nv := range nameValues { + namePad := strings.Repeat(" ", maxLen-len(nv[0])) + out[i] = " " + nv[0] + ": " + namePad + nv[1] + } + return strings.Join(out, "\n") + "\n" +} + var paychListCmd = &cli.Command{ Name: "list", Usage: "List all locally registered payment channels", @@ -217,9 +304,9 @@ var paychVoucherCreateCmd = &cli.Command{ return err } - amt, err := types.BigFromString(cctx.Args().Get(1)) + amt, err := types.ParseFIL(cctx.Args().Get(1)) if err != nil { - return err + return ShowHelp(cctx, fmt.Errorf("parsing amount failed: %s", err)) } lane := cctx.Int("lane") @@ -232,12 +319,16 @@ var paychVoucherCreateCmd = &cli.Command{ ctx := ReqContext(cctx) - sv, err := api.PaychVoucherCreate(ctx, ch, amt, uint64(lane)) + v, err := api.PaychVoucherCreate(ctx, ch, types.BigInt(amt), uint64(lane)) if err != nil { return err } - enc, err := EncodedString(sv) + if v.Voucher == nil { + return fmt.Errorf("Could not create voucher: insufficient funds in channel, shortfall: %d", v.Shortfall) + } + + enc, err := EncodedString(v.Voucher) if err != nil { return err } diff --git a/cli/paych_test.go b/cli/paych_test.go index 11a08fb2c..8eaeb0f13 100644 --- a/cli/paych_test.go +++ b/cli/paych_test.go @@ -6,11 +6,14 @@ import ( "flag" "fmt" "os" + "regexp" "strconv" "strings" "testing" "time" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/specs-actors/actors/abi/big" saminer "github.com/filecoin-project/specs-actors/actors/builtin/miner" "github.com/filecoin-project/specs-actors/actors/builtin/power" @@ -52,7 +55,7 @@ func TestPaymentChannels(t *testing.T) { ctx := context.Background() nodes, addrs := startTwoNodesOneMiner(ctx, t, blocktime) paymentCreator := nodes[0] - paymentReceiver := nodes[0] + paymentReceiver := nodes[1] creatorAddr := addrs[0] receiverAddr := addrs[1] @@ -61,10 +64,10 @@ func TestPaymentChannels(t *testing.T) { creatorCLI := mockCLI.client(paymentCreator.ListenAddr) receiverCLI := mockCLI.client(paymentReceiver.ListenAddr) - // creator: paych get + // creator: paych add-funds channelAmt := "100000" cmd := []string{creatorAddr.String(), receiverAddr.String(), channelAmt} - chstr := creatorCLI.runCmd(paychGetCmd, cmd) + chstr := creatorCLI.runCmd(paychAddFundsCmd, cmd) chAddr, err := address.NewFromString(chstr) require.NoError(t, err) @@ -98,6 +101,83 @@ type voucherSpec struct { lane int } +// TestPaymentChannelStatus tests the payment channel status CLI command +func TestPaymentChannelStatus(t *testing.T) { + _ = os.Setenv("BELLMAN_NO_GPU", "1") + + blocktime := 5 * time.Millisecond + ctx := context.Background() + nodes, addrs := startTwoNodesOneMiner(ctx, t, blocktime) + paymentCreator := nodes[0] + creatorAddr := addrs[0] + receiverAddr := addrs[1] + + // Create mock CLI + mockCLI := newMockCLI(t) + creatorCLI := mockCLI.client(paymentCreator.ListenAddr) + + cmd := []string{creatorAddr.String(), receiverAddr.String()} + out := creatorCLI.runCmd(paychStatusCmd, cmd) + fmt.Println(out) + noChannelState := "Channel does not exist" + require.Regexp(t, regexp.MustCompile(noChannelState), out) + + channelAmt := uint64(100) + create := make(chan string) + go func() { + // creator: paych get + cmd = []string{creatorAddr.String(), receiverAddr.String(), fmt.Sprintf("%d", channelAmt)} + create <- creatorCLI.runCmd(paychGetCmd, cmd) + }() + + // Wait for the output to stop being "Channel does not exist" + for regexp.MustCompile(noChannelState).MatchString(out) { + cmd = []string{creatorAddr.String(), receiverAddr.String()} + out = creatorCLI.runCmd(paychStatusCmd, cmd) + } + fmt.Println(out) + + // The next state should be creating channel or channel created, depending + // on timing + stateCreating := regexp.MustCompile("Creating channel").MatchString(out) + stateCreated := regexp.MustCompile("Channel exists").MatchString(out) + require.True(t, stateCreating || stateCreated) + + channelAmtAtto := types.BigMul(types.NewInt(channelAmt), types.NewInt(build.FilecoinPrecision)) + channelAmtStr := fmt.Sprintf("%d", channelAmtAtto) + if stateCreating { + // If we're in the creating state (most likely) the amount should be pending + require.Regexp(t, regexp.MustCompile("Pending.*"+channelAmtStr), out) + } + + // Wait for create channel to complete + chstr := <-create + + cmd = []string{creatorAddr.String(), receiverAddr.String()} + out = creatorCLI.runCmd(paychStatusCmd, cmd) + fmt.Println(out) + // Output should have the channel address + require.Regexp(t, regexp.MustCompile("Channel.*"+chstr), out) + // Output should have confirmed amount + require.Regexp(t, regexp.MustCompile("Confirmed.*"+channelAmtStr), out) + + chAddr, err := address.NewFromString(chstr) + require.NoError(t, err) + + // creator: paych voucher create + voucherAmt := uint64(10) + cmd = []string{chAddr.String(), fmt.Sprintf("%d", voucherAmt)} + creatorCLI.runCmd(paychVoucherCreateCmd, cmd) + + cmd = []string{creatorAddr.String(), receiverAddr.String()} + out = creatorCLI.runCmd(paychStatusCmd, cmd) + fmt.Println(out) + voucherAmtAtto := types.BigMul(types.NewInt(voucherAmt), types.NewInt(build.FilecoinPrecision)) + voucherAmtStr := fmt.Sprintf("%d", voucherAmtAtto) + // Output should include voucher amount + require.Regexp(t, regexp.MustCompile("Voucher.*"+voucherAmtStr), out) +} + // TestPaymentChannelVouchers does a basic test to exercise some payment // channel voucher commands func TestPaymentChannelVouchers(t *testing.T) { @@ -116,10 +196,10 @@ func TestPaymentChannelVouchers(t *testing.T) { creatorCLI := mockCLI.client(paymentCreator.ListenAddr) receiverCLI := mockCLI.client(paymentReceiver.ListenAddr) - // creator: paych get + // creator: paych add-funds channelAmt := "100000" cmd := []string{creatorAddr.String(), receiverAddr.String(), channelAmt} - chstr := creatorCLI.runCmd(paychGetCmd, cmd) + chstr := creatorCLI.runCmd(paychAddFundsCmd, cmd) chAddr, err := address.NewFromString(chstr) require.NoError(t, err) @@ -248,6 +328,50 @@ func TestPaymentChannelVouchers(t *testing.T) { checkVoucherOutput(t, bestSpendable, bestVouchers) } +// TestPaymentChannelVoucherCreateShortfall verifies that if a voucher amount +// is greater than what's left in the channel, voucher create fails +func TestPaymentChannelVoucherCreateShortfall(t *testing.T) { + _ = os.Setenv("BELLMAN_NO_GPU", "1") + + blocktime := 5 * time.Millisecond + ctx := context.Background() + nodes, addrs := startTwoNodesOneMiner(ctx, t, blocktime) + paymentCreator := nodes[0] + creatorAddr := addrs[0] + receiverAddr := addrs[1] + + // Create mock CLI + mockCLI := newMockCLI(t) + creatorCLI := mockCLI.client(paymentCreator.ListenAddr) + + // creator: paych get + channelAmt := 100 + cmd := []string{creatorAddr.String(), receiverAddr.String(), fmt.Sprintf("%d", channelAmt)} + chstr := creatorCLI.runCmd(paychGetCmd, cmd) + + chAddr, err := address.NewFromString(chstr) + require.NoError(t, err) + + // creator: paych voucher create --lane=1 + voucherAmt1 := 60 + lane1 := "--lane=1" + cmd = []string{lane1, chAddr.String(), strconv.Itoa(voucherAmt1)} + voucher1 := creatorCLI.runCmd(paychVoucherCreateCmd, cmd) + fmt.Println(voucher1) + + // creator: paych voucher create --lane=2 + lane2 := "--lane=2" + voucherAmt2 := 70 + cmd = []string{lane2, chAddr.String(), strconv.Itoa(voucherAmt2)} + _, err = creatorCLI.runCmdRaw(paychVoucherCreateCmd, cmd) + + // Should fail because channel doesn't have required amount + require.Error(t, err) + + shortfall := voucherAmt1 + voucherAmt2 - channelAmt + require.Regexp(t, regexp.MustCompile(fmt.Sprintf("shortfall: %d", shortfall)), err.Error()) +} + func checkVoucherOutput(t *testing.T, list string, vouchers []voucherSpec) { lines := strings.Split(list, "\n") listVouchers := make(map[string]string) @@ -350,6 +474,13 @@ type mockCLIClient struct { } func (c *mockCLIClient) runCmd(cmd *cli.Command, input []string) string { + out, err := c.runCmdRaw(cmd, input) + require.NoError(c.t, err) + + return out +} + +func (c *mockCLIClient) runCmdRaw(cmd *cli.Command, input []string) (string, error) { // prepend --api= apiFlag := "--api=" + c.addr.String() input = append([]string{apiFlag}, input...) @@ -359,12 +490,11 @@ func (c *mockCLIClient) runCmd(cmd *cli.Command, input []string) string { require.NoError(c.t, err) err = cmd.Action(cli.NewContext(c.cctx.App, fs, c.cctx)) - require.NoError(c.t, err) // Get the output str := strings.TrimSpace(c.out.String()) c.out.Reset() - return str + return str, err } func (c *mockCLIClient) flagSet(cmd *cli.Command) *flag.FlagSet { diff --git a/cli/state.go b/cli/state.go index 1e75cc7cd..a0256c2e3 100644 --- a/cli/state.go +++ b/cli/state.go @@ -26,11 +26,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/specs-actors/actors/abi" "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/builtin/market" - miner2 "github.com/filecoin-project/specs-actors/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/actors/builtin/multisig" - "github.com/filecoin-project/specs-actors/actors/builtin/paych" - "github.com/filecoin-project/specs-actors/actors/builtin/power" + "github.com/filecoin-project/specs-actors/actors/builtin/exported" "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" "github.com/filecoin-project/lotus/api" @@ -568,25 +564,7 @@ var stateGetActorCmd = &cli.Command{ return err } - var strtype string - switch a.Code { - case builtin.AccountActorCodeID: - strtype = "account" - case builtin.MultisigActorCodeID: - strtype = "multisig" - case builtin.CronActorCodeID: - strtype = "cron" - case builtin.InitActorCodeID: - strtype = "init" - case builtin.StorageMinerActorCodeID: - strtype = "miner" - case builtin.StorageMarketActorCodeID: - strtype = "market" - case builtin.StoragePowerActorCodeID: - strtype = "power" - default: - strtype = "unknown" - } + strtype := builtin.ActorNameByCode(a.Code) fmt.Printf("Address:\t%s\n", addr) fmt.Printf("Balance:\t%s\n", types.FIL(a.Balance)) @@ -1460,21 +1438,21 @@ func parseParamsForMethod(act cid.Cid, method uint64, args []string) ([]byte, er return nil, nil } - var f interface{} - switch act { - case builtin.StorageMarketActorCodeID: - f = market.Actor{}.Exports()[method] - case builtin.StorageMinerActorCodeID: - f = miner2.Actor{}.Exports()[method] - case builtin.StoragePowerActorCodeID: - f = power.Actor{}.Exports()[method] - case builtin.MultisigActorCodeID: - f = multisig.Actor{}.Exports()[method] - case builtin.PaymentChannelActorCodeID: - f = paych.Actor{}.Exports()[method] - default: - return nil, fmt.Errorf("the lazy devs didnt add support for that actor to this call yet") + var target abi.Invokee + for _, actor := range exported.BuiltinActors() { + if actor.Code() == act { + target = actor + } } + if target == nil { + return nil, fmt.Errorf("unknown actor %s", act) + } + methods := target.Exports() + if uint64(len(methods)) <= method || methods[method] == nil { + return nil, fmt.Errorf("unknown method %d for actor %s", method, act) + } + + f := methods[method] rf := reflect.TypeOf(f) if rf.NumIn() != 3 { diff --git a/cli/sync.go b/cli/sync.go index 57553a068..27957ac35 100644 --- a/cli/sync.go +++ b/cli/sync.go @@ -180,11 +180,13 @@ func SyncWait(ctx context.Context, napi api.FullNode) error { ss := state.ActiveSyncs[working] var target []cid.Cid + var theight abi.ChainEpoch if ss.Target != nil { target = ss.Target.Cids() + theight = ss.Target.Height() } - fmt.Printf("\r\x1b[2KWorker %d: Target: %s\tState: %s\tHeight: %d", working, target, chain.SyncStageString(ss.Stage), ss.Height) + fmt.Printf("\r\x1b[2KWorker %d: Target Height: %d\tTarget: %s\tState: %s\tHeight: %d", working, theight, target, chain.SyncStageString(ss.Stage), ss.Height) if time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs) { fmt.Println("\nDone!") diff --git a/cmd/lotus-chainwatch/processor/messages.go b/cmd/lotus-chainwatch/processor/messages.go index e3d23f219..333477c6a 100644 --- a/cmd/lotus-chainwatch/processor/messages.go +++ b/cmd/lotus-chainwatch/processor/messages.go @@ -254,7 +254,9 @@ func (p *Processor) fetchMessages(ctx context.Context, blocks map[cid.Cid]*types parmap.Par(50, parmap.MapArr(blocks), func(header *types.BlockHeader) { msgs, err := p.node.ChainGetBlockMessages(ctx, header.Cid()) if err != nil { - panic(err) + log.Error(err) + log.Debugw("ChainGetBlockMessages", "header_cid", header.Cid()) + return } vmm := make([]*types.Message, 0, len(msgs.Cids)) @@ -290,11 +292,15 @@ func (p *Processor) fetchParentReceipts(ctx context.Context, toSync map[cid.Cid] parmap.Par(50, parmap.MapArr(toSync), func(header *types.BlockHeader) { recs, err := p.node.ChainGetParentReceipts(ctx, header.Cid()) if err != nil { - panic(err) + log.Error(err) + log.Debugw("ChainGetParentReceipts", "header_cid", header.Cid()) + return } msgs, err := p.node.ChainGetParentMessages(ctx, header.Cid()) if err != nil { - panic(err) + log.Error(err) + log.Debugw("ChainGetParentMessages", "header_cid", header.Cid()) + return } lk.Lock() diff --git a/cmd/lotus-chainwatch/processor/processor.go b/cmd/lotus-chainwatch/processor/processor.go index 59e3bbb7e..e6c2ffb94 100644 --- a/cmd/lotus-chainwatch/processor/processor.go +++ b/cmd/lotus-chainwatch/processor/processor.go @@ -246,7 +246,8 @@ func (p *Processor) collectActorChanges(ctx context.Context, toProcess map[cid.C pts, err := p.node.ChainGetTipSet(ctx, types.NewTipSetKey(bh.Parents...)) if err != nil { - panic(err) + log.Error(err) + return } if pts.ParentState().Equals(bh.ParentStateRoot) { @@ -260,7 +261,9 @@ func (p *Processor) collectActorChanges(ctx context.Context, toProcess map[cid.C // a separate strategy for deleted actors changes, err = p.node.StateChangedActors(ctx, pts.ParentState(), bh.ParentStateRoot) if err != nil { - panic(err) + log.Error(err) + log.Debugw("StateChangedActors", "grandparent_state", pts.ParentState(), "parent_state", bh.ParentStateRoot) + return } // record the state of all actors that have changed @@ -271,7 +274,9 @@ func (p *Processor) collectActorChanges(ctx context.Context, toProcess map[cid.C // ignore actors that were deleted. has, err := p.node.ChainHasObj(ctx, act.Head) if err != nil { - log.Fatal(err) + log.Error(err) + log.Debugw("ChanHasObj", "actor_head", act.Head) + return } if !has { continue @@ -279,19 +284,24 @@ func (p *Processor) collectActorChanges(ctx context.Context, toProcess map[cid.C addr, err := address.NewFromString(a) if err != nil { - log.Fatal(err.Error()) + log.Error(err) + log.Debugw("NewFromString", "address_string", a) + return } ast, err := p.node.StateReadState(ctx, addr, pts.Key()) if err != nil { - log.Fatal(err.Error()) + log.Error(err) + log.Debugw("StateReadState", "address_string", a, "parent_tipset_key", pts.Key()) + return } // TODO look here for an empty state, maybe thats a sign the actor was deleted? state, err := json.Marshal(ast.State) if err != nil { - panic(err) + log.Error(err) + return } outMu.Lock() @@ -324,10 +334,10 @@ func (p *Processor) unprocessedBlocks(ctx context.Context, batch int) (map[cid.C }() rows, err := p.db.Query(` with toProcess as ( - select blocks.cid, blocks.height, rank() over (order by height) as rnk - from blocks - left join blocks_synced bs on blocks.cid = bs.cid - where bs.processed_at is null and blocks.height > 0 + select b.cid, b.height, rank() over (order by height) as rnk + from blocks_synced bs + left join blocks b on bs.cid = b.cid + where bs.processed_at is null and b.height > 0 ) select cid from toProcess diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index 828fcd2c3..39671f74c 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -3,6 +3,7 @@ package main import ( "bufio" "fmt" + "io" "os" "path/filepath" "sort" @@ -345,6 +346,10 @@ var dealsListCmd = &cli.Command{ Name: "verbose", Aliases: []string{"v"}, }, + &cli.BoolFlag{ + Name: "watch", + Usage: "watch deal updates in real-time, rather than a one time list", + }, }, Action: func(cctx *cli.Context) error { api, closer, err := lcli.GetStorageMinerAPI(cctx) @@ -360,42 +365,83 @@ var dealsListCmd = &cli.Command{ return err } - sort.Slice(deals, func(i, j int) bool { - return deals[i].CreationTime.Time().Before(deals[j].CreationTime.Time()) - }) - - w := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) - verbose := cctx.Bool("verbose") + watch := cctx.Bool("watch") + + if watch { + updates, err := api.MarketGetDealUpdates(ctx) + if err != nil { + return err + } + + for { + tm.Clear() + tm.MoveCursor(1, 1) + + err = outputStorageDeals(tm.Output, deals, verbose) + if err != nil { + return err + } + + tm.Flush() + + select { + case <-ctx.Done(): + return nil + case updated := <-updates: + var found bool + for i, existing := range deals { + if existing.ProposalCid.Equals(updated.ProposalCid) { + deals[i] = updated + found = true + break + } + } + if !found { + deals = append(deals, updated) + } + } + } + } + + return outputStorageDeals(os.Stdout, deals, verbose) + }, +} + +func outputStorageDeals(out io.Writer, deals []storagemarket.MinerDeal, verbose bool) error { + sort.Slice(deals, func(i, j int) bool { + return deals[i].CreationTime.Time().Before(deals[j].CreationTime.Time()) + }) + + w := tabwriter.NewWriter(out, 2, 4, 2, ' ', 0) + + if verbose { + _, _ = fmt.Fprintf(w, "Creation\tProposalCid\tDealId\tState\tClient\tSize\tPrice\tDuration\tMessage\n") + } else { + _, _ = fmt.Fprintf(w, "ProposalCid\tDealId\tState\tClient\tSize\tPrice\tDuration\n") + } + + for _, deal := range deals { + propcid := deal.ProposalCid.String() + if !verbose { + propcid = "..." + propcid[len(propcid)-8:] + } + + fil := types.FIL(types.BigMul(deal.Proposal.StoragePricePerEpoch, types.NewInt(uint64(deal.Proposal.Duration())))) if verbose { - _, _ = fmt.Fprintf(w, "Creation\tProposalCid\tDealId\tState\tClient\tSize\tPrice\tDuration\tMessage\n") - } else { - _, _ = fmt.Fprintf(w, "ProposalCid\tDealId\tState\tClient\tSize\tPrice\tDuration\n") + _, _ = fmt.Fprintf(w, "%s\t", deal.CreationTime.Time().Format(time.Stamp)) } - for _, deal := range deals { - propcid := deal.ProposalCid.String() - if !verbose { - propcid = "..." + propcid[len(propcid)-8:] - } - - fil := types.FIL(types.BigMul(deal.Proposal.StoragePricePerEpoch, types.NewInt(uint64(deal.Proposal.Duration())))) - - if verbose { - _, _ = fmt.Fprintf(w, "%s\t", deal.CreationTime.Time().Format(time.Stamp)) - } - - _, _ = fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%s\t%s", propcid, deal.DealID, storagemarket.DealStates[deal.State], deal.Proposal.Client, units.BytesSize(float64(deal.Proposal.PieceSize)), fil, deal.Proposal.Duration()) - if verbose { - _, _ = fmt.Fprintf(w, "\t%s", deal.Message) - } - - _, _ = fmt.Fprintln(w) + _, _ = fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%s\t%s", propcid, deal.DealID, storagemarket.DealStates[deal.State], deal.Proposal.Client, units.BytesSize(float64(deal.Proposal.PieceSize)), fil, deal.Proposal.Duration()) + if verbose { + _, _ = fmt.Fprintf(w, "\t%s", deal.Message) } - return w.Flush() - }, + _, _ = fmt.Fprintln(w) + } + + return w.Flush() } var getBlocklistCmd = &cli.Command{ diff --git a/conformance/driver.go b/conformance/driver.go index b2a902a0c..218198a05 100644 --- a/conformance/driver.go +++ b/conformance/driver.go @@ -2,7 +2,8 @@ package conformance import ( "context" - "fmt" + + "github.com/filecoin-project/specs-actors/actors/crypto" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" @@ -12,7 +13,6 @@ import ( "github.com/filecoin-project/lotus/lib/blockstore" "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/puppet" "github.com/filecoin-project/test-vectors/chaos" "github.com/filecoin-project/test-vectors/schema" @@ -80,11 +80,14 @@ func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, preroot } switch msg.From.Protocol() { case address.SECP256K1: - sb.SecpkMessages = append(sb.SecpkMessages, msg) + sb.SecpkMessages = append(sb.SecpkMessages, toChainMsg(msg)) case address.BLS: - sb.BlsMessages = append(sb.BlsMessages, msg) + sb.BlsMessages = append(sb.BlsMessages, toChainMsg(msg)) default: - return nil, fmt.Errorf("from account is not secpk nor bls: %s", msg.From) + // sneak in messages originating from other addresses as both kinds. + // these should fail, as they are actually invalid senders. + sb.SecpkMessages = append(sb.SecpkMessages, msg) + sb.BlsMessages = append(sb.BlsMessages, msg) } } blocks = append(blocks, sb) @@ -133,17 +136,14 @@ func (d *Driver) ExecuteMessage(bs blockstore.Blockstore, preroot cid.Cid, epoch invoker := vm.NewInvoker() - // add support for the puppet and chaos actors. - if puppetOn, ok := d.selector["puppet_actor"]; ok && puppetOn == "true" { - invoker.Register(puppet.PuppetActorCodeID, puppet.Actor{}, puppet.State{}) - } + // register the chaos actor if required by the vector. if chaosOn, ok := d.selector["chaos_actor"]; ok && chaosOn == "true" { invoker.Register(chaos.ChaosActorCodeCID, chaos.Actor{}, chaos.State{}) } lvm.SetInvoker(invoker) - ret, err := lvm.ApplyMessage(d.ctx, msg) + ret, err := lvm.ApplyMessage(d.ctx, toChainMsg(msg)) if err != nil { return nil, cid.Undef, err } @@ -151,3 +151,22 @@ func (d *Driver) ExecuteMessage(bs blockstore.Blockstore, preroot cid.Cid, epoch root, err := lvm.Flush(d.ctx) return ret, root, err } + +// toChainMsg injects a synthetic 0-filled signature of the right length to +// messages that originate from secp256k senders, leaving all +// others untouched. +// TODO: generate a signature in the DSL so that it's encoded in +// the test vector. +func toChainMsg(msg *types.Message) (ret types.ChainMsg) { + ret = msg + if msg.From.Protocol() == address.SECP256K1 { + ret = &types.SignedMessage{ + Message: *msg, + Signature: crypto.Signature{ + Type: crypto.SigTypeSecp256k1, + Data: make([]byte, 65), + }, + } + } + return ret +} diff --git a/documentation/en/api-methods.md b/documentation/en/api-methods.md index d60e87eb1..bba212d45 100644 --- a/documentation/en/api-methods.md +++ b/documentation/en/api-methods.md @@ -84,7 +84,11 @@ * [MsigSwapPropose](#MsigSwapPropose) * [Net](#Net) * [NetAddrsListen](#NetAddrsListen) + * [NetAgentVersion](#NetAgentVersion) * [NetAutoNatStatus](#NetAutoNatStatus) + * [NetBandwidthStats](#NetBandwidthStats) + * [NetBandwidthStatsByPeer](#NetBandwidthStatsByPeer) + * [NetBandwidthStatsByProtocol](#NetBandwidthStatsByProtocol) * [NetConnect](#NetConnect) * [NetConnectedness](#NetConnectedness) * [NetDisconnect](#NetDisconnect) @@ -93,6 +97,7 @@ * [NetPubsubScores](#NetPubsubScores) * [Paych](#Paych) * [PaychAllocateLane](#PaychAllocateLane) + * [PaychAvailableFunds](#PaychAvailableFunds) * [PaychCollect](#PaychCollect) * [PaychGet](#PaychGet) * [PaychGetWaitReady](#PaychGetWaitReady) @@ -2044,6 +2049,20 @@ Response: } ``` +### NetAgentVersion + + +Perms: read + +Inputs: +```json +[ + "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf" +] +``` + +Response: `"string value"` + ### NetAutoNatStatus @@ -2059,6 +2078,61 @@ Response: } ``` +### NetBandwidthStats + + +Perms: read + +Inputs: `null` + +Response: +```json +{ + "TotalIn": 9, + "TotalOut": 9, + "RateIn": 12.3, + "RateOut": 12.3 +} +``` + +### NetBandwidthStatsByPeer + + +Perms: read + +Inputs: `null` + +Response: +```json +{ + "12D3KooWSXmXLJmBR1M7i9RW9GQPNUhZSzXKzxDHWtAgNuJAbyEJ": { + "TotalIn": 174000, + "TotalOut": 12500, + "RateIn": 100, + "RateOut": 50 + } +} +``` + +### NetBandwidthStatsByProtocol + + +Perms: read + +Inputs: `null` + +Response: +```json +{ + "/fil/hello/1.0.0": { + "TotalIn": 174000, + "TotalOut": 12500, + "RateIn": 100, + "RateOut": 50 + } +} +``` + ### NetConnect @@ -2160,6 +2234,30 @@ Inputs: Response: `42` +### PaychAvailableFunds +There are not yet any comments for this method. + +Perms: sign + +Inputs: +```json +[ + "t01234" +] +``` + +Response: +```json +{ + "Channel": "\u003cempty\u003e", + "ConfirmedAmt": "0", + "PendingAmt": "0", + "PendingWaitSentinel": null, + "QueuedAmt": "0", + "VoucherReedeemedAmt": "0" +} +``` + ### PaychCollect There are not yet any comments for this method. @@ -2415,24 +2513,27 @@ Inputs: Response: ```json { - "ChannelAddr": "t01234", - "TimeLockMin": 10101, - "TimeLockMax": 10101, - "SecretPreimage": "Ynl0ZSBhcnJheQ==", - "Extra": { - "Actor": "t01234", - "Method": 1, - "Data": "Ynl0ZSBhcnJheQ==" + "Voucher": { + "ChannelAddr": "t01234", + "TimeLockMin": 10101, + "TimeLockMax": 10101, + "SecretPreimage": "Ynl0ZSBhcnJheQ==", + "Extra": { + "Actor": "t01234", + "Method": 1, + "Data": "Ynl0ZSBhcnJheQ==" + }, + "Lane": 42, + "Nonce": 42, + "Amount": "0", + "MinSettleHeight": 10101, + "Merges": null, + "Signature": { + "Type": 2, + "Data": "Ynl0ZSBhcnJheQ==" + } }, - "Lane": 42, - "Nonce": 42, - "Amount": "0", - "MinSettleHeight": 10101, - "Merges": null, - "Signature": { - "Type": 2, - "Data": "Ynl0ZSBhcnJheQ==" - } + "Shortfall": "0" } ``` diff --git a/documentation/en/payment-channels.md b/documentation/en/payment-channels.md index 44f9affd9..7179da916 100644 --- a/documentation/en/payment-channels.md +++ b/documentation/en/payment-channels.md @@ -22,7 +22,7 @@ Note that payment channels and vouchers can be used for any situation in which t For example a client creates a payment channel to a provider with value 10 FIL. ```sh -$ lotus paych get 10 +$ lotus paych add-funds 10 ``` @@ -51,10 +51,10 @@ $ lotus paych voucher create 4 The client sends the voucher to the provider and the provider adds the voucher and sends back more data. etc. -The client can add value to the channel after it has been created by calling `paych get` with the same client and provider addresses. +The client can add value to the channel after it has been created by calling `paych add-funds` with the same client and provider addresses. ```sh -$ lotus paych get 5 +$ lotus paych add-funds 5 # Same address as above. Channel now has 15 ``` diff --git a/extern/test-vectors b/extern/test-vectors index 9806d09b0..84da0a5ea 160000 --- a/extern/test-vectors +++ b/extern/test-vectors @@ -1 +1 @@ -Subproject commit 9806d09b005dbaa0d08a6944aca67dd5ad2cd3b3 +Subproject commit 84da0a5ea1256a6e66bcbf73542c93e4916d6356 diff --git a/go.mod b/go.mod index 700d591a6..109f8110b 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/filecoin-project/lotus go 1.14 -replace github.com/supranational/blst => github.com/filecoin-project/blst v0.1.2-adx +replace github.com/supranational/blst => github.com/supranational/blst v0.1.2-alpha.1 require ( contrib.go.opencensus.io/exporter/jaeger v0.1.0 @@ -18,6 +18,7 @@ require ( github.com/docker/go-units v0.4.0 github.com/drand/drand v1.0.3-0.20200714175734-29705eaf09d4 github.com/drand/kyber v1.1.1 + github.com/dustin/go-humanize v1.0.0 github.com/elastic/go-sysinfo v1.3.0 github.com/fatih/color v1.8.0 github.com/filecoin-project/chain-validation v0.0.6-0.20200813000554-40c22fe26eef @@ -28,7 +29,7 @@ require ( github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03 github.com/filecoin-project/go-data-transfer v0.6.3 github.com/filecoin-project/go-fil-commcid v0.0.0-20200716160307-8f644712406f - github.com/filecoin-project/go-fil-markets v0.5.8 + github.com/filecoin-project/go-fil-markets v0.5.9 github.com/filecoin-project/go-jsonrpc v0.1.2-0.20200822201400-474f4fdccc52 github.com/filecoin-project/go-multistore v0.0.3 github.com/filecoin-project/go-padreader v0.0.0-20200210211231-548257017ca6 @@ -39,7 +40,7 @@ require ( github.com/filecoin-project/specs-actors v0.9.3 github.com/filecoin-project/specs-storage v0.1.1-0.20200730063404-f7db367e9401 github.com/filecoin-project/statediff v0.0.1 - github.com/filecoin-project/test-vectors v0.0.0-20200902131127-9806d09b005d + github.com/filecoin-project/test-vectors v0.0.0-20200903223506-84da0a5ea125 github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 github.com/go-kit/kit v0.10.0 github.com/google/uuid v1.1.1 diff --git a/go.sum b/go.sum index 26a64100d..b483f8030 100644 --- a/go.sum +++ b/go.sum @@ -215,8 +215,6 @@ 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/blst v0.1.2-adx h1:qyirtiGFTN/C17y4xlCFAblgw2OXhW8+wtnLwV27/cM= -github.com/filecoin-project/blst v0.1.2-adx/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= 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= @@ -248,6 +246,8 @@ github.com/filecoin-project/go-fil-commcid v0.0.0-20200716160307-8f644712406f/go github.com/filecoin-project/go-fil-markets v0.5.6-0.20200814234959-80b1788108ac/go.mod h1:umicPCaN99ysHTiYOmwhuLxTFbOwcsI+mdw/t96vvM4= github.com/filecoin-project/go-fil-markets v0.5.8 h1:uwl0QNUVmmSlUQfxshpj21Dmhh6WKTQNhnb1GMfdp18= github.com/filecoin-project/go-fil-markets v0.5.8/go.mod h1:6ZX1vbZbnukbVQ8tCB/MmEizuW/bmRX7SpGAltU3KVg= +github.com/filecoin-project/go-fil-markets v0.5.9 h1:iIO17UfIjUCiB37TRwgiBwAyfJJwHb8e8uAfu7F37gc= +github.com/filecoin-project/go-fil-markets v0.5.9/go.mod h1:/cb1IoaiHhwFEWyIAPm9yN6Z+MiPujFZBT8BGH7LwB8= github.com/filecoin-project/go-jsonrpc v0.1.2-0.20200817153016-2ea5cbaf5ec0/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= github.com/filecoin-project/go-jsonrpc v0.1.2-0.20200822201400-474f4fdccc52 h1:FXtCp0ybqdQL9knb3OGDpkNTaBbPxgkqPeWKotUwkH0= github.com/filecoin-project/go-jsonrpc v0.1.2-0.20200822201400-474f4fdccc52/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= @@ -268,7 +268,7 @@ github.com/filecoin-project/go-statestore v0.1.0/go.mod h1:LFc9hD+fRxPqiHiaqUEZO github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b h1:fkRZSPrYpk42PV3/lIXiL0LHetxde7vyYYvSsttQtfg= github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b/go.mod h1:Q0GQOBtKf1oE10eSXSlhN45kDBdGvEcVOqMiffqX+N8= github.com/filecoin-project/lotus v0.4.3-0.20200820203717-d1718369a182/go.mod h1:biFZPQ/YyQGfkHUmHMiaNf2hnD6zm1+OAXPQYQ61Zkg= -github.com/filecoin-project/lotus v0.5.8-0.20200902130912-0962292f920e/go.mod h1:OkZ5aUqs+fFnJOq9243WJDsTa9c3/Ae67NIAwVhAB+0= +github.com/filecoin-project/lotus v0.5.8-0.20200903221953-ada5e6ae68cf/go.mod h1:wxuzS4ozpCFThia18G+J5P0Jp/DSiq9ezzJF1yvZuP4= github.com/filecoin-project/sector-storage v0.0.0-20200712023225-1d67dcfa3c15/go.mod h1:salgVdX7qeXFo/xaiEQE29J4pPkjn71T0kt0n+VDBzo= github.com/filecoin-project/sector-storage v0.0.0-20200730050024-3ee28c3b6d9a/go.mod h1:oOawOl9Yk+qeytLzzIryjI8iRbqo+qzS6EEeElP4PWA= github.com/filecoin-project/sector-storage v0.0.0-20200810171746-eac70842d8e0 h1:E1fZ27fhKK05bhZItfTwqr1i05vXnEZJznQFEYwEEUU= @@ -687,6 +687,8 @@ github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.1.1-0.20190114141812-62fb9bc030d1 h1:qBCV/RLV02TSfQa7tFmxTihnG+u+7JXByOkhlkR5rmQ= github.com/jonboulle/clockwork v0.1.1-0.20190114141812-62fb9bc030d1/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= github.com/jsimonetti/rtnetlink v0.0.0-20190830100107-3784a6c7c552/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= @@ -824,6 +826,7 @@ github.com/libp2p/go-libp2p-core v0.6.1 h1:XS+Goh+QegCDojUZp00CaPMfiEADCrLjNZskW github.com/libp2p/go-libp2p-core v0.6.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-crypto v0.0.1/go.mod h1:yJkNyDmO341d5wwXxDUGO0LykUVT72ImHNUqh5D/dBE= github.com/libp2p/go-libp2p-crypto v0.0.2/go.mod h1:eETI5OUfBnvARGOHrJz2eWNyTUxEGZnBxMcbUjfIj4I= +github.com/libp2p/go-libp2p-crypto v0.1.0 h1:k9MFy+o2zGDNGsaoZl0MA3iZ75qXxr9OOoAZF+sD5OQ= github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= github.com/libp2p/go-libp2p-daemon v0.2.2/go.mod h1:kyrpsLB2JeNYR2rvXSVWyY0iZuRIMhqzWR3im9BV6NQ= github.com/libp2p/go-libp2p-discovery v0.0.1/go.mod h1:ZkkF9xIFRLA1xCc7bstYFkd80gBGK8Fc1JqGoU2i+zI= @@ -849,6 +852,7 @@ github.com/libp2p/go-libp2p-kbucket v0.4.2/go.mod h1:7sCeZx2GkNK1S6lQnGUW5JYZCFP github.com/libp2p/go-libp2p-loggables v0.0.1/go.mod h1:lDipDlBNYbpyqyPX/KcoO+eq0sJYEVR2JgOexcivchg= github.com/libp2p/go-libp2p-loggables v0.1.0 h1:h3w8QFfCt2UJl/0/NW4K829HX/0S4KD31PQ7m8UXXO8= github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= +github.com/libp2p/go-libp2p-metrics v0.0.1 h1:yumdPC/P2VzINdmcKZd0pciSUCpou+s0lwYCjBbzQZU= github.com/libp2p/go-libp2p-metrics v0.0.1/go.mod h1:jQJ95SXXA/K1VZi13h52WZMa9ja78zjyy5rspMsC/08= github.com/libp2p/go-libp2p-mplex v0.1.1/go.mod h1:KUQWpGkCzfV7UIpi8SKsAVxyBgz1c9R5EvxgnwLsb/I= github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo= @@ -871,6 +875,7 @@ github.com/libp2p/go-libp2p-noise v0.1.1 h1:vqYQWvnIcHpIoWJKC7Al4D6Hgj0H012TuXRh github.com/libp2p/go-libp2p-noise v0.1.1/go.mod h1:QDFLdKX7nluB7DEnlVPbz7xlLHdwHFA9HiohJRr3vwM= github.com/libp2p/go-libp2p-peer v0.0.1/go.mod h1:nXQvOBbwVqoP+T5Y5nCjeH4sP9IX/J0AMzcDUVruVoo= github.com/libp2p/go-libp2p-peer v0.1.1/go.mod h1:jkF12jGB4Gk/IOo+yomm+7oLWxF278F7UnrYUQ1Q8es= +github.com/libp2p/go-libp2p-peer v0.2.0 h1:EQ8kMjaCUwt/Y5uLgjT8iY2qg0mGUT0N1zUjer50DsY= github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= github.com/libp2p/go-libp2p-peerstore v0.0.1/go.mod h1:RabLyPVJLuNQ+GFyoEkfi8H4Ti6k/HtZJ7YKgtSq+20= github.com/libp2p/go-libp2p-peerstore v0.0.6/go.mod h1:RabLyPVJLuNQ+GFyoEkfi8H4Ti6k/HtZJ7YKgtSq+20= @@ -887,6 +892,7 @@ github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuD github.com/libp2p/go-libp2p-pnet v0.2.0 h1:J6htxttBipJujEjz1y0a5+eYoiPcFHhSYHH6na5f0/k= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= github.com/libp2p/go-libp2p-protocol v0.0.1/go.mod h1:Af9n4PiruirSDjHycM1QuiMi/1VZNHYcK8cLgFJLZ4s= +github.com/libp2p/go-libp2p-protocol v0.1.0 h1:HdqhEyhg0ToCaxgMhnOmUO8snQtt/kQlcjVk3UoJU3c= github.com/libp2p/go-libp2p-protocol v0.1.0/go.mod h1:KQPHpAabB57XQxGrXCNvbL6UEXfQqUgC/1adR2Xtflk= github.com/libp2p/go-libp2p-pubsub v0.1.1/go.mod h1:ZwlKzRSe1eGvSIdU5bD7+8RZN/Uzw0t1Bp9R1znpR/Q= github.com/libp2p/go-libp2p-pubsub v0.3.2-0.20200527132641-c0712c6e92cf/go.mod h1:TxPOBuo1FPdsTjFnv+FGZbNbWYsp74Culx+4ViQpato= @@ -1370,6 +1376,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/supranational/blst v0.1.2-alpha.1 h1:v0UqVlvbRNZIaSeMPr+T01kvTUq1h0EZuZ6gnDR1Mlg= +github.com/supranational/blst v0.1.2-alpha.1/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= diff --git a/markets/retrievaladapter/client.go b/markets/retrievaladapter/client.go index 59eca595c..e57a11bd1 100644 --- a/markets/retrievaladapter/client.go +++ b/markets/retrievaladapter/client.go @@ -3,11 +3,14 @@ package retrievaladapter import ( "context" + "golang.org/x/xerrors" + + "github.com/filecoin-project/specs-actors/actors/builtin/paych" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-fil-markets/shared" "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/builtin/paych" "github.com/ipfs/go-cid" "github.com/multiformats/go-multiaddr" @@ -56,7 +59,10 @@ func (rcn *retrievalClientNode) CreatePaymentVoucher(ctx context.Context, paymen if err != nil { return nil, err } - return voucher, nil + if voucher.Voucher == nil { + return nil, xerrors.Errorf("Could not create voucher - shortfall: %d", voucher.Shortfall) + } + return voucher.Voucher, nil } func (rcn *retrievalClientNode) GetChainHead(ctx context.Context) (shared.TipSetToken, abi.ChainEpoch, error) { diff --git a/node/builder.go b/node/builder.go index bd9bc5005..75639ec97 100644 --- a/node/builder.go +++ b/node/builder.go @@ -87,6 +87,7 @@ var ( NatPortMapKey = special{8} // Libp2p option ConnectionManagerKey = special{9} // Libp2p option AutoNATSvcKey = special{10} // Libp2p option + BandwidthReporterKey = special{11} // Libp2p option ) type invoke int @@ -192,6 +193,7 @@ func libp2p() Option { Override(new(routing.Routing), lp2p.Routing), Override(NatPortMapKey, lp2p.NatPortMap), + Override(BandwidthReporterKey, lp2p.BandwidthCounter), Override(ConnectionManagerKey, lp2p.ConnectionManager(50, 200, 20*time.Second, nil)), Override(AutoNATSvcKey, lp2p.AutoNATService), diff --git a/node/impl/common/common.go b/node/impl/common/common.go index d149486b1..6a69b2a7a 100644 --- a/node/impl/common/common.go +++ b/node/impl/common/common.go @@ -9,8 +9,10 @@ import ( "github.com/gbrlsnchs/jwt/v3" "github.com/libp2p/go-libp2p-core/host" + metrics "github.com/libp2p/go-libp2p-core/metrics" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" + protocol "github.com/libp2p/go-libp2p-core/protocol" swarm "github.com/libp2p/go-libp2p-swarm" basichost "github.com/libp2p/go-libp2p/p2p/host/basic" ma "github.com/multiformats/go-multiaddr" @@ -32,6 +34,7 @@ type CommonAPI struct { RawHost lp2p.RawHost Host host.Host Router lp2p.BaseIpfsRouting + Reporter metrics.Reporter Sk *dtypes.ScoreKeeper ShutdownChan dtypes.ShutdownChan } @@ -133,6 +136,35 @@ func (a *CommonAPI) NetAutoNatStatus(ctx context.Context) (i api.NatInfo, err er }, nil } +func (a *CommonAPI) NetAgentVersion(ctx context.Context, p peer.ID) (string, error) { + ag, err := a.Host.Peerstore().Get(p, "AgentVersion") + if err != nil { + return "", err + } + + if ag == nil { + return "unknown", nil + } + + return ag.(string), nil +} + +func (a *CommonAPI) NetBandwidthStats(ctx context.Context) (metrics.Stats, error) { + return a.Reporter.GetBandwidthTotals(), nil +} + +func (a *CommonAPI) NetBandwidthStatsByPeer(ctx context.Context) (map[string]metrics.Stats, error) { + out := make(map[string]metrics.Stats) + for p, s := range a.Reporter.GetBandwidthByPeer() { + out[p.String()] = s + } + return out, nil +} + +func (a *CommonAPI) NetBandwidthStatsByProtocol(ctx context.Context) (map[protocol.ID]metrics.Stats, error) { + return a.Reporter.GetBandwidthByProtocol(), nil +} + func (a *CommonAPI) ID(context.Context) (peer.ID, error) { return a.Host.ID(), nil } diff --git a/node/impl/full/chain.go b/node/impl/full/chain.go index 61eb4d3f5..ce5e3822a 100644 --- a/node/impl/full/chain.go +++ b/node/impl/full/chain.go @@ -1,6 +1,7 @@ package full import ( + "bufio" "bytes" "context" "encoding/json" @@ -503,7 +504,11 @@ func (a *ChainAPI) ChainExport(ctx context.Context, nroots abi.ChainEpoch, tsk t out := make(chan []byte) go func() { defer w.Close() //nolint:errcheck // it is a pipe - if err := a.Chain.Export(ctx, ts, nroots, w); err != nil { + + bw := bufio.NewWriterSize(w, 1<<20) + defer bw.Flush() //nolint:errcheck // it is a write to a pipe + + if err := a.Chain.Export(ctx, ts, nroots, bw); err != nil { log.Errorf("chain export call failed: %s", err) return } @@ -512,7 +517,7 @@ func (a *ChainAPI) ChainExport(ctx context.Context, nroots abi.ChainEpoch, tsk t go func() { defer close(out) for { - buf := make([]byte, 4096) + buf := make([]byte, 1<<20) n, err := r.Read(buf) if err != nil && err != io.EOF { log.Errorf("chain export pipe read failed: %s", err) @@ -522,6 +527,7 @@ func (a *ChainAPI) ChainExport(ctx context.Context, nroots abi.ChainEpoch, tsk t case out <- buf[:n]: case <-ctx.Done(): log.Warnf("export writer failed: %s", ctx.Err()) + return } if err == io.EOF { return diff --git a/node/impl/full/gas.go b/node/impl/full/gas.go index 54d9ef1b1..d0fae9afc 100644 --- a/node/impl/full/gas.go +++ b/node/impl/full/gas.go @@ -14,6 +14,7 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/specs-actors/actors/abi" "github.com/filecoin-project/specs-actors/actors/abi/big" "github.com/filecoin-project/specs-actors/actors/builtin" "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" @@ -36,28 +37,11 @@ func (a *GasAPI) GasEstimateFeeCap(ctx context.Context, msg *types.Message, maxq tsk types.TipSetKey) (types.BigInt, error) { ts := a.Chain.GetHeaviestTipSet() - var act types.Actor - err := a.Stmgr.WithParentState(ts, a.Stmgr.WithActor(msg.From, stmgr.GetActor(&act))) - if err != nil { - return types.NewInt(0), xerrors.Errorf("getting actor: %w", err) - } - parentBaseFee := ts.Blocks()[0].ParentBaseFee increaseFactor := math.Pow(1.+1./float64(build.BaseFeeMaxChangeDenom), float64(maxqueueblks)) feeInFuture := types.BigMul(parentBaseFee, types.NewInt(uint64(increaseFactor*(1<<8)))) - feeInFuture = types.BigDiv(feeInFuture, types.NewInt(1<<8)) - - gasLimitBig := types.NewInt(uint64(msg.GasLimit)) - maxAccepted := types.BigDiv(act.Balance, types.NewInt(MaxSpendOnFeeDenom)) - expectedFee := types.BigMul(feeInFuture, gasLimitBig) - - out := feeInFuture - if types.BigCmp(expectedFee, maxAccepted) > 0 { - log.Warnf("Expected fee for message higher than tolerance: %s > %s, setting to tolerance", - types.FIL(expectedFee), types.FIL(maxAccepted)) - out = types.BigDiv(maxAccepted, gasLimitBig) - } + out := types.BigDiv(feeInFuture, types.NewInt(1<<8)) if msg.GasPremium != types.EmptyInt { out = types.BigAdd(out, msg.GasPremium) @@ -225,3 +209,24 @@ func (a *GasAPI) GasEstimateMessageGas(ctx context.Context, msg *types.Message, return msg, nil } + +func capGasFee(msg *types.Message, maxFee abi.TokenAmount) { + if maxFee.Equals(big.Zero()) { + maxFee = types.NewInt(build.FilecoinPrecision / 10) + } + + gl := types.NewInt(uint64(msg.GasLimit)) + totalFee := types.BigMul(msg.GasFeeCap, gl) + minerFee := types.BigMul(msg.GasPremium, gl) + + if totalFee.LessThanEqual(maxFee) { + return + } + + // scale chain/miner fee down proportionally to fit in our budget + // TODO: there are probably smarter things we can do here to optimize + // message inclusion latency + + msg.GasFeeCap = big.Div(maxFee, gl) + msg.GasPremium = big.Div(big.Div(big.Mul(minerFee, maxFee), totalFee), gl) +} diff --git a/node/impl/full/mpool.go b/node/impl/full/mpool.go index 735b982d0..bfb7439bb 100644 --- a/node/impl/full/mpool.go +++ b/node/impl/full/mpool.go @@ -8,9 +8,6 @@ import ( "go.uber.org/fx" "golang.org/x/xerrors" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/messagepool" "github.com/filecoin-project/lotus/chain/store" @@ -115,27 +112,6 @@ func (a *MpoolAPI) MpoolPush(ctx context.Context, smsg *types.SignedMessage) (ci return a.Mpool.Push(smsg) } -func capGasFee(msg *types.Message, maxFee abi.TokenAmount) { - if maxFee.Equals(big.Zero()) { - return - } - - gl := types.NewInt(uint64(msg.GasLimit)) - totalFee := types.BigMul(msg.GasFeeCap, gl) - minerFee := types.BigMul(msg.GasPremium, gl) - - if totalFee.LessThanEqual(maxFee) { - return - } - - // scale chain/miner fee down proportionally to fit in our budget - // TODO: there are probably smarter things we can do here to optimize - // message inclusion latency - - msg.GasFeeCap = big.Div(maxFee, gl) - msg.GasPremium = big.Div(big.Div(big.Mul(minerFee, maxFee), totalFee), gl) -} - func (a *MpoolAPI) MpoolPushMessage(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec) (*types.SignedMessage, error) { { fromA, err := a.Stmgr.ResolveToKeyAddress(ctx, msg.From, nil) diff --git a/node/impl/paych/paych.go b/node/impl/paych/paych.go index 5ce88c501..e998f969f 100644 --- a/node/impl/paych/paych.go +++ b/node/impl/paych/paych.go @@ -3,9 +3,10 @@ package paych import ( "context" + "golang.org/x/xerrors" + "github.com/ipfs/go-cid" "go.uber.org/fx" - "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/specs-actors/actors/builtin/paych" @@ -38,6 +39,10 @@ func (a *PaychAPI) PaychGet(ctx context.Context, from, to address.Address, amt t }, nil } +func (a *PaychAPI) PaychAvailableFunds(from, to address.Address) (*api.ChannelAvailableFunds, error) { + return a.PaychMgr.AvailableFunds(from, to) +} + func (a *PaychAPI) PaychGetWaitReady(ctx context.Context, sentinel cid.Cid) (address.Address, error) { return a.PaychMgr.GetPaychWaitReady(ctx, sentinel) } @@ -64,9 +69,7 @@ func (a *PaychAPI) PaychNewPayment(ctx context.Context, from, to address.Address svs := make([]*paych.SignedVoucher, len(vouchers)) for i, v := range vouchers { - sv, err := a.paychVoucherCreate(ctx, ch.Channel, paych.SignedVoucher{ - ChannelAddr: ch.Channel, - + sv, err := a.PaychMgr.CreateVoucher(ctx, ch.Channel, paych.SignedVoucher{ Amount: v.Amount, Lane: lane, @@ -78,8 +81,11 @@ func (a *PaychAPI) PaychNewPayment(ctx context.Context, from, to address.Address if err != nil { return nil, err } + if sv.Voucher == nil { + return nil, xerrors.Errorf("Could not create voucher - shortfall of %d", sv.Shortfall) + } - svs[i] = sv + svs[i] = sv.Voucher } return &api.PaymentInfo{ @@ -129,41 +135,10 @@ func (a *PaychAPI) PaychVoucherAdd(ctx context.Context, ch address.Address, sv * // that will be used to create the voucher, so if previous vouchers exist, the // actual additional value of this voucher will only be the difference between // the two. -func (a *PaychAPI) PaychVoucherCreate(ctx context.Context, pch address.Address, amt types.BigInt, lane uint64) (*paych.SignedVoucher, error) { - return a.paychVoucherCreate(ctx, pch, paych.SignedVoucher{ChannelAddr: pch, Amount: amt, Lane: lane}) -} - -func (a *PaychAPI) paychVoucherCreate(ctx context.Context, pch address.Address, voucher paych.SignedVoucher) (*paych.SignedVoucher, error) { - ci, err := a.PaychMgr.GetChannelInfo(pch) - if err != nil { - return nil, xerrors.Errorf("get channel info: %w", err) - } - - nonce, err := a.PaychMgr.NextNonceForLane(ctx, pch, voucher.Lane) - if err != nil { - return nil, xerrors.Errorf("getting next nonce for lane: %w", err) - } - - sv := &voucher - sv.Nonce = nonce - - vb, err := sv.SigningBytes() - if err != nil { - return nil, err - } - - sig, err := a.WalletSign(ctx, ci.Control, vb) - if err != nil { - return nil, err - } - - sv.Signature = sig - - if _, err := a.PaychMgr.AddVoucherOutbound(ctx, pch, sv, nil, types.NewInt(0)); err != nil { - return nil, xerrors.Errorf("failed to persist voucher: %w", err) - } - - return sv, nil +// If there are insufficient funds in the channel to create the voucher, +// returns a nil voucher and the shortfall. +func (a *PaychAPI) PaychVoucherCreate(ctx context.Context, pch address.Address, amt types.BigInt, lane uint64) (*api.VoucherCreateResult, error) { + return a.PaychMgr.CreateVoucher(ctx, pch, paych.SignedVoucher{Amount: amt, Lane: lane}) } func (a *PaychAPI) PaychVoucherList(ctx context.Context, pch address.Address) ([]*paych.SignedVoucher, error) { diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 3507453dd..c688ff677 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -320,14 +320,12 @@ func (sm *StorageMinerAPI) MarketListRetrievalDeals(ctx context.Context) ([]retr return out, nil } -func (sm *StorageMinerAPI) MarketGetDealUpdates(ctx context.Context, d cid.Cid) (<-chan storagemarket.MinerDeal, error) { +func (sm *StorageMinerAPI) MarketGetDealUpdates(ctx context.Context) (<-chan storagemarket.MinerDeal, error) { results := make(chan storagemarket.MinerDeal) unsub := sm.StorageProvider.SubscribeToEvents(func(evt storagemarket.ProviderEvent, deal storagemarket.MinerDeal) { - if deal.ProposalCid.Equals(d) { - select { - case results <- deal: - case <-ctx.Done(): - } + select { + case results <- deal: + case <-ctx.Done(): } }) go func() { diff --git a/paychmgr/accessorcache.go b/paychmgr/accessorcache.go index 43223200d..176fbdd11 100644 --- a/paychmgr/accessorcache.go +++ b/paychmgr/accessorcache.go @@ -60,7 +60,7 @@ func (pm *Manager) accessorCacheKey(from address.Address, to address.Address) st // access a channel use the same lock (the lock on the accessor) func (pm *Manager) addAccessorToCache(from address.Address, to address.Address) *channelAccessor { key := pm.accessorCacheKey(from, to) - ca := newChannelAccessor(pm) + ca := newChannelAccessor(pm, from, to) // TODO: Use LRU pm.channels[key] = ca return ca diff --git a/paychmgr/manager.go b/paychmgr/manager.go index b1755a7d3..d1fd715ef 100644 --- a/paychmgr/manager.go +++ b/paychmgr/manager.go @@ -4,6 +4,8 @@ import ( "context" "sync" + "github.com/filecoin-project/specs-actors/actors/crypto" + "github.com/filecoin-project/lotus/node/modules/helpers" "github.com/ipfs/go-datastore" @@ -49,6 +51,7 @@ type paychAPI interface { StateWaitMsg(ctx context.Context, msg cid.Cid, confidence uint64) (*api.MsgLookup, error) MpoolPushMessage(ctx context.Context, msg *types.Message, maxFee *api.MessageSendSpec) (*types.SignedMessage, error) WalletHas(ctx context.Context, addr address.Address) (bool, error) + WalletSign(ctx context.Context, k address.Address, msg []byte) (*crypto.Signature, error) } // managerAPI defines all methods needed by the manager @@ -135,7 +138,16 @@ func (pm *Manager) GetPaych(ctx context.Context, from, to address.Address, amt t return address.Undef, cid.Undef, err } - return chanAccessor.getPaych(ctx, from, to, amt) + return chanAccessor.getPaych(ctx, amt) +} + +func (pm *Manager) AvailableFunds(from address.Address, to address.Address) (*api.ChannelAvailableFunds, error) { + chanAccessor, err := pm.accessorByFromTo(from, to) + if err != nil { + return nil, err + } + + return chanAccessor.availableFunds() } // GetPaychWaitReady waits until the create channel / add funds message with the @@ -179,6 +191,15 @@ 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) { + ca, err := pm.accessorByAddress(ch) + if err != nil { + return nil, err + } + + return ca.createVoucher(ctx, ch, voucher) +} + // 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). @@ -309,14 +330,6 @@ func (pm *Manager) ListVouchers(ctx context.Context, ch address.Address) ([]*Vou return ca.listVouchers(ctx, ch) } -func (pm *Manager) NextNonceForLane(ctx context.Context, ch address.Address, lane uint64) (uint64, error) { - ca, err := pm.accessorByAddress(ch) - if err != nil { - return 0, err - } - return ca.nextNonceForLane(ctx, ch, lane) -} - func (pm *Manager) Settle(ctx context.Context, addr address.Address) (cid.Cid, error) { ca, err := pm.accessorByAddress(addr) if err != nil { diff --git a/paychmgr/mock_test.go b/paychmgr/mock_test.go index 54519bae2..d2aa047ee 100644 --- a/paychmgr/mock_test.go +++ b/paychmgr/mock_test.go @@ -5,6 +5,10 @@ import ( "fmt" "sync" + "github.com/filecoin-project/lotus/lib/sigs" + + "github.com/filecoin-project/specs-actors/actors/crypto" + cbornode "github.com/ipfs/go-ipld-cbor" "github.com/filecoin-project/specs-actors/actors/util/adt" @@ -132,6 +136,7 @@ type mockPaychAPI struct { waitingCalls map[cid.Cid]*waitingCall waitingResponses map[cid.Cid]*waitingResponse wallet map[address.Address]struct{} + signingKey []byte } func newMockPaychAPI() *mockPaychAPI { @@ -240,3 +245,17 @@ func (pchapi *mockPaychAPI) addWalletAddress(addr address.Address) { pchapi.wallet[addr] = struct{}{} } + +func (pchapi *mockPaychAPI) WalletSign(ctx context.Context, k address.Address, msg []byte) (*crypto.Signature, error) { + pchapi.lk.Lock() + defer pchapi.lk.Unlock() + + return sigs.Sign(crypto.SigTypeSecp256k1, pchapi.signingKey, msg) +} + +func (pchapi *mockPaychAPI) addSigningKey(key []byte) { + pchapi.lk.Lock() + defer pchapi.lk.Unlock() + + pchapi.signingKey = key +} diff --git a/paychmgr/paych.go b/paychmgr/paych.go index 0f12adb11..be43aaf9b 100644 --- a/paychmgr/paych.go +++ b/paychmgr/paych.go @@ -5,6 +5,8 @@ import ( "context" "fmt" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/specs-actors/actors/util/adt" "github.com/ipfs/go-cid" @@ -21,11 +23,36 @@ import ( xerrors "golang.org/x/xerrors" ) +// insufficientFundsErr indicates that there are not enough funds in the +// channel to create a voucher +type insufficientFundsErr interface { + Shortfall() types.BigInt +} + +type ErrInsufficientFunds struct { + shortfall types.BigInt +} + +func newErrInsufficientFunds(shortfall types.BigInt) *ErrInsufficientFunds { + return &ErrInsufficientFunds{shortfall: shortfall} +} + +func (e *ErrInsufficientFunds) Error() string { + return fmt.Sprintf("not enough funds in channel to cover voucher - shortfall: %d", e.shortfall) +} + +func (e *ErrInsufficientFunds) Shortfall() types.BigInt { + return e.shortfall +} + // channelAccessor is used to simplify locking when accessing a channel type channelAccessor struct { - // waitCtx is used by processes that wait for things to be confirmed - // on chain - waitCtx context.Context + from address.Address + to address.Address + + // chctx is used by background processes (eg when waiting for things to be + // confirmed on chain) + chctx context.Context sa *stateAccessor api managerAPI store *Store @@ -34,14 +61,16 @@ type channelAccessor struct { msgListeners msgListeners } -func newChannelAccessor(pm *Manager) *channelAccessor { +func newChannelAccessor(pm *Manager, from address.Address, to address.Address) *channelAccessor { return &channelAccessor{ - lk: &channelLock{globalLock: &pm.lk}, + from: from, + to: to, + chctx: pm.ctx, sa: pm.sa, api: pm.pchapi, store: pm.store, + lk: &channelLock{globalLock: &pm.lk}, msgListeners: newMsgListeners(), - waitCtx: pm.ctx, } } @@ -52,6 +81,69 @@ func (ca *channelAccessor) getChannelInfo(addr address.Address) (*ChannelInfo, e return ca.store.ByAddress(addr) } +// createVoucher creates a voucher with the given specification, setting its +// 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) { + ca.lk.Lock() + defer ca.lk.Unlock() + + // Find the channel for the voucher + ci, err := ca.store.ByAddress(ch) + if err != nil { + return nil, xerrors.Errorf("failed to get channel info by address: %w", err) + } + + // Set the voucher channel + sv := &voucher + sv.ChannelAddr = ch + + // Get the next nonce on the given lane + sv.Nonce = ca.nextNonceForLane(ci, voucher.Lane) + + // Sign the voucher + vb, err := sv.SigningBytes() + if err != nil { + return nil, xerrors.Errorf("failed to get voucher signing bytes: %w", err) + } + + sig, err := ca.api.WalletSign(ctx, ci.Control, vb) + if err != nil { + return nil, xerrors.Errorf("failed to sign voucher: %w", err) + } + sv.Signature = sig + + // Store the voucher + if _, err := ca.addVoucherUnlocked(ctx, ch, sv, nil, types.NewInt(0)); err != nil { + // If there are not enough funds in the channel to cover the voucher, + // return a voucher create result with the shortfall + var ife insufficientFundsErr + if xerrors.As(err, &ife) { + return &api.VoucherCreateResult{ + Shortfall: ife.Shortfall(), + }, nil + } + + return nil, xerrors.Errorf("failed to persist voucher: %w", err) + } + + return &api.VoucherCreateResult{Voucher: sv, Shortfall: types.NewInt(0)}, nil +} + +func (ca *channelAccessor) nextNonceForLane(ci *ChannelInfo, lane uint64) uint64 { + var maxnonce uint64 + for _, v := range ci.Vouchers { + if v.Voucher.Lane == lane { + if v.Voucher.Nonce > maxnonce { + maxnonce = v.Voucher.Nonce + } + } + } + + return maxnonce + 1 +} + func (ca *channelAccessor) checkVoucherValid(ctx context.Context, ch address.Address, sv *paych.SignedVoucher) (map[uint64]*paych.LaneState, error) { ca.lk.Lock() defer ca.lk.Unlock() @@ -133,7 +225,7 @@ func (ca *channelAccessor) checkVoucherValidUnlocked(ctx context.Context, ch add // Must not exceed actor balance newTotal := types.BigAdd(totalRedeemed, pchState.ToSend) if act.Balance.LessThan(newTotal) { - return nil, fmt.Errorf("not enough funds in channel to cover voucher") + return nil, newErrInsufficientFunds(types.BigSub(newTotal, act.Balance)) } if len(sv.Merges) != 0 { @@ -221,6 +313,10 @@ func (ca *channelAccessor) addVoucher(ctx context.Context, ch address.Address, s 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) { ci, err := ca.store.ByAddress(ch) if err != nil { return types.BigInt{}, err @@ -382,28 +478,6 @@ func (ca *channelAccessor) listVouchers(ctx context.Context, ch address.Address) return ca.store.VouchersForPaych(ch) } -func (ca *channelAccessor) nextNonceForLane(ctx context.Context, ch address.Address, lane uint64) (uint64, error) { - ca.lk.Lock() - defer ca.lk.Unlock() - - // TODO: should this take into account lane state? - vouchers, err := ca.store.VouchersForPaych(ch) - if err != nil { - return 0, err - } - - var maxnonce uint64 - for _, v := range vouchers { - if v.Voucher.Lane == lane { - if v.Voucher.Nonce > maxnonce { - maxnonce = v.Voucher.Nonce - } - } - } - - return maxnonce + 1, nil -} - // 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) { @@ -479,7 +553,7 @@ func (ca *channelAccessor) totalRedeemedWithVoucher(laneStates map[uint64]*paych lane, ok := laneStates[sv.Lane] if ok { // If the voucher is for an existing lane, and the voucher nonce - // and is higher than the lane nonce + // is higher than the lane nonce if sv.Nonce > lane.Nonce { // Add the delta between the redeemed amount and the voucher // amount to the total diff --git a/paychmgr/paych_test.go b/paychmgr/paych_test.go index 4439fb2fb..e1ae487e1 100644 --- a/paychmgr/paych_test.go +++ b/paychmgr/paych_test.go @@ -381,11 +381,76 @@ func TestCheckVoucherValidCountingAllLanes(t *testing.T) { 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) + + // Create a voucher in lane 1 + voucherLane1Amt := big.NewInt(5) + voucher := paych.SignedVoucher{ + Lane: 1, + Amount: voucherLane1Amt, + } + res, err := s.mgr.CreateVoucher(ctx, s.ch, voucher) + require.NoError(t, err) + require.NotNil(t, res.Voucher) + require.Equal(t, s.ch, res.Voucher.ChannelAddr) + require.Equal(t, voucherLane1Amt, res.Voucher.Amount) + require.EqualValues(t, 0, res.Shortfall.Int64()) + + nonce := res.Voucher.Nonce + + // Create a voucher in lane 1 again, with a higher amount + voucherLane1Amt = big.NewInt(8) + voucher = paych.SignedVoucher{ + Lane: 1, + Amount: voucherLane1Amt, + } + res, err = s.mgr.CreateVoucher(ctx, s.ch, voucher) + require.NoError(t, err) + require.NotNil(t, res.Voucher) + require.Equal(t, s.ch, res.Voucher.ChannelAddr) + require.Equal(t, voucherLane1Amt, res.Voucher.Amount) + require.EqualValues(t, 0, res.Shortfall.Int64()) + require.Equal(t, nonce+1, res.Voucher.Nonce) + + // Create a voucher in lane 2 that covers all the remaining funds + // in the channel + voucherLane2Amt := big.Sub(s.amt, voucherLane1Amt) + voucher = paych.SignedVoucher{ + Lane: 2, + Amount: voucherLane2Amt, + } + res, err = s.mgr.CreateVoucher(ctx, s.ch, voucher) + require.NoError(t, err) + require.NotNil(t, res.Voucher) + require.Equal(t, s.ch, res.Voucher.ChannelAddr) + require.Equal(t, voucherLane2Amt, res.Voucher.Amount) + require.EqualValues(t, 0, res.Shortfall.Int64()) + + // Create a voucher in lane 2 that exceeds the remaining funds in the + // channel + voucherLane2Amt = big.Add(voucherLane2Amt, big.NewInt(1)) + voucher = paych.SignedVoucher{ + Lane: 2, + Amount: voucherLane2Amt, + } + res, err = s.mgr.CreateVoucher(ctx, s.ch, voucher) + require.NoError(t, err) + + // Expect a shortfall value equal to the amount required to add the voucher + // to the channel + require.Nil(t, res.Voucher) + require.EqualValues(t, 1, res.Shortfall.Int64()) +} + func TestAddVoucherDelta(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - mgr, _, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(ctx, t) voucherLane := uint64(1) @@ -393,23 +458,23 @@ func TestAddVoucherDelta(t *testing.T) { minDelta := big.NewInt(2) nonce := uint64(1) voucherAmount := big.NewInt(1) - sv := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err := mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) + sv := createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err := s.mgr.AddVoucherOutbound(ctx, s.ch, sv, nil, minDelta) require.Error(t, err) // Expect success when adding a voucher whose amount is equal to minDelta nonce++ voucherAmount = big.NewInt(2) - sv = createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - delta, err := mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) + sv = createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + delta, err := s.mgr.AddVoucherOutbound(ctx, s.ch, sv, nil, minDelta) require.NoError(t, err) require.EqualValues(t, delta.Int64(), 2) // Check that delta is correct when there's an existing voucher nonce++ voucherAmount = big.NewInt(5) - sv = createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - delta, err = mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) + sv = createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + delta, err = s.mgr.AddVoucherOutbound(ctx, s.ch, sv, nil, minDelta) require.NoError(t, err) require.EqualValues(t, delta.Int64(), 3) @@ -417,8 +482,8 @@ func TestAddVoucherDelta(t *testing.T) { nonce = uint64(1) voucherAmount = big.NewInt(6) voucherLane = uint64(2) - sv = createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - delta, err = mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) + sv = createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + delta, err = s.mgr.AddVoucherOutbound(ctx, s.ch, sv, nil, minDelta) require.NoError(t, err) require.EqualValues(t, delta.Int64(), 6) } @@ -427,7 +492,7 @@ func TestAddVoucherNextLane(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - mgr, _, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(ctx, t) minDelta := big.NewInt(0) voucherAmount := big.NewInt(2) @@ -435,40 +500,40 @@ func TestAddVoucherNextLane(t *testing.T) { // Add a voucher in lane 2 nonce := uint64(1) voucherLane := uint64(2) - sv := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err := mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) + sv := createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err := s.mgr.AddVoucherOutbound(ctx, s.ch, sv, nil, minDelta) require.NoError(t, err) - ci, err := mgr.GetChannelInfo(ch) + ci, err := s.mgr.GetChannelInfo(s.ch) require.NoError(t, err) require.EqualValues(t, ci.NextLane, 3) // Allocate a lane (should be lane 3) - lane, err := mgr.AllocateLane(ch) + lane, err := s.mgr.AllocateLane(s.ch) require.NoError(t, err) require.EqualValues(t, lane, 3) - ci, err = mgr.GetChannelInfo(ch) + ci, err = s.mgr.GetChannelInfo(s.ch) require.NoError(t, err) require.EqualValues(t, ci.NextLane, 4) // Add a voucher in lane 1 voucherLane = uint64(1) - sv = createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err = mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) + sv = createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err = s.mgr.AddVoucherOutbound(ctx, s.ch, sv, nil, minDelta) require.NoError(t, err) - ci, err = mgr.GetChannelInfo(ch) + ci, err = s.mgr.GetChannelInfo(s.ch) require.NoError(t, err) require.EqualValues(t, ci.NextLane, 4) // Add a voucher in lane 7 voucherLane = uint64(7) - sv = createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err = mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) + sv = createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err = s.mgr.AddVoucherOutbound(ctx, s.ch, sv, nil, minDelta) require.NoError(t, err) - ci, err = mgr.GetChannelInfo(ch) + ci, err = s.mgr.GetChannelInfo(s.ch) require.NoError(t, err) require.EqualValues(t, ci.NextLane, 8) } @@ -477,15 +542,15 @@ func TestAllocateLane(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - mgr, _, ch, _ := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(ctx, t) // First lane should be 0 - lane, err := mgr.AllocateLane(ch) + lane, err := s.mgr.AllocateLane(s.ch) require.NoError(t, err) require.EqualValues(t, lane, 0) // Next lane should be 1 - lane, err = mgr.AllocateLane(ch) + lane, err = s.mgr.AllocateLane(s.ch) require.NoError(t, err) require.EqualValues(t, lane, 1) } @@ -553,7 +618,7 @@ func TestAddVoucherProof(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - mgr, _, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(ctx, t) nonce := uint64(1) voucherAmount := big.NewInt(1) @@ -563,34 +628,34 @@ func TestAddVoucherProof(t *testing.T) { // Add a voucher with no proof var proof []byte - sv := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err := mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) + sv := createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err := s.mgr.AddVoucherOutbound(ctx, s.ch, sv, nil, minDelta) require.NoError(t, err) // Expect one voucher with no proof - ci, err := mgr.GetChannelInfo(ch) + ci, err := s.mgr.GetChannelInfo(s.ch) require.NoError(t, err) require.Len(t, ci.Vouchers, 1) require.Len(t, ci.Vouchers[0].Proof, 0) // Add same voucher with no proof voucherLane = uint64(1) - _, err = mgr.AddVoucherOutbound(ctx, ch, sv, proof, minDelta) + _, err = s.mgr.AddVoucherOutbound(ctx, s.ch, sv, proof, minDelta) require.NoError(t, err) // Expect one voucher with no proof - ci, err = mgr.GetChannelInfo(ch) + ci, err = s.mgr.GetChannelInfo(s.ch) require.NoError(t, err) require.Len(t, ci.Vouchers, 1) require.Len(t, ci.Vouchers[0].Proof, 0) // Add same voucher with proof proof = []byte{1} - _, err = mgr.AddVoucherOutbound(ctx, ch, sv, proof, minDelta) + _, err = s.mgr.AddVoucherOutbound(ctx, s.ch, sv, proof, minDelta) require.NoError(t, err) // Should add proof to existing voucher - ci, err = mgr.GetChannelInfo(ch) + ci, err = s.mgr.GetChannelInfo(s.ch) require.NoError(t, err) require.Len(t, ci.Vouchers, 1) require.Len(t, ci.Vouchers[0].Proof, 1) @@ -663,47 +728,47 @@ func TestBestSpendable(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - mgr, mock, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(ctx, t) // Add vouchers to lane 1 with amounts: [1, 2, 3] voucherLane := uint64(1) minDelta := big.NewInt(0) nonce := uint64(1) voucherAmount := big.NewInt(1) - svL1V1 := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err := mgr.AddVoucherInbound(ctx, ch, svL1V1, nil, minDelta) + svL1V1 := createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err := s.mgr.AddVoucherInbound(ctx, s.ch, svL1V1, nil, minDelta) require.NoError(t, err) nonce++ voucherAmount = big.NewInt(2) - svL1V2 := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err = mgr.AddVoucherInbound(ctx, ch, svL1V2, nil, minDelta) + svL1V2 := createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err = s.mgr.AddVoucherInbound(ctx, s.ch, svL1V2, nil, minDelta) require.NoError(t, err) nonce++ voucherAmount = big.NewInt(3) - svL1V3 := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err = mgr.AddVoucherInbound(ctx, ch, svL1V3, nil, minDelta) + svL1V3 := createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err = s.mgr.AddVoucherInbound(ctx, s.ch, svL1V3, nil, minDelta) require.NoError(t, err) // Add voucher to lane 2 with amounts: [2] voucherLane = uint64(2) nonce = uint64(1) voucherAmount = big.NewInt(2) - svL2V1 := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err = mgr.AddVoucherInbound(ctx, ch, svL2V1, nil, minDelta) + svL2V1 := createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err = s.mgr.AddVoucherInbound(ctx, s.ch, svL2V1, nil, minDelta) require.NoError(t, err) // Return success exit code from calls to check if voucher is spendable - bsapi := newMockBestSpendableAPI(mgr) - mock.setCallResponse(&api.InvocResult{ + bsapi := newMockBestSpendableAPI(s.mgr) + s.mock.setCallResponse(&api.InvocResult{ MsgRct: &types.MessageReceipt{ ExitCode: 0, }, }) // Verify best spendable vouchers on each lane - vouchers, err := BestSpendableByLane(ctx, bsapi, ch) + vouchers, err := BestSpendableByLane(ctx, bsapi, s.ch) require.NoError(t, err) require.Len(t, vouchers, 2) @@ -716,21 +781,21 @@ func TestBestSpendable(t *testing.T) { require.EqualValues(t, 2, vchr.Amount.Int64()) // Submit voucher from lane 2 - _, err = mgr.SubmitVoucher(ctx, ch, svL2V1, nil, nil) + _, err = s.mgr.SubmitVoucher(ctx, s.ch, svL2V1, nil, nil) require.NoError(t, err) // Best spendable voucher should no longer include lane 2 // (because voucher has not been submitted) - vouchers, err = BestSpendableByLane(ctx, bsapi, ch) + vouchers, err = BestSpendableByLane(ctx, bsapi, s.ch) require.NoError(t, err) require.Len(t, vouchers, 1) // Submit first voucher from lane 1 - _, err = mgr.SubmitVoucher(ctx, ch, svL1V1, nil, nil) + _, err = s.mgr.SubmitVoucher(ctx, s.ch, svL1V1, nil, nil) require.NoError(t, err) // Best spendable voucher for lane 1 should still be highest value voucher - vouchers, err = BestSpendableByLane(ctx, bsapi, ch) + vouchers, err = BestSpendableByLane(ctx, bsapi, s.ch) require.NoError(t, err) require.Len(t, vouchers, 1) @@ -743,18 +808,18 @@ func TestCheckSpendable(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - mgr, mock, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(ctx, t) // Create voucher with Extra voucherLane := uint64(1) nonce := uint64(1) voucherAmount := big.NewInt(1) - voucher := createTestVoucherWithExtra(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) + voucher := createTestVoucherWithExtra(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) // Add voucher with proof minDelta := big.NewInt(0) proof := []byte("proof") - _, err := mgr.AddVoucherInbound(ctx, ch, voucher, proof, minDelta) + _, err := s.mgr.AddVoucherInbound(ctx, s.ch, voucher, proof, minDelta) require.NoError(t, err) // Return success exit code from VM call, which indicates that voucher is @@ -764,17 +829,17 @@ func TestCheckSpendable(t *testing.T) { ExitCode: 0, }, } - mock.setCallResponse(successResponse) + s.mock.setCallResponse(successResponse) // Check that spendable is true secret := []byte("secret") otherProof := []byte("other proof") - spendable, err := mgr.CheckVoucherSpendable(ctx, ch, voucher, secret, otherProof) + spendable, err := s.mgr.CheckVoucherSpendable(ctx, s.ch, voucher, secret, otherProof) require.NoError(t, err) require.True(t, spendable) // Check that the secret and proof were passed through correctly - lastCall := mock.getLastCall() + lastCall := s.mock.getLastCall() var p paych.UpdateChannelStateParams err = p.UnmarshalCBOR(bytes.NewReader(lastCall.Params)) require.NoError(t, err) @@ -784,11 +849,11 @@ func TestCheckSpendable(t *testing.T) { // Check that if no proof is supplied, the proof supplied to add voucher // above is used secret2 := []byte("secret2") - spendable, err = mgr.CheckVoucherSpendable(ctx, ch, voucher, secret2, nil) + spendable, err = s.mgr.CheckVoucherSpendable(ctx, s.ch, voucher, secret2, nil) require.NoError(t, err) require.True(t, spendable) - lastCall = mock.getLastCall() + lastCall = s.mock.getLastCall() var p2 paych.UpdateChannelStateParams err = p2.UnmarshalCBOR(bytes.NewReader(lastCall.Params)) require.NoError(t, err) @@ -796,26 +861,26 @@ func TestCheckSpendable(t *testing.T) { require.Equal(t, secret2, p2.Secret) // Check that if VM call returns non-success exit code, spendable is false - mock.setCallResponse(&api.InvocResult{ + s.mock.setCallResponse(&api.InvocResult{ MsgRct: &types.MessageReceipt{ ExitCode: 1, }, }) - spendable, err = mgr.CheckVoucherSpendable(ctx, ch, voucher, secret, nil) + spendable, err = s.mgr.CheckVoucherSpendable(ctx, s.ch, voucher, secret, nil) require.NoError(t, err) require.False(t, spendable) // Return success exit code (indicating voucher is spendable) - mock.setCallResponse(successResponse) - spendable, err = mgr.CheckVoucherSpendable(ctx, ch, voucher, secret, nil) + s.mock.setCallResponse(successResponse) + spendable, err = s.mgr.CheckVoucherSpendable(ctx, s.ch, voucher, secret, nil) require.NoError(t, err) require.True(t, spendable) // Check that voucher is no longer spendable once it has been submitted - _, err = mgr.SubmitVoucher(ctx, ch, voucher, nil, nil) + _, err = s.mgr.SubmitVoucher(ctx, s.ch, voucher, nil, nil) require.NoError(t, err) - spendable, err = mgr.CheckVoucherSpendable(ctx, ch, voucher, secret, nil) + spendable, err = s.mgr.CheckVoucherSpendable(ctx, s.ch, voucher, secret, nil) require.NoError(t, err) require.False(t, spendable) } @@ -824,28 +889,28 @@ func TestSubmitVoucher(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - mgr, mock, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(ctx, t) // Create voucher with Extra voucherLane := uint64(1) nonce := uint64(1) voucherAmount := big.NewInt(1) - voucher := createTestVoucherWithExtra(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) + voucher := createTestVoucherWithExtra(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) // Add voucher with proof minDelta := big.NewInt(0) addVoucherProof := []byte("proof") - _, err := mgr.AddVoucherInbound(ctx, ch, voucher, addVoucherProof, minDelta) + _, err := s.mgr.AddVoucherInbound(ctx, s.ch, voucher, addVoucherProof, minDelta) require.NoError(t, err) // Submit voucher secret := []byte("secret") submitProof := []byte("submit proof") - submitCid, err := mgr.SubmitVoucher(ctx, ch, voucher, secret, submitProof) + submitCid, err := s.mgr.SubmitVoucher(ctx, s.ch, voucher, secret, submitProof) require.NoError(t, err) // Check that the secret and proof were passed through correctly - msg := mock.pushedMessages(submitCid) + msg := s.mock.pushedMessages(submitCid) var p paych.UpdateChannelStateParams err = p.UnmarshalCBOR(bytes.NewReader(msg.Message.Params)) require.NoError(t, err) @@ -858,14 +923,14 @@ func TestSubmitVoucher(t *testing.T) { voucherAmount = big.NewInt(2) addVoucherProof2 := []byte("proof2") secret2 := []byte("secret2") - voucher = createTestVoucherWithExtra(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err = mgr.AddVoucherInbound(ctx, ch, voucher, addVoucherProof2, minDelta) + voucher = createTestVoucherWithExtra(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err = s.mgr.AddVoucherInbound(ctx, s.ch, voucher, addVoucherProof2, minDelta) require.NoError(t, err) - submitCid, err = mgr.SubmitVoucher(ctx, ch, voucher, secret2, nil) + submitCid, err = s.mgr.SubmitVoucher(ctx, s.ch, voucher, secret2, nil) require.NoError(t, err) - msg = mock.pushedMessages(submitCid) + msg = s.mock.pushedMessages(submitCid) var p2 paych.UpdateChannelStateParams err = p2.UnmarshalCBOR(bytes.NewReader(msg.Message.Params)) require.NoError(t, err) @@ -877,11 +942,11 @@ func TestSubmitVoucher(t *testing.T) { voucherAmount = big.NewInt(3) secret3 := []byte("secret2") proof3 := []byte("proof3") - voucher = createTestVoucherWithExtra(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - submitCid, err = mgr.SubmitVoucher(ctx, ch, voucher, secret3, proof3) + voucher = createTestVoucherWithExtra(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + submitCid, err = s.mgr.SubmitVoucher(ctx, s.ch, voucher, secret3, proof3) require.NoError(t, err) - msg = mock.pushedMessages(submitCid) + msg = s.mock.pushedMessages(submitCid) var p3 paych.UpdateChannelStateParams err = p3.UnmarshalCBOR(bytes.NewReader(msg.Message.Params)) require.NoError(t, err) @@ -889,7 +954,7 @@ func TestSubmitVoucher(t *testing.T) { require.Equal(t, secret3, p3.Secret) // Verify that vouchers are marked as submitted - vis, err := mgr.ListVouchers(ctx, ch) + vis, err := s.mgr.ListVouchers(ctx, s.ch) require.NoError(t, err) require.Len(t, vis, 3) @@ -898,55 +963,20 @@ func TestSubmitVoucher(t *testing.T) { } // Attempting to submit the same voucher again should fail - _, err = mgr.SubmitVoucher(ctx, ch, voucher, secret2, nil) + _, err = s.mgr.SubmitVoucher(ctx, s.ch, voucher, secret2, nil) require.Error(t, err) } -func TestNextNonceForLane(t *testing.T) { - ctx := context.Background() - - // Set up a manager with a single payment channel - mgr, _, ch, key := testSetupMgrWithChannel(ctx, t) - - // Expect next nonce for non-existent lane to be 1 - next, err := mgr.NextNonceForLane(ctx, ch, 1) - require.NoError(t, err) - require.EqualValues(t, next, 1) - - voucherAmount := big.NewInt(1) - minDelta := big.NewInt(0) - voucherAmount = big.NewInt(2) - - // Add vouchers such that we have - // lane 1: nonce 2 - // lane 1: nonce 4 - voucherLane := uint64(1) - for _, nonce := range []uint64{2, 4} { - voucherAmount = big.Add(voucherAmount, big.NewInt(1)) - sv := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, key) - _, err := mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) - require.NoError(t, err) - } - - // lane 2: nonce 7 - voucherLane = uint64(2) - nonce := uint64(7) - sv := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, key) - _, err = mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) - require.NoError(t, err) - - // Expect next nonce for lane 1 to be 5 - next, err = mgr.NextNonceForLane(ctx, ch, 1) - require.NoError(t, err) - require.EqualValues(t, next, 5) - - // Expect next nonce for lane 2 to be 8 - next, err = mgr.NextNonceForLane(ctx, ch, 2) - require.NoError(t, err) - require.EqualValues(t, next, 8) +type testScaffold struct { + mgr *Manager + mock *mockManagerAPI + ch address.Address + amt big.Int + fromAcct address.Address + fromKeyPrivate []byte } -func testSetupMgrWithChannel(ctx context.Context, t *testing.T) (*Manager, *mockManagerAPI, address.Address, []byte) { +func testSetupMgrWithChannel(ctx context.Context, t *testing.T) *testScaffold { fromKeyPrivate, fromKeyPublic := testGenerateKeyPair(t) ch := tutils.NewIDAddr(t, 100) @@ -962,11 +992,12 @@ func testSetupMgrWithChannel(ctx context.Context, t *testing.T) (*Manager, *mock mock.setAccountState(toAcct, account.State{Address: to}) // Create channel in state + balance := big.NewInt(20) act := &types.Actor{ Code: builtin.AccountActorCodeID, Head: cid.Cid{}, Nonce: 0, - Balance: big.NewInt(20), + Balance: balance, } mock.setPaychState(ch, act, paych.State{ From: fromAcct, @@ -991,7 +1022,17 @@ func testSetupMgrWithChannel(ctx context.Context, t *testing.T) (*Manager, *mock err = mgr.store.putChannelInfo(ci) require.NoError(t, err) - return mgr, mock, ch, fromKeyPrivate + // Add the from signing key to the wallet + mock.addSigningKey(fromKeyPrivate) + + return &testScaffold{ + mgr: mgr, + mock: mock, + ch: ch, + amt: balance, + fromAcct: fromAcct, + fromKeyPrivate: fromKeyPrivate, + } } func testGenerateKeyPair(t *testing.T) ([]byte, []byte) { diff --git a/paychmgr/paychget_test.go b/paychmgr/paychget_test.go index 64ef6f9d7..8eff08bdd 100644 --- a/paychmgr/paychget_test.go +++ b/paychmgr/paychget_test.go @@ -6,6 +6,12 @@ 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/specs-actors/actors/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" @@ -650,8 +656,6 @@ func TestPaychGetMergeAddFunds(t *testing.T) { require.NoError(t, err) // Queue up two add funds requests behind create channel - //var addFundsQueuedUp sync.WaitGroup - //addFundsQueuedUp.Add(2) var addFundsSent sync.WaitGroup addFundsSent.Add(2) @@ -662,7 +666,6 @@ func TestPaychGetMergeAddFunds(t *testing.T) { var addFundsMcid1 cid.Cid var addFundsMcid2 cid.Cid go func() { - //go addFundsQueuedUp.Done() defer addFundsSent.Done() // Request add funds - should block until create channel has completed @@ -671,7 +674,6 @@ func TestPaychGetMergeAddFunds(t *testing.T) { }() go func() { - //go addFundsQueuedUp.Done() defer addFundsSent.Done() // Request add funds again - should merge with waiting add funds request @@ -899,6 +901,168 @@ func TestPaychGetMergeAddFundsCtxCancelAll(t *testing.T) { require.Equal(t, createAmt, createMsg.Message.Value) } +// TestPaychAvailableFunds tests that PaychAvailableFunds returns the correct +// channel state +func TestPaychAvailableFunds(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.NewIDAddr(t, 102) + fromAcct := tutils.NewActorAddr(t, "fromAct") + toAcct := tutils.NewActorAddr(t, "toAct") + + mock := newMockManagerAPI() + defer mock.close() + + mgr, err := newManager(store, mock) + require.NoError(t, err) + + // No channel created yet so available funds should be all zeroes + av, err := mgr.AvailableFunds(from, to) + require.NoError(t, err) + require.Nil(t, av.Channel) + require.Nil(t, av.PendingWaitSentinel) + require.EqualValues(t, 0, av.ConfirmedAmt.Int64()) + require.EqualValues(t, 0, av.PendingAmt.Int64()) + require.EqualValues(t, 0, av.QueuedAmt.Int64()) + require.EqualValues(t, 0, av.VoucherReedeemedAmt.Int64()) + + // 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) + + // Available funds should reflect create channel message sent + av, err = mgr.AvailableFunds(from, to) + require.NoError(t, err) + require.Nil(t, av.Channel) + require.EqualValues(t, 0, av.ConfirmedAmt.Int64()) + require.EqualValues(t, createAmt, av.PendingAmt) + require.EqualValues(t, 0, av.QueuedAmt.Int64()) + require.EqualValues(t, 0, av.VoucherReedeemedAmt.Int64()) + // Should now have a pending wait sentinel + require.NotNil(t, av.PendingWaitSentinel) + + // Queue up an add funds request behind create channel + var addFundsSent sync.WaitGroup + addFundsSent.Add(1) + + addFundsAmt := big.NewInt(5) + var addFundsMcid cid.Cid + go func() { + defer addFundsSent.Done() + + // Request add funds - should block until create channel has completed + _, addFundsMcid, err = mgr.GetPaych(ctx, from, to, addFundsAmt) + require.NoError(t, err) + }() + + // Wait for add funds request to be queued up + waitForQueueSize(t, mgr, from, to, 1) + + // Available funds should now include queued funds + av, err = mgr.AvailableFunds(from, to) + require.NoError(t, err) + require.Nil(t, av.Channel) + require.NotNil(t, av.PendingWaitSentinel) + require.EqualValues(t, 0, av.ConfirmedAmt.Int64()) + // create amount is still pending + require.EqualValues(t, createAmt, av.PendingAmt) + // queued amount now includes add funds amount + require.EqualValues(t, addFundsAmt, av.QueuedAmt) + 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}) + 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, + }) + + // Send create channel response + response := testChannelResponse(t, ch) + mock.receiveMsgResponse(createMsgCid, response) + + // Wait for create channel response + chres, err := mgr.GetPaychWaitReady(ctx, *av.PendingWaitSentinel) + require.NoError(t, err) + require.Equal(t, ch, chres) + + // Wait for add funds request to be sent + addFundsSent.Wait() + + // Available funds should now include the channel and also a wait sentinel + // for the add funds message + av, err = mgr.AvailableFunds(from, to) + require.NoError(t, err) + require.NotNil(t, av.Channel) + require.NotNil(t, av.PendingWaitSentinel) + // create amount is now confirmed + require.EqualValues(t, createAmt, av.ConfirmedAmt) + // add funds amount it now pending + require.EqualValues(t, addFundsAmt, av.PendingAmt) + require.EqualValues(t, 0, av.QueuedAmt.Int64()) + require.EqualValues(t, 0, av.VoucherReedeemedAmt.Int64()) + + // Send success add funds response + mock.receiveMsgResponse(addFundsMcid, types.MessageReceipt{ + ExitCode: 0, + Return: []byte{}, + }) + + // Wait for add funds response + _, err = mgr.GetPaychWaitReady(ctx, *av.PendingWaitSentinel) + require.NoError(t, err) + + // Available funds should no longer have a wait sentinel + av, err = mgr.AvailableFunds(from, to) + require.NoError(t, err) + require.NotNil(t, av.Channel) + require.Nil(t, av.PendingWaitSentinel) + // confirmed amount now includes create and add funds amounts + require.EqualValues(t, types.BigAdd(createAmt, addFundsAmt), av.ConfirmedAmt) + require.EqualValues(t, 0, av.PendingAmt.Int64()) + require.EqualValues(t, 0, av.QueuedAmt.Int64()) + require.EqualValues(t, 0, av.VoucherReedeemedAmt.Int64()) + + // Add some vouchers + voucherAmt1 := types.NewInt(3) + voucher := createTestVoucher(t, ch, 1, 1, voucherAmt1, fromKeyPrivate) + _, err = mgr.AddVoucherOutbound(ctx, ch, voucher, nil, types.NewInt(0)) + require.NoError(t, err) + + voucherAmt2 := types.NewInt(2) + voucher = createTestVoucher(t, ch, 2, 1, voucherAmt2, fromKeyPrivate) + _, err = mgr.AddVoucherOutbound(ctx, ch, voucher, nil, types.NewInt(0)) + require.NoError(t, err) + + av, err = mgr.AvailableFunds(from, to) + require.NoError(t, err) + require.NotNil(t, av.Channel) + require.Nil(t, av.PendingWaitSentinel) + require.EqualValues(t, types.BigAdd(createAmt, addFundsAmt), av.ConfirmedAmt) + require.EqualValues(t, 0, av.PendingAmt.Int64()) + require.EqualValues(t, 0, av.QueuedAmt.Int64()) + // voucher redeemed amount now includes vouchers + require.EqualValues(t, types.BigAdd(voucherAmt1, voucherAmt2), av.VoucherReedeemedAmt) +} + // waitForQueueSize waits for the funds request queue to be of the given size func waitForQueueSize(t *testing.T, mgr *Manager, from address.Address, to address.Address, size int) { ca, err := mgr.accessorByFromTo(from, to) diff --git a/paychmgr/simple.go b/paychmgr/simple.go index b2f3c4404..88d94645e 100644 --- a/paychmgr/simple.go +++ b/paychmgr/simple.go @@ -6,6 +6,8 @@ import ( "fmt" "sync" + "github.com/filecoin-project/lotus/api" + "golang.org/x/sync/errgroup" "github.com/filecoin-project/specs-actors/actors/abi/big" @@ -34,8 +36,6 @@ type paychFundsRes struct { type fundsReq struct { ctx context.Context promise chan *paychFundsRes - from address.Address - to address.Address amt types.BigInt lk sync.Mutex @@ -45,13 +45,11 @@ type fundsReq struct { active bool } -func newFundsReq(ctx context.Context, from address.Address, to address.Address, amt types.BigInt) *fundsReq { +func newFundsReq(ctx context.Context, amt types.BigInt) *fundsReq { promise := make(chan *paychFundsRes) return &fundsReq{ ctx: ctx, promise: promise, - from: from, - to: to, amt: amt, active: true, } @@ -148,14 +146,6 @@ func (m *mergedFundsReq) onComplete(res *paychFundsRes) { } } -func (m *mergedFundsReq) from() address.Address { - return m.reqs[0].from -} - -func (m *mergedFundsReq) to() address.Address { - return m.reqs[0].to -} - // sum is the sum of the amounts in all requests in the merge func (m *mergedFundsReq) sum() types.BigInt { sum := types.NewInt(0) @@ -178,9 +168,9 @@ func (m *mergedFundsReq) sum() types.BigInt { // address and the CID of the new add funds message. // If an operation returns an error, subsequent waiting operations will still // be attempted. -func (ca *channelAccessor) getPaych(ctx context.Context, from, to address.Address, amt types.BigInt) (address.Address, cid.Cid, error) { +func (ca *channelAccessor) getPaych(ctx context.Context, amt types.BigInt) (address.Address, cid.Cid, error) { // Add the request to add funds to a queue and wait for the result - freq := newFundsReq(ctx, from, to, amt) + freq := newFundsReq(ctx, amt) ca.enqueue(freq) select { case res := <-freq.promise: @@ -191,17 +181,17 @@ func (ca *channelAccessor) getPaych(ctx context.Context, from, to address.Addres } } -// Queue up an add funds operations +// Queue up an add funds operation func (ca *channelAccessor) enqueue(task *fundsReq) { ca.lk.Lock() defer ca.lk.Unlock() ca.fundsReqQueue = append(ca.fundsReqQueue, task) - go ca.processQueue() + go ca.processQueue() // nolint: errcheck } // Run the operations in the queue -func (ca *channelAccessor) processQueue() { +func (ca *channelAccessor) processQueue() (*api.ChannelAvailableFunds, error) { ca.lk.Lock() defer ca.lk.Unlock() @@ -210,7 +200,7 @@ func (ca *channelAccessor) processQueue() { // If there's nothing in the queue, bail out if len(ca.fundsReqQueue) == 0 { - return + return ca.currentAvailableFunds(types.NewInt(0)) } // Merge all pending requests into one. @@ -221,17 +211,17 @@ func (ca *channelAccessor) processQueue() { if amt.IsZero() { // Note: The amount can be zero if requests are cancelled as we're // building the mergedFundsReq - return + return ca.currentAvailableFunds(amt) } - res := ca.processTask(merged.ctx, merged.from(), merged.to(), amt) + res := ca.processTask(merged.ctx, amt) // If the task is waiting on an external event (eg something to appear on // chain) it will return nil if res == nil { // Stop processing the fundsReqQueue and wait. When the event occurs it will // call processQueue() again - return + return ca.currentAvailableFunds(amt) } // Finished processing so clear the queue @@ -239,6 +229,8 @@ func (ca *channelAccessor) processQueue() { // Call the task callback with its results merged.onComplete(res) + + return ca.currentAvailableFunds(types.NewInt(0)) } // filterQueue filters cancelled requests out of the queue @@ -291,32 +283,83 @@ func (ca *channelAccessor) msgWaitComplete(mcid cid.Cid, err error) { // The queue may have been waiting for msg completion to proceed, so // process the next queue item if len(ca.fundsReqQueue) > 0 { - go ca.processQueue() + go ca.processQueue() // nolint: errcheck } } +func (ca *channelAccessor) currentAvailableFunds(queuedAmt types.BigInt) (*api.ChannelAvailableFunds, error) { + channelInfo, err := ca.store.OutboundActiveByFromTo(ca.from, ca.to) + if err == ErrChannelNotTracked { + // If the channel does not exist we still want to return an empty + // ChannelAvailableFunds, so that clients can check for the existence + // of a channel between from / to without getting an error. + return &api.ChannelAvailableFunds{ + Channel: nil, + ConfirmedAmt: types.NewInt(0), + PendingAmt: types.NewInt(0), + PendingWaitSentinel: nil, + QueuedAmt: queuedAmt, + VoucherReedeemedAmt: types.NewInt(0), + }, nil + } + if err != nil { + return nil, err + } + + // The channel may have a pending create or add funds message + waitSentinel := channelInfo.CreateMsg + if waitSentinel == nil { + waitSentinel = channelInfo.AddFundsMsg + } + + // Get the total amount redeemed by vouchers. + // This includes vouchers that have been submitted, and vouchers that are + // in the datastore but haven't yet been submitted. + totalRedeemed := types.NewInt(0) + if channelInfo.Channel != nil { + ch := *channelInfo.Channel + _, pchState, err := ca.sa.loadPaychActorState(ca.chctx, ch) + if err != nil { + return nil, err + } + + laneStates, err := ca.laneState(ca.chctx, pchState, ch) + if err != nil { + return nil, err + } + + for _, ls := range laneStates { + totalRedeemed = types.BigAdd(totalRedeemed, ls.Redeemed) + } + } + + return &api.ChannelAvailableFunds{ + Channel: channelInfo.Channel, + ConfirmedAmt: channelInfo.Amount, + PendingAmt: channelInfo.PendingAmount, + PendingWaitSentinel: waitSentinel, + QueuedAmt: queuedAmt, + VoucherReedeemedAmt: totalRedeemed, + }, nil +} + // processTask checks the state of the channel and takes appropriate action // (see description of getPaych). // Note that processTask may be called repeatedly in the same state, and should // return nil if there is no state change to be made (eg when waiting for a // message to be confirmed on chain) -func (ca *channelAccessor) processTask( - ctx context.Context, - from address.Address, - to address.Address, - amt types.BigInt, -) *paychFundsRes { +func (ca *channelAccessor) processTask(ctx context.Context, amt types.BigInt) *paychFundsRes { // Get the payment channel for the from/to addresses. // Note: It's ok if we get ErrChannelNotTracked. It just means we need to // create a channel. - channelInfo, err := ca.store.OutboundActiveByFromTo(from, to) + channelInfo, err := ca.store.OutboundActiveByFromTo(ca.from, ca.to) if err != nil && err != ErrChannelNotTracked { return &paychFundsRes{err: err} } // If a channel has not yet been created, create one. if channelInfo == nil { - mcid, err := ca.createPaych(ctx, from, to, amt) + mcid, err := ca.createPaych(ctx, amt) if err != nil { return &paychFundsRes{err: err} } @@ -348,8 +391,8 @@ func (ca *channelAccessor) processTask( } // createPaych sends a message to create the channel and returns the message cid -func (ca *channelAccessor) createPaych(ctx context.Context, from, to address.Address, amt types.BigInt) (cid.Cid, error) { - params, aerr := actors.SerializeParams(&paych.ConstructorParams{From: from, To: to}) +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}) if aerr != nil { return cid.Undef, aerr } @@ -364,7 +407,7 @@ func (ca *channelAccessor) createPaych(ctx context.Context, from, to address.Add msg := &types.Message{ To: builtin.InitActorAddr, - From: from, + From: ca.from, Value: amt, Method: builtin.MethodsInit.Exec, Params: enc, @@ -377,7 +420,7 @@ func (ca *channelAccessor) createPaych(ctx context.Context, from, to address.Add mcid := smsg.Cid() // Create a new channel in the store - ci, err := ca.store.CreateChannel(from, to, mcid, amt) + ci, err := ca.store.CreateChannel(ca.from, ca.to, mcid, amt) if err != nil { log.Errorf("creating channel: %s", err) return cid.Undef, err @@ -397,7 +440,7 @@ func (ca *channelAccessor) waitForPaychCreateMsg(channelID string, mcid cid.Cid) } func (ca *channelAccessor) waitPaychCreateMsg(channelID string, mcid cid.Cid) error { - mwait, err := ca.api.StateWaitMsg(ca.waitCtx, mcid, build.MessageConfidence) + mwait, err := ca.api.StateWaitMsg(ca.chctx, mcid, build.MessageConfidence) if err != nil { log.Errorf("wait msg: %w", err) return err @@ -480,7 +523,7 @@ func (ca *channelAccessor) waitForAddFundsMsg(channelID string, mcid cid.Cid) { } func (ca *channelAccessor) waitAddFundsMsg(channelID string, mcid cid.Cid) error { - mwait, err := ca.api.StateWaitMsg(ca.waitCtx, mcid, build.MessageConfidence) + mwait, err := ca.api.StateWaitMsg(ca.chctx, mcid, build.MessageConfidence) if err != nil { log.Error(err) return err @@ -669,3 +712,7 @@ func (ca *channelAccessor) msgPromise(ctx context.Context, mcid cid.Cid) chan on return promise } + +func (ca *channelAccessor) availableFunds() (*api.ChannelAvailableFunds, error) { + return ca.processQueue() +} diff --git a/paychmgr/store.go b/paychmgr/store.go index 0ae81da57..4a5a4f49f 100644 --- a/paychmgr/store.go +++ b/paychmgr/store.go @@ -59,12 +59,12 @@ type ChannelInfo struct { ChannelID string // Channel address - may be nil if the channel hasn't been created yet Channel *address.Address - // Control is the address of the account that created the channel + // Control is the address of the local node Control address.Address - // Target is the address of the account on the other end of the channel + // Target is the address of the remote node (on the other end of the channel) Target address.Address - // Direction indicates if the channel is inbound (this node is the Target) - // or outbound (this node is the Control) + // Direction indicates if the channel is inbound (Control is the "to" address) + // or outbound (Control is the "from" address) Direction uint64 // Vouchers is a list of all vouchers sent on the channel Vouchers []*VoucherInfo