Merge branch 'master' into inmem-journal

This commit is contained in:
Raúl Kripalani 2020-09-04 10:55:40 +01:00
commit 1ec534d607
50 changed files with 1682 additions and 521 deletions

View File

@ -1,3 +1,5 @@
comment: off
ignore:
- "cbor_gen.go"
github_checks:
annotations: false

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,6 +12,12 @@ import (
)
func TestRepubMessages(t *testing.T) {
oldRepublishBatchDelay := RepublishBatchDelay
RepublishBatchDelay = time.Microsecond
defer func() {
RepublishBatchDelay = oldRepublishBatchDelay
}()
tma := newTestMpoolAPI()
ds := datastore.NewMapDatastore()

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = "<unknown>"
}
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()
},
}

View File

@ -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> <to>"))
}
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
}

View File

@ -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> <receiver> <amount>
// creator: paych add-funds <creator> <receiver> <amount>
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 <creator> <receiver> <amount>
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 <channel> <amount>
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> <receiver> <amount>
// creator: paych add-funds <creator> <receiver> <amount>
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 <creator> <receiver> <amount>
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 <channel> <amount> --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 <channel> <amount> --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=<node api listener address>
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 {

View File

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

View File

@ -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!")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <client addr> <provider addr> 10
$ lotus paych add-funds <client addr> <provider addr> 10
<channel addr>
```
@ -51,10 +51,10 @@ $ lotus paych voucher create <channel addr> 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 <client addr> <provider addr> 5
$ lotus paych add-funds <client addr> <provider addr> 5
<channel addr> # Same address as above. Channel now has 15
```

2
extern/test-vectors vendored

@ -1 +1 @@
Subproject commit 9806d09b005dbaa0d08a6944aca67dd5ad2cd3b3
Subproject commit 84da0a5ea1256a6e66bcbf73542c93e4916d6356

7
go.mod
View File

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

14
go.sum
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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