From 2c98bf0cc70f83aca4bc07db1dbefa529866cb05 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Tue, 1 Sep 2020 16:33:44 +0200 Subject: [PATCH 01/41] feat: PaychAvailableFunds API method --- api/api_full.go | 30 ++- api/apistruct/struct.go | 9 +- api/test/paych.go | 10 +- cli/paych.go | 12 +- cli/paych_test.go | 55 +++++- markets/retrievaladapter/client.go | 10 +- node/impl/paych/paych.go | 55 ++---- paychmgr/accessorcache.go | 2 +- paychmgr/manager.go | 29 ++- paychmgr/mock_test.go | 19 ++ paychmgr/paych.go | 134 +++++++++++--- paychmgr/paych_test.go | 283 +++++++++++++++++------------ paychmgr/paychget_test.go | 172 +++++++++++++++++- paychmgr/simple.go | 81 ++++++++- 14 files changed, 675 insertions(+), 226 deletions(-) diff --git a/api/api_full.go b/api/api_full.go index 2f146267b..4aaa9bb01 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -418,6 +418,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) @@ -426,7 +427,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) @@ -535,6 +536,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 @@ -550,6 +568,16 @@ type VoucherSpec struct { Extra *paych.ModVerifyParams } +// VoucherCreateResult is the response to calling PaychVoucherCreate +type VoucherCreateResult struct { + // Voucher that was created, or nil if there was an error or if there + // were insufficient funds in the channel + Voucher *paych.SignedVoucher + // Shortfall is the additional amount that would be needed in the channel + // in order to be able to create the voucher + Shortfall types.BigInt +} + type MinerPower struct { MinerPower power.Claim TotalPower power.Claim diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 33b02933f..6634436a4 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -203,6 +203,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 +214,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"` } @@ -882,6 +883,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 +907,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) } diff --git a/api/test/paych.go b/api/test/paych.go index faa0bf8d9..b0ccc0a5c 100644 --- a/api/test/paych.go +++ b/api/test/paych.go @@ -96,18 +96,24 @@ func TestPaymentChannels(t *testing.T, b APIBuilder, blocktime time.Duration) { if err != nil { t.Fatal(err) } + if vouch1.Voucher == nil { + t.Fatal(fmt.Errorf("Not enough funds to create voucher: missing %d", vouch1.Shortfall)) + } vouch2, err := paymentCreator.PaychVoucherCreate(ctx, channel, abi.NewTokenAmount(2000), lane) if err != nil { t.Fatal(err) } - delta1, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouch1, nil, abi.NewTokenAmount(1000)) + if vouch2.Voucher == nil { + t.Fatal(fmt.Errorf("Not enough funds to create voucher: missing %d", vouch2.Shortfall)) + } + delta1, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouch1.Voucher, nil, abi.NewTokenAmount(1000)) if err != nil { t.Fatal(err) } if !delta1.Equals(abi.NewTokenAmount(1000)) { t.Fatal("voucher didn't have the right amount") } - delta2, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouch2, nil, abi.NewTokenAmount(1000)) + delta2, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouch2.Voucher, nil, abi.NewTokenAmount(1000)) if err != nil { t.Fatal(err) } diff --git a/cli/paych.go b/cli/paych.go index ff4d769da..ce92af0bc 100644 --- a/cli/paych.go +++ b/cli/paych.go @@ -217,9 +217,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 +232,16 @@ var paychVoucherCreateCmd = &cli.Command{ ctx := ReqContext(cctx) - sv, err := api.PaychVoucherCreate(ctx, ch, amt, uint64(lane)) + v, err := api.PaychVoucherCreate(ctx, ch, types.BigInt(amt), uint64(lane)) if err != nil { return err } - enc, err := EncodedString(sv) + if v.Voucher == nil { + return fmt.Errorf("Could not create voucher: insufficient funds in channel, shortfall: %d", v.Shortfall) + } + + enc, err := EncodedString(v.Voucher) if err != nil { return err } diff --git a/cli/paych_test.go b/cli/paych_test.go index 11a08fb2c..31d41e36a 100644 --- a/cli/paych_test.go +++ b/cli/paych_test.go @@ -6,6 +6,7 @@ import ( "flag" "fmt" "os" + "regexp" "strconv" "strings" "testing" @@ -248,6 +249,50 @@ func TestPaymentChannelVouchers(t *testing.T) { checkVoucherOutput(t, bestSpendable, bestVouchers) } +// TestPaymentChannelVoucherCreateShortfall verifies that if a voucher amount +// is greater than what's left in the channel, voucher create fails +func TestPaymentChannelVoucherCreateShortfall(t *testing.T) { + _ = os.Setenv("BELLMAN_NO_GPU", "1") + + blocktime := 5 * time.Millisecond + ctx := context.Background() + nodes, addrs := startTwoNodesOneMiner(ctx, t, blocktime) + paymentCreator := nodes[0] + creatorAddr := addrs[0] + receiverAddr := addrs[1] + + // Create mock CLI + mockCLI := newMockCLI(t) + creatorCLI := mockCLI.client(paymentCreator.ListenAddr) + + // creator: paych get + channelAmt := 100 + cmd := []string{creatorAddr.String(), receiverAddr.String(), fmt.Sprintf("%d", channelAmt)} + chstr := creatorCLI.runCmd(paychGetCmd, cmd) + + chAddr, err := address.NewFromString(chstr) + require.NoError(t, err) + + // creator: paych voucher create --lane=1 + voucherAmt1 := 60 + lane1 := "--lane=1" + cmd = []string{lane1, chAddr.String(), strconv.Itoa(voucherAmt1)} + voucher1 := creatorCLI.runCmd(paychVoucherCreateCmd, cmd) + fmt.Println(voucher1) + + // creator: paych voucher create --lane=2 + lane2 := "--lane=2" + voucherAmt2 := 70 + cmd = []string{lane2, chAddr.String(), strconv.Itoa(voucherAmt2)} + _, err = creatorCLI.runCmdRaw(paychVoucherCreateCmd, cmd) + + // Should fail because channel doesn't have required amount + require.Error(t, err) + + shortfall := voucherAmt1 + voucherAmt2 - channelAmt + require.Regexp(t, regexp.MustCompile(fmt.Sprintf("shortfall: %d", shortfall)), err.Error()) +} + func checkVoucherOutput(t *testing.T, list string, vouchers []voucherSpec) { lines := strings.Split(list, "\n") listVouchers := make(map[string]string) @@ -350,6 +395,13 @@ type mockCLIClient struct { } func (c *mockCLIClient) runCmd(cmd *cli.Command, input []string) string { + out, err := c.runCmdRaw(cmd, input) + require.NoError(c.t, err) + + return out +} + +func (c *mockCLIClient) runCmdRaw(cmd *cli.Command, input []string) (string, error) { // prepend --api= apiFlag := "--api=" + c.addr.String() input = append([]string{apiFlag}, input...) @@ -359,12 +411,11 @@ func (c *mockCLIClient) runCmd(cmd *cli.Command, input []string) string { require.NoError(c.t, err) err = cmd.Action(cli.NewContext(c.cctx.App, fs, c.cctx)) - require.NoError(c.t, err) // Get the output str := strings.TrimSpace(c.out.String()) c.out.Reset() - return str + return str, err } func (c *mockCLIClient) flagSet(cmd *cli.Command) *flag.FlagSet { diff --git a/markets/retrievaladapter/client.go b/markets/retrievaladapter/client.go index 59eca595c..e57a11bd1 100644 --- a/markets/retrievaladapter/client.go +++ b/markets/retrievaladapter/client.go @@ -3,11 +3,14 @@ package retrievaladapter import ( "context" + "golang.org/x/xerrors" + + "github.com/filecoin-project/specs-actors/actors/builtin/paych" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-fil-markets/shared" "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/builtin/paych" "github.com/ipfs/go-cid" "github.com/multiformats/go-multiaddr" @@ -56,7 +59,10 @@ func (rcn *retrievalClientNode) CreatePaymentVoucher(ctx context.Context, paymen if err != nil { return nil, err } - return voucher, nil + if voucher.Voucher == nil { + return nil, xerrors.Errorf("Could not create voucher - shortfall: %d", voucher.Shortfall) + } + return voucher.Voucher, nil } func (rcn *retrievalClientNode) GetChainHead(ctx context.Context) (shared.TipSetToken, abi.ChainEpoch, error) { diff --git a/node/impl/paych/paych.go b/node/impl/paych/paych.go index 5ce88c501..e998f969f 100644 --- a/node/impl/paych/paych.go +++ b/node/impl/paych/paych.go @@ -3,9 +3,10 @@ package paych import ( "context" + "golang.org/x/xerrors" + "github.com/ipfs/go-cid" "go.uber.org/fx" - "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/specs-actors/actors/builtin/paych" @@ -38,6 +39,10 @@ func (a *PaychAPI) PaychGet(ctx context.Context, from, to address.Address, amt t }, nil } +func (a *PaychAPI) PaychAvailableFunds(from, to address.Address) (*api.ChannelAvailableFunds, error) { + return a.PaychMgr.AvailableFunds(from, to) +} + func (a *PaychAPI) PaychGetWaitReady(ctx context.Context, sentinel cid.Cid) (address.Address, error) { return a.PaychMgr.GetPaychWaitReady(ctx, sentinel) } @@ -64,9 +69,7 @@ func (a *PaychAPI) PaychNewPayment(ctx context.Context, from, to address.Address svs := make([]*paych.SignedVoucher, len(vouchers)) for i, v := range vouchers { - sv, err := a.paychVoucherCreate(ctx, ch.Channel, paych.SignedVoucher{ - ChannelAddr: ch.Channel, - + sv, err := a.PaychMgr.CreateVoucher(ctx, ch.Channel, paych.SignedVoucher{ Amount: v.Amount, Lane: lane, @@ -78,8 +81,11 @@ func (a *PaychAPI) PaychNewPayment(ctx context.Context, from, to address.Address if err != nil { return nil, err } + if sv.Voucher == nil { + return nil, xerrors.Errorf("Could not create voucher - shortfall of %d", sv.Shortfall) + } - svs[i] = sv + svs[i] = sv.Voucher } return &api.PaymentInfo{ @@ -129,41 +135,10 @@ func (a *PaychAPI) PaychVoucherAdd(ctx context.Context, ch address.Address, sv * // that will be used to create the voucher, so if previous vouchers exist, the // actual additional value of this voucher will only be the difference between // the two. -func (a *PaychAPI) PaychVoucherCreate(ctx context.Context, pch address.Address, amt types.BigInt, lane uint64) (*paych.SignedVoucher, error) { - return a.paychVoucherCreate(ctx, pch, paych.SignedVoucher{ChannelAddr: pch, Amount: amt, Lane: lane}) -} - -func (a *PaychAPI) paychVoucherCreate(ctx context.Context, pch address.Address, voucher paych.SignedVoucher) (*paych.SignedVoucher, error) { - ci, err := a.PaychMgr.GetChannelInfo(pch) - if err != nil { - return nil, xerrors.Errorf("get channel info: %w", err) - } - - nonce, err := a.PaychMgr.NextNonceForLane(ctx, pch, voucher.Lane) - if err != nil { - return nil, xerrors.Errorf("getting next nonce for lane: %w", err) - } - - sv := &voucher - sv.Nonce = nonce - - vb, err := sv.SigningBytes() - if err != nil { - return nil, err - } - - sig, err := a.WalletSign(ctx, ci.Control, vb) - if err != nil { - return nil, err - } - - sv.Signature = sig - - if _, err := a.PaychMgr.AddVoucherOutbound(ctx, pch, sv, nil, types.NewInt(0)); err != nil { - return nil, xerrors.Errorf("failed to persist voucher: %w", err) - } - - return sv, nil +// If there are insufficient funds in the channel to create the voucher, +// returns a nil voucher and the shortfall. +func (a *PaychAPI) PaychVoucherCreate(ctx context.Context, pch address.Address, amt types.BigInt, lane uint64) (*api.VoucherCreateResult, error) { + return a.PaychMgr.CreateVoucher(ctx, pch, paych.SignedVoucher{Amount: amt, Lane: lane}) } func (a *PaychAPI) PaychVoucherList(ctx context.Context, pch address.Address) ([]*paych.SignedVoucher, error) { diff --git a/paychmgr/accessorcache.go b/paychmgr/accessorcache.go index 43223200d..176fbdd11 100644 --- a/paychmgr/accessorcache.go +++ b/paychmgr/accessorcache.go @@ -60,7 +60,7 @@ func (pm *Manager) accessorCacheKey(from address.Address, to address.Address) st // access a channel use the same lock (the lock on the accessor) func (pm *Manager) addAccessorToCache(from address.Address, to address.Address) *channelAccessor { key := pm.accessorCacheKey(from, to) - ca := newChannelAccessor(pm) + ca := newChannelAccessor(pm, from, to) // TODO: Use LRU pm.channels[key] = ca return ca diff --git a/paychmgr/manager.go b/paychmgr/manager.go index b1755a7d3..093afc0e9 100644 --- a/paychmgr/manager.go +++ b/paychmgr/manager.go @@ -4,6 +4,8 @@ import ( "context" "sync" + "github.com/filecoin-project/specs-actors/actors/crypto" + "github.com/filecoin-project/lotus/node/modules/helpers" "github.com/ipfs/go-datastore" @@ -49,6 +51,7 @@ type paychAPI interface { StateWaitMsg(ctx context.Context, msg cid.Cid, confidence uint64) (*api.MsgLookup, error) MpoolPushMessage(ctx context.Context, msg *types.Message, maxFee *api.MessageSendSpec) (*types.SignedMessage, error) WalletHas(ctx context.Context, addr address.Address) (bool, error) + WalletSign(ctx context.Context, k address.Address, msg []byte) (*crypto.Signature, error) } // managerAPI defines all methods needed by the manager @@ -138,6 +141,15 @@ func (pm *Manager) GetPaych(ctx context.Context, from, to address.Address, amt t return chanAccessor.getPaych(ctx, from, to, 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 // given message CID arrives. // The returned channel address can safely be used against the Manager methods. @@ -179,6 +191,15 @@ func (pm *Manager) GetChannelInfo(addr address.Address) (*ChannelInfo, error) { return ca.getChannelInfo(addr) } +func (pm *Manager) CreateVoucher(ctx context.Context, ch address.Address, voucher paych.SignedVoucher) (*api.VoucherCreateResult, error) { + ca, err := pm.accessorByAddress(ch) + if err != nil { + return nil, err + } + + return ca.createVoucher(ctx, ch, voucher) +} + // CheckVoucherValid checks if the given voucher is valid (is or could become spendable at some point). // If the channel is not in the store, fetches the channel from state (and checks that // the channel To address is owned by the wallet). @@ -309,14 +330,6 @@ func (pm *Manager) ListVouchers(ctx context.Context, ch address.Address) ([]*Vou return ca.listVouchers(ctx, ch) } -func (pm *Manager) NextNonceForLane(ctx context.Context, ch address.Address, lane uint64) (uint64, error) { - ca, err := pm.accessorByAddress(ch) - if err != nil { - return 0, err - } - return ca.nextNonceForLane(ctx, ch, lane) -} - func (pm *Manager) Settle(ctx context.Context, addr address.Address) (cid.Cid, error) { ca, err := pm.accessorByAddress(addr) if err != nil { diff --git a/paychmgr/mock_test.go b/paychmgr/mock_test.go index 54519bae2..d2aa047ee 100644 --- a/paychmgr/mock_test.go +++ b/paychmgr/mock_test.go @@ -5,6 +5,10 @@ import ( "fmt" "sync" + "github.com/filecoin-project/lotus/lib/sigs" + + "github.com/filecoin-project/specs-actors/actors/crypto" + cbornode "github.com/ipfs/go-ipld-cbor" "github.com/filecoin-project/specs-actors/actors/util/adt" @@ -132,6 +136,7 @@ type mockPaychAPI struct { waitingCalls map[cid.Cid]*waitingCall waitingResponses map[cid.Cid]*waitingResponse wallet map[address.Address]struct{} + signingKey []byte } func newMockPaychAPI() *mockPaychAPI { @@ -240,3 +245,17 @@ func (pchapi *mockPaychAPI) addWalletAddress(addr address.Address) { pchapi.wallet[addr] = struct{}{} } + +func (pchapi *mockPaychAPI) WalletSign(ctx context.Context, k address.Address, msg []byte) (*crypto.Signature, error) { + pchapi.lk.Lock() + defer pchapi.lk.Unlock() + + return sigs.Sign(crypto.SigTypeSecp256k1, pchapi.signingKey, msg) +} + +func (pchapi *mockPaychAPI) addSigningKey(key []byte) { + pchapi.lk.Lock() + defer pchapi.lk.Unlock() + + pchapi.signingKey = key +} diff --git a/paychmgr/paych.go b/paychmgr/paych.go index 0f12adb11..be43aaf9b 100644 --- a/paychmgr/paych.go +++ b/paychmgr/paych.go @@ -5,6 +5,8 @@ import ( "context" "fmt" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/specs-actors/actors/util/adt" "github.com/ipfs/go-cid" @@ -21,11 +23,36 @@ import ( xerrors "golang.org/x/xerrors" ) +// insufficientFundsErr indicates that there are not enough funds in the +// channel to create a voucher +type insufficientFundsErr interface { + Shortfall() types.BigInt +} + +type ErrInsufficientFunds struct { + shortfall types.BigInt +} + +func newErrInsufficientFunds(shortfall types.BigInt) *ErrInsufficientFunds { + return &ErrInsufficientFunds{shortfall: shortfall} +} + +func (e *ErrInsufficientFunds) Error() string { + return fmt.Sprintf("not enough funds in channel to cover voucher - shortfall: %d", e.shortfall) +} + +func (e *ErrInsufficientFunds) Shortfall() types.BigInt { + return e.shortfall +} + // channelAccessor is used to simplify locking when accessing a channel type channelAccessor struct { - // waitCtx is used by processes that wait for things to be confirmed - // on chain - waitCtx context.Context + from address.Address + to address.Address + + // chctx is used by background processes (eg when waiting for things to be + // confirmed on chain) + chctx context.Context sa *stateAccessor api managerAPI store *Store @@ -34,14 +61,16 @@ type channelAccessor struct { msgListeners msgListeners } -func newChannelAccessor(pm *Manager) *channelAccessor { +func newChannelAccessor(pm *Manager, from address.Address, to address.Address) *channelAccessor { return &channelAccessor{ - lk: &channelLock{globalLock: &pm.lk}, + from: from, + to: to, + chctx: pm.ctx, sa: pm.sa, api: pm.pchapi, store: pm.store, + lk: &channelLock{globalLock: &pm.lk}, msgListeners: newMsgListeners(), - waitCtx: pm.ctx, } } @@ -52,6 +81,69 @@ func (ca *channelAccessor) getChannelInfo(addr address.Address) (*ChannelInfo, e return ca.store.ByAddress(addr) } +// createVoucher creates a voucher with the given specification, setting its +// nonce, signing the voucher and storing it in the local datastore. +// If there are not enough funds in the channel to create the voucher, returns +// the shortfall in funds. +func (ca *channelAccessor) createVoucher(ctx context.Context, ch address.Address, voucher paych.SignedVoucher) (*api.VoucherCreateResult, error) { + ca.lk.Lock() + defer ca.lk.Unlock() + + // Find the channel for the voucher + ci, err := ca.store.ByAddress(ch) + if err != nil { + return nil, xerrors.Errorf("failed to get channel info by address: %w", err) + } + + // Set the voucher channel + sv := &voucher + sv.ChannelAddr = ch + + // Get the next nonce on the given lane + sv.Nonce = ca.nextNonceForLane(ci, voucher.Lane) + + // Sign the voucher + vb, err := sv.SigningBytes() + if err != nil { + return nil, xerrors.Errorf("failed to get voucher signing bytes: %w", err) + } + + sig, err := ca.api.WalletSign(ctx, ci.Control, vb) + if err != nil { + return nil, xerrors.Errorf("failed to sign voucher: %w", err) + } + sv.Signature = sig + + // Store the voucher + if _, err := ca.addVoucherUnlocked(ctx, ch, sv, nil, types.NewInt(0)); err != nil { + // If there are not enough funds in the channel to cover the voucher, + // return a voucher create result with the shortfall + var ife insufficientFundsErr + if xerrors.As(err, &ife) { + return &api.VoucherCreateResult{ + Shortfall: ife.Shortfall(), + }, nil + } + + return nil, xerrors.Errorf("failed to persist voucher: %w", err) + } + + return &api.VoucherCreateResult{Voucher: sv, Shortfall: types.NewInt(0)}, nil +} + +func (ca *channelAccessor) nextNonceForLane(ci *ChannelInfo, lane uint64) uint64 { + var maxnonce uint64 + for _, v := range ci.Vouchers { + if v.Voucher.Lane == lane { + if v.Voucher.Nonce > maxnonce { + maxnonce = v.Voucher.Nonce + } + } + } + + return maxnonce + 1 +} + func (ca *channelAccessor) checkVoucherValid(ctx context.Context, ch address.Address, sv *paych.SignedVoucher) (map[uint64]*paych.LaneState, error) { ca.lk.Lock() defer ca.lk.Unlock() @@ -133,7 +225,7 @@ func (ca *channelAccessor) checkVoucherValidUnlocked(ctx context.Context, ch add // Must not exceed actor balance newTotal := types.BigAdd(totalRedeemed, pchState.ToSend) if act.Balance.LessThan(newTotal) { - return nil, fmt.Errorf("not enough funds in channel to cover voucher") + return nil, newErrInsufficientFunds(types.BigSub(newTotal, act.Balance)) } if len(sv.Merges) != 0 { @@ -221,6 +313,10 @@ func (ca *channelAccessor) addVoucher(ctx context.Context, ch address.Address, s ca.lk.Lock() defer ca.lk.Unlock() + return ca.addVoucherUnlocked(ctx, ch, sv, proof, minDelta) +} + +func (ca *channelAccessor) addVoucherUnlocked(ctx context.Context, ch address.Address, sv *paych.SignedVoucher, proof []byte, minDelta types.BigInt) (types.BigInt, error) { ci, err := ca.store.ByAddress(ch) if err != nil { return types.BigInt{}, err @@ -382,28 +478,6 @@ func (ca *channelAccessor) listVouchers(ctx context.Context, ch address.Address) return ca.store.VouchersForPaych(ch) } -func (ca *channelAccessor) nextNonceForLane(ctx context.Context, ch address.Address, lane uint64) (uint64, error) { - ca.lk.Lock() - defer ca.lk.Unlock() - - // TODO: should this take into account lane state? - vouchers, err := ca.store.VouchersForPaych(ch) - if err != nil { - return 0, err - } - - var maxnonce uint64 - for _, v := range vouchers { - if v.Voucher.Lane == lane { - if v.Voucher.Nonce > maxnonce { - maxnonce = v.Voucher.Nonce - } - } - } - - return maxnonce + 1, nil -} - // laneState gets the LaneStates from chain, then applies all vouchers in // the data store over the chain state func (ca *channelAccessor) laneState(ctx context.Context, state *paych.State, ch address.Address) (map[uint64]*paych.LaneState, error) { @@ -479,7 +553,7 @@ func (ca *channelAccessor) totalRedeemedWithVoucher(laneStates map[uint64]*paych lane, ok := laneStates[sv.Lane] if ok { // If the voucher is for an existing lane, and the voucher nonce - // and is higher than the lane nonce + // is higher than the lane nonce if sv.Nonce > lane.Nonce { // Add the delta between the redeemed amount and the voucher // amount to the total diff --git a/paychmgr/paych_test.go b/paychmgr/paych_test.go index 4439fb2fb..e1ae487e1 100644 --- a/paychmgr/paych_test.go +++ b/paychmgr/paych_test.go @@ -381,11 +381,76 @@ func TestCheckVoucherValidCountingAllLanes(t *testing.T) { require.NoError(t, err) } +func TestCreateVoucher(t *testing.T) { + ctx := context.Background() + + // Set up a manager with a single payment channel + s := testSetupMgrWithChannel(ctx, t) + + // Create a voucher in lane 1 + voucherLane1Amt := big.NewInt(5) + voucher := paych.SignedVoucher{ + Lane: 1, + Amount: voucherLane1Amt, + } + res, err := s.mgr.CreateVoucher(ctx, s.ch, voucher) + require.NoError(t, err) + require.NotNil(t, res.Voucher) + require.Equal(t, s.ch, res.Voucher.ChannelAddr) + require.Equal(t, voucherLane1Amt, res.Voucher.Amount) + require.EqualValues(t, 0, res.Shortfall.Int64()) + + nonce := res.Voucher.Nonce + + // Create a voucher in lane 1 again, with a higher amount + voucherLane1Amt = big.NewInt(8) + voucher = paych.SignedVoucher{ + Lane: 1, + Amount: voucherLane1Amt, + } + res, err = s.mgr.CreateVoucher(ctx, s.ch, voucher) + require.NoError(t, err) + require.NotNil(t, res.Voucher) + require.Equal(t, s.ch, res.Voucher.ChannelAddr) + require.Equal(t, voucherLane1Amt, res.Voucher.Amount) + require.EqualValues(t, 0, res.Shortfall.Int64()) + require.Equal(t, nonce+1, res.Voucher.Nonce) + + // Create a voucher in lane 2 that covers all the remaining funds + // in the channel + voucherLane2Amt := big.Sub(s.amt, voucherLane1Amt) + voucher = paych.SignedVoucher{ + Lane: 2, + Amount: voucherLane2Amt, + } + res, err = s.mgr.CreateVoucher(ctx, s.ch, voucher) + require.NoError(t, err) + require.NotNil(t, res.Voucher) + require.Equal(t, s.ch, res.Voucher.ChannelAddr) + require.Equal(t, voucherLane2Amt, res.Voucher.Amount) + require.EqualValues(t, 0, res.Shortfall.Int64()) + + // Create a voucher in lane 2 that exceeds the remaining funds in the + // channel + voucherLane2Amt = big.Add(voucherLane2Amt, big.NewInt(1)) + voucher = paych.SignedVoucher{ + Lane: 2, + Amount: voucherLane2Amt, + } + res, err = s.mgr.CreateVoucher(ctx, s.ch, voucher) + require.NoError(t, err) + + // Expect a shortfall value equal to the amount required to add the voucher + // to the channel + require.Nil(t, res.Voucher) + require.EqualValues(t, 1, res.Shortfall.Int64()) +} + func TestAddVoucherDelta(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - mgr, _, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(ctx, t) voucherLane := uint64(1) @@ -393,23 +458,23 @@ func TestAddVoucherDelta(t *testing.T) { minDelta := big.NewInt(2) nonce := uint64(1) voucherAmount := big.NewInt(1) - sv := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err := mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) + sv := createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err := s.mgr.AddVoucherOutbound(ctx, s.ch, sv, nil, minDelta) require.Error(t, err) // Expect success when adding a voucher whose amount is equal to minDelta nonce++ voucherAmount = big.NewInt(2) - sv = createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - delta, err := mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) + sv = createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + delta, err := s.mgr.AddVoucherOutbound(ctx, s.ch, sv, nil, minDelta) require.NoError(t, err) require.EqualValues(t, delta.Int64(), 2) // Check that delta is correct when there's an existing voucher nonce++ voucherAmount = big.NewInt(5) - sv = createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - delta, err = mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) + sv = createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + delta, err = s.mgr.AddVoucherOutbound(ctx, s.ch, sv, nil, minDelta) require.NoError(t, err) require.EqualValues(t, delta.Int64(), 3) @@ -417,8 +482,8 @@ func TestAddVoucherDelta(t *testing.T) { nonce = uint64(1) voucherAmount = big.NewInt(6) voucherLane = uint64(2) - sv = createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - delta, err = mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) + sv = createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + delta, err = s.mgr.AddVoucherOutbound(ctx, s.ch, sv, nil, minDelta) require.NoError(t, err) require.EqualValues(t, delta.Int64(), 6) } @@ -427,7 +492,7 @@ func TestAddVoucherNextLane(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - mgr, _, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(ctx, t) minDelta := big.NewInt(0) voucherAmount := big.NewInt(2) @@ -435,40 +500,40 @@ func TestAddVoucherNextLane(t *testing.T) { // Add a voucher in lane 2 nonce := uint64(1) voucherLane := uint64(2) - sv := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err := mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) + sv := createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err := s.mgr.AddVoucherOutbound(ctx, s.ch, sv, nil, minDelta) require.NoError(t, err) - ci, err := mgr.GetChannelInfo(ch) + ci, err := s.mgr.GetChannelInfo(s.ch) require.NoError(t, err) require.EqualValues(t, ci.NextLane, 3) // Allocate a lane (should be lane 3) - lane, err := mgr.AllocateLane(ch) + lane, err := s.mgr.AllocateLane(s.ch) require.NoError(t, err) require.EqualValues(t, lane, 3) - ci, err = mgr.GetChannelInfo(ch) + ci, err = s.mgr.GetChannelInfo(s.ch) require.NoError(t, err) require.EqualValues(t, ci.NextLane, 4) // Add a voucher in lane 1 voucherLane = uint64(1) - sv = createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err = mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) + sv = createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err = s.mgr.AddVoucherOutbound(ctx, s.ch, sv, nil, minDelta) require.NoError(t, err) - ci, err = mgr.GetChannelInfo(ch) + ci, err = s.mgr.GetChannelInfo(s.ch) require.NoError(t, err) require.EqualValues(t, ci.NextLane, 4) // Add a voucher in lane 7 voucherLane = uint64(7) - sv = createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err = mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) + sv = createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err = s.mgr.AddVoucherOutbound(ctx, s.ch, sv, nil, minDelta) require.NoError(t, err) - ci, err = mgr.GetChannelInfo(ch) + ci, err = s.mgr.GetChannelInfo(s.ch) require.NoError(t, err) require.EqualValues(t, ci.NextLane, 8) } @@ -477,15 +542,15 @@ func TestAllocateLane(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - mgr, _, ch, _ := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(ctx, t) // First lane should be 0 - lane, err := mgr.AllocateLane(ch) + lane, err := s.mgr.AllocateLane(s.ch) require.NoError(t, err) require.EqualValues(t, lane, 0) // Next lane should be 1 - lane, err = mgr.AllocateLane(ch) + lane, err = s.mgr.AllocateLane(s.ch) require.NoError(t, err) require.EqualValues(t, lane, 1) } @@ -553,7 +618,7 @@ func TestAddVoucherProof(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - mgr, _, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(ctx, t) nonce := uint64(1) voucherAmount := big.NewInt(1) @@ -563,34 +628,34 @@ func TestAddVoucherProof(t *testing.T) { // Add a voucher with no proof var proof []byte - sv := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err := mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) + sv := createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err := s.mgr.AddVoucherOutbound(ctx, s.ch, sv, nil, minDelta) require.NoError(t, err) // Expect one voucher with no proof - ci, err := mgr.GetChannelInfo(ch) + ci, err := s.mgr.GetChannelInfo(s.ch) require.NoError(t, err) require.Len(t, ci.Vouchers, 1) require.Len(t, ci.Vouchers[0].Proof, 0) // Add same voucher with no proof voucherLane = uint64(1) - _, err = mgr.AddVoucherOutbound(ctx, ch, sv, proof, minDelta) + _, err = s.mgr.AddVoucherOutbound(ctx, s.ch, sv, proof, minDelta) require.NoError(t, err) // Expect one voucher with no proof - ci, err = mgr.GetChannelInfo(ch) + ci, err = s.mgr.GetChannelInfo(s.ch) require.NoError(t, err) require.Len(t, ci.Vouchers, 1) require.Len(t, ci.Vouchers[0].Proof, 0) // Add same voucher with proof proof = []byte{1} - _, err = mgr.AddVoucherOutbound(ctx, ch, sv, proof, minDelta) + _, err = s.mgr.AddVoucherOutbound(ctx, s.ch, sv, proof, minDelta) require.NoError(t, err) // Should add proof to existing voucher - ci, err = mgr.GetChannelInfo(ch) + ci, err = s.mgr.GetChannelInfo(s.ch) require.NoError(t, err) require.Len(t, ci.Vouchers, 1) require.Len(t, ci.Vouchers[0].Proof, 1) @@ -663,47 +728,47 @@ func TestBestSpendable(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - mgr, mock, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(ctx, t) // Add vouchers to lane 1 with amounts: [1, 2, 3] voucherLane := uint64(1) minDelta := big.NewInt(0) nonce := uint64(1) voucherAmount := big.NewInt(1) - svL1V1 := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err := mgr.AddVoucherInbound(ctx, ch, svL1V1, nil, minDelta) + svL1V1 := createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err := s.mgr.AddVoucherInbound(ctx, s.ch, svL1V1, nil, minDelta) require.NoError(t, err) nonce++ voucherAmount = big.NewInt(2) - svL1V2 := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err = mgr.AddVoucherInbound(ctx, ch, svL1V2, nil, minDelta) + svL1V2 := createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err = s.mgr.AddVoucherInbound(ctx, s.ch, svL1V2, nil, minDelta) require.NoError(t, err) nonce++ voucherAmount = big.NewInt(3) - svL1V3 := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err = mgr.AddVoucherInbound(ctx, ch, svL1V3, nil, minDelta) + svL1V3 := createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err = s.mgr.AddVoucherInbound(ctx, s.ch, svL1V3, nil, minDelta) require.NoError(t, err) // Add voucher to lane 2 with amounts: [2] voucherLane = uint64(2) nonce = uint64(1) voucherAmount = big.NewInt(2) - svL2V1 := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err = mgr.AddVoucherInbound(ctx, ch, svL2V1, nil, minDelta) + svL2V1 := createTestVoucher(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err = s.mgr.AddVoucherInbound(ctx, s.ch, svL2V1, nil, minDelta) require.NoError(t, err) // Return success exit code from calls to check if voucher is spendable - bsapi := newMockBestSpendableAPI(mgr) - mock.setCallResponse(&api.InvocResult{ + bsapi := newMockBestSpendableAPI(s.mgr) + s.mock.setCallResponse(&api.InvocResult{ MsgRct: &types.MessageReceipt{ ExitCode: 0, }, }) // Verify best spendable vouchers on each lane - vouchers, err := BestSpendableByLane(ctx, bsapi, ch) + vouchers, err := BestSpendableByLane(ctx, bsapi, s.ch) require.NoError(t, err) require.Len(t, vouchers, 2) @@ -716,21 +781,21 @@ func TestBestSpendable(t *testing.T) { require.EqualValues(t, 2, vchr.Amount.Int64()) // Submit voucher from lane 2 - _, err = mgr.SubmitVoucher(ctx, ch, svL2V1, nil, nil) + _, err = s.mgr.SubmitVoucher(ctx, s.ch, svL2V1, nil, nil) require.NoError(t, err) // Best spendable voucher should no longer include lane 2 // (because voucher has not been submitted) - vouchers, err = BestSpendableByLane(ctx, bsapi, ch) + vouchers, err = BestSpendableByLane(ctx, bsapi, s.ch) require.NoError(t, err) require.Len(t, vouchers, 1) // Submit first voucher from lane 1 - _, err = mgr.SubmitVoucher(ctx, ch, svL1V1, nil, nil) + _, err = s.mgr.SubmitVoucher(ctx, s.ch, svL1V1, nil, nil) require.NoError(t, err) // Best spendable voucher for lane 1 should still be highest value voucher - vouchers, err = BestSpendableByLane(ctx, bsapi, ch) + vouchers, err = BestSpendableByLane(ctx, bsapi, s.ch) require.NoError(t, err) require.Len(t, vouchers, 1) @@ -743,18 +808,18 @@ func TestCheckSpendable(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - mgr, mock, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(ctx, t) // Create voucher with Extra voucherLane := uint64(1) nonce := uint64(1) voucherAmount := big.NewInt(1) - voucher := createTestVoucherWithExtra(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) + voucher := createTestVoucherWithExtra(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) // Add voucher with proof minDelta := big.NewInt(0) proof := []byte("proof") - _, err := mgr.AddVoucherInbound(ctx, ch, voucher, proof, minDelta) + _, err := s.mgr.AddVoucherInbound(ctx, s.ch, voucher, proof, minDelta) require.NoError(t, err) // Return success exit code from VM call, which indicates that voucher is @@ -764,17 +829,17 @@ func TestCheckSpendable(t *testing.T) { ExitCode: 0, }, } - mock.setCallResponse(successResponse) + s.mock.setCallResponse(successResponse) // Check that spendable is true secret := []byte("secret") otherProof := []byte("other proof") - spendable, err := mgr.CheckVoucherSpendable(ctx, ch, voucher, secret, otherProof) + spendable, err := s.mgr.CheckVoucherSpendable(ctx, s.ch, voucher, secret, otherProof) require.NoError(t, err) require.True(t, spendable) // Check that the secret and proof were passed through correctly - lastCall := mock.getLastCall() + lastCall := s.mock.getLastCall() var p paych.UpdateChannelStateParams err = p.UnmarshalCBOR(bytes.NewReader(lastCall.Params)) require.NoError(t, err) @@ -784,11 +849,11 @@ func TestCheckSpendable(t *testing.T) { // Check that if no proof is supplied, the proof supplied to add voucher // above is used secret2 := []byte("secret2") - spendable, err = mgr.CheckVoucherSpendable(ctx, ch, voucher, secret2, nil) + spendable, err = s.mgr.CheckVoucherSpendable(ctx, s.ch, voucher, secret2, nil) require.NoError(t, err) require.True(t, spendable) - lastCall = mock.getLastCall() + lastCall = s.mock.getLastCall() var p2 paych.UpdateChannelStateParams err = p2.UnmarshalCBOR(bytes.NewReader(lastCall.Params)) require.NoError(t, err) @@ -796,26 +861,26 @@ func TestCheckSpendable(t *testing.T) { require.Equal(t, secret2, p2.Secret) // Check that if VM call returns non-success exit code, spendable is false - mock.setCallResponse(&api.InvocResult{ + s.mock.setCallResponse(&api.InvocResult{ MsgRct: &types.MessageReceipt{ ExitCode: 1, }, }) - spendable, err = mgr.CheckVoucherSpendable(ctx, ch, voucher, secret, nil) + spendable, err = s.mgr.CheckVoucherSpendable(ctx, s.ch, voucher, secret, nil) require.NoError(t, err) require.False(t, spendable) // Return success exit code (indicating voucher is spendable) - mock.setCallResponse(successResponse) - spendable, err = mgr.CheckVoucherSpendable(ctx, ch, voucher, secret, nil) + s.mock.setCallResponse(successResponse) + spendable, err = s.mgr.CheckVoucherSpendable(ctx, s.ch, voucher, secret, nil) require.NoError(t, err) require.True(t, spendable) // Check that voucher is no longer spendable once it has been submitted - _, err = mgr.SubmitVoucher(ctx, ch, voucher, nil, nil) + _, err = s.mgr.SubmitVoucher(ctx, s.ch, voucher, nil, nil) require.NoError(t, err) - spendable, err = mgr.CheckVoucherSpendable(ctx, ch, voucher, secret, nil) + spendable, err = s.mgr.CheckVoucherSpendable(ctx, s.ch, voucher, secret, nil) require.NoError(t, err) require.False(t, spendable) } @@ -824,28 +889,28 @@ func TestSubmitVoucher(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - mgr, mock, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(ctx, t) // Create voucher with Extra voucherLane := uint64(1) nonce := uint64(1) voucherAmount := big.NewInt(1) - voucher := createTestVoucherWithExtra(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) + voucher := createTestVoucherWithExtra(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) // Add voucher with proof minDelta := big.NewInt(0) addVoucherProof := []byte("proof") - _, err := mgr.AddVoucherInbound(ctx, ch, voucher, addVoucherProof, minDelta) + _, err := s.mgr.AddVoucherInbound(ctx, s.ch, voucher, addVoucherProof, minDelta) require.NoError(t, err) // Submit voucher secret := []byte("secret") submitProof := []byte("submit proof") - submitCid, err := mgr.SubmitVoucher(ctx, ch, voucher, secret, submitProof) + submitCid, err := s.mgr.SubmitVoucher(ctx, s.ch, voucher, secret, submitProof) require.NoError(t, err) // Check that the secret and proof were passed through correctly - msg := mock.pushedMessages(submitCid) + msg := s.mock.pushedMessages(submitCid) var p paych.UpdateChannelStateParams err = p.UnmarshalCBOR(bytes.NewReader(msg.Message.Params)) require.NoError(t, err) @@ -858,14 +923,14 @@ func TestSubmitVoucher(t *testing.T) { voucherAmount = big.NewInt(2) addVoucherProof2 := []byte("proof2") secret2 := []byte("secret2") - voucher = createTestVoucherWithExtra(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - _, err = mgr.AddVoucherInbound(ctx, ch, voucher, addVoucherProof2, minDelta) + voucher = createTestVoucherWithExtra(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + _, err = s.mgr.AddVoucherInbound(ctx, s.ch, voucher, addVoucherProof2, minDelta) require.NoError(t, err) - submitCid, err = mgr.SubmitVoucher(ctx, ch, voucher, secret2, nil) + submitCid, err = s.mgr.SubmitVoucher(ctx, s.ch, voucher, secret2, nil) require.NoError(t, err) - msg = mock.pushedMessages(submitCid) + msg = s.mock.pushedMessages(submitCid) var p2 paych.UpdateChannelStateParams err = p2.UnmarshalCBOR(bytes.NewReader(msg.Message.Params)) require.NoError(t, err) @@ -877,11 +942,11 @@ func TestSubmitVoucher(t *testing.T) { voucherAmount = big.NewInt(3) secret3 := []byte("secret2") proof3 := []byte("proof3") - voucher = createTestVoucherWithExtra(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate) - submitCid, err = mgr.SubmitVoucher(ctx, ch, voucher, secret3, proof3) + voucher = createTestVoucherWithExtra(t, s.ch, voucherLane, nonce, voucherAmount, s.fromKeyPrivate) + submitCid, err = s.mgr.SubmitVoucher(ctx, s.ch, voucher, secret3, proof3) require.NoError(t, err) - msg = mock.pushedMessages(submitCid) + msg = s.mock.pushedMessages(submitCid) var p3 paych.UpdateChannelStateParams err = p3.UnmarshalCBOR(bytes.NewReader(msg.Message.Params)) require.NoError(t, err) @@ -889,7 +954,7 @@ func TestSubmitVoucher(t *testing.T) { require.Equal(t, secret3, p3.Secret) // Verify that vouchers are marked as submitted - vis, err := mgr.ListVouchers(ctx, ch) + vis, err := s.mgr.ListVouchers(ctx, s.ch) require.NoError(t, err) require.Len(t, vis, 3) @@ -898,55 +963,20 @@ func TestSubmitVoucher(t *testing.T) { } // Attempting to submit the same voucher again should fail - _, err = mgr.SubmitVoucher(ctx, ch, voucher, secret2, nil) + _, err = s.mgr.SubmitVoucher(ctx, s.ch, voucher, secret2, nil) require.Error(t, err) } -func TestNextNonceForLane(t *testing.T) { - ctx := context.Background() - - // Set up a manager with a single payment channel - mgr, _, ch, key := testSetupMgrWithChannel(ctx, t) - - // Expect next nonce for non-existent lane to be 1 - next, err := mgr.NextNonceForLane(ctx, ch, 1) - require.NoError(t, err) - require.EqualValues(t, next, 1) - - voucherAmount := big.NewInt(1) - minDelta := big.NewInt(0) - voucherAmount = big.NewInt(2) - - // Add vouchers such that we have - // lane 1: nonce 2 - // lane 1: nonce 4 - voucherLane := uint64(1) - for _, nonce := range []uint64{2, 4} { - voucherAmount = big.Add(voucherAmount, big.NewInt(1)) - sv := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, key) - _, err := mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) - require.NoError(t, err) - } - - // lane 2: nonce 7 - voucherLane = uint64(2) - nonce := uint64(7) - sv := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, key) - _, err = mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) - require.NoError(t, err) - - // Expect next nonce for lane 1 to be 5 - next, err = mgr.NextNonceForLane(ctx, ch, 1) - require.NoError(t, err) - require.EqualValues(t, next, 5) - - // Expect next nonce for lane 2 to be 8 - next, err = mgr.NextNonceForLane(ctx, ch, 2) - require.NoError(t, err) - require.EqualValues(t, next, 8) +type testScaffold struct { + mgr *Manager + mock *mockManagerAPI + ch address.Address + amt big.Int + fromAcct address.Address + fromKeyPrivate []byte } -func testSetupMgrWithChannel(ctx context.Context, t *testing.T) (*Manager, *mockManagerAPI, address.Address, []byte) { +func testSetupMgrWithChannel(ctx context.Context, t *testing.T) *testScaffold { fromKeyPrivate, fromKeyPublic := testGenerateKeyPair(t) ch := tutils.NewIDAddr(t, 100) @@ -962,11 +992,12 @@ func testSetupMgrWithChannel(ctx context.Context, t *testing.T) (*Manager, *mock mock.setAccountState(toAcct, account.State{Address: to}) // Create channel in state + balance := big.NewInt(20) act := &types.Actor{ Code: builtin.AccountActorCodeID, Head: cid.Cid{}, Nonce: 0, - Balance: big.NewInt(20), + Balance: balance, } mock.setPaychState(ch, act, paych.State{ From: fromAcct, @@ -991,7 +1022,17 @@ func testSetupMgrWithChannel(ctx context.Context, t *testing.T) (*Manager, *mock err = mgr.store.putChannelInfo(ci) require.NoError(t, err) - return mgr, mock, ch, fromKeyPrivate + // Add the from signing key to the wallet + mock.addSigningKey(fromKeyPrivate) + + return &testScaffold{ + mgr: mgr, + mock: mock, + ch: ch, + amt: balance, + fromAcct: fromAcct, + fromKeyPrivate: fromKeyPrivate, + } } func testGenerateKeyPair(t *testing.T) ([]byte, []byte) { diff --git a/paychmgr/paychget_test.go b/paychmgr/paychget_test.go index 64ef6f9d7..8eff08bdd 100644 --- a/paychmgr/paychget_test.go +++ b/paychmgr/paychget_test.go @@ -6,6 +6,12 @@ import ( "testing" "time" + "github.com/filecoin-project/specs-actors/actors/builtin/account" + "github.com/filecoin-project/specs-actors/actors/util/adt" + + "github.com/filecoin-project/specs-actors/actors/abi" + "github.com/filecoin-project/specs-actors/actors/builtin/paych" + cborrpc "github.com/filecoin-project/go-cbor-util" init_ "github.com/filecoin-project/specs-actors/actors/builtin/init" @@ -650,8 +656,6 @@ func TestPaychGetMergeAddFunds(t *testing.T) { require.NoError(t, err) // Queue up two add funds requests behind create channel - //var addFundsQueuedUp sync.WaitGroup - //addFundsQueuedUp.Add(2) var addFundsSent sync.WaitGroup addFundsSent.Add(2) @@ -662,7 +666,6 @@ func TestPaychGetMergeAddFunds(t *testing.T) { var addFundsMcid1 cid.Cid var addFundsMcid2 cid.Cid go func() { - //go addFundsQueuedUp.Done() defer addFundsSent.Done() // Request add funds - should block until create channel has completed @@ -671,7 +674,6 @@ func TestPaychGetMergeAddFunds(t *testing.T) { }() go func() { - //go addFundsQueuedUp.Done() defer addFundsSent.Done() // Request add funds again - should merge with waiting add funds request @@ -899,6 +901,168 @@ func TestPaychGetMergeAddFundsCtxCancelAll(t *testing.T) { require.Equal(t, createAmt, createMsg.Message.Value) } +// TestPaychAvailableFunds tests that PaychAvailableFunds returns the correct +// channel state +func TestPaychAvailableFunds(t *testing.T) { + ctx := context.Background() + store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) + + fromKeyPrivate, fromKeyPublic := testGenerateKeyPair(t) + ch := tutils.NewIDAddr(t, 100) + from := tutils.NewSECP256K1Addr(t, string(fromKeyPublic)) + to := tutils.NewIDAddr(t, 102) + fromAcct := tutils.NewActorAddr(t, "fromAct") + toAcct := tutils.NewActorAddr(t, "toAct") + + mock := newMockManagerAPI() + defer mock.close() + + mgr, err := newManager(store, mock) + require.NoError(t, err) + + // No channel created yet so available funds should be all zeroes + av, err := mgr.AvailableFunds(from, to) + require.NoError(t, err) + require.Nil(t, av.Channel) + require.Nil(t, av.PendingWaitSentinel) + require.EqualValues(t, 0, av.ConfirmedAmt.Int64()) + require.EqualValues(t, 0, av.PendingAmt.Int64()) + require.EqualValues(t, 0, av.QueuedAmt.Int64()) + require.EqualValues(t, 0, av.VoucherReedeemedAmt.Int64()) + + // Send create message for a channel with value 10 + createAmt := big.NewInt(10) + _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt) + require.NoError(t, err) + + // Available funds should reflect create channel message sent + av, err = mgr.AvailableFunds(from, to) + require.NoError(t, err) + require.Nil(t, av.Channel) + require.EqualValues(t, 0, av.ConfirmedAmt.Int64()) + require.EqualValues(t, createAmt, av.PendingAmt) + require.EqualValues(t, 0, av.QueuedAmt.Int64()) + require.EqualValues(t, 0, av.VoucherReedeemedAmt.Int64()) + // Should now have a pending wait sentinel + require.NotNil(t, av.PendingWaitSentinel) + + // Queue up an add funds request behind create channel + var addFundsSent sync.WaitGroup + addFundsSent.Add(1) + + addFundsAmt := big.NewInt(5) + var addFundsMcid cid.Cid + go func() { + defer addFundsSent.Done() + + // Request add funds - should block until create channel has completed + _, addFundsMcid, err = mgr.GetPaych(ctx, from, to, addFundsAmt) + require.NoError(t, err) + }() + + // Wait for add funds request to be queued up + waitForQueueSize(t, mgr, from, to, 1) + + // Available funds should now include queued funds + av, err = mgr.AvailableFunds(from, to) + require.NoError(t, err) + require.Nil(t, av.Channel) + require.NotNil(t, av.PendingWaitSentinel) + require.EqualValues(t, 0, av.ConfirmedAmt.Int64()) + // create amount is still pending + require.EqualValues(t, createAmt, av.PendingAmt) + // queued amount now includes add funds amount + require.EqualValues(t, addFundsAmt, av.QueuedAmt) + require.EqualValues(t, 0, av.VoucherReedeemedAmt.Int64()) + + // Create channel in state + arr, err := adt.MakeEmptyArray(mock.store).Root() + require.NoError(t, err) + mock.setAccountState(fromAcct, account.State{Address: from}) + mock.setAccountState(toAcct, account.State{Address: to}) + act := &types.Actor{ + Code: builtin.AccountActorCodeID, + Head: cid.Cid{}, + Nonce: 0, + Balance: createAmt, + } + mock.setPaychState(ch, act, paych.State{ + From: fromAcct, + To: toAcct, + ToSend: big.NewInt(0), + SettlingAt: abi.ChainEpoch(0), + MinSettleHeight: abi.ChainEpoch(0), + LaneStates: arr, + }) + + // Send create channel response + response := testChannelResponse(t, ch) + mock.receiveMsgResponse(createMsgCid, response) + + // Wait for create channel response + chres, err := mgr.GetPaychWaitReady(ctx, *av.PendingWaitSentinel) + require.NoError(t, err) + require.Equal(t, ch, chres) + + // Wait for add funds request to be sent + addFundsSent.Wait() + + // Available funds should now include the channel and also a wait sentinel + // for the add funds message + av, err = mgr.AvailableFunds(from, to) + require.NoError(t, err) + require.NotNil(t, av.Channel) + require.NotNil(t, av.PendingWaitSentinel) + // create amount is now confirmed + require.EqualValues(t, createAmt, av.ConfirmedAmt) + // add funds amount it now pending + require.EqualValues(t, addFundsAmt, av.PendingAmt) + require.EqualValues(t, 0, av.QueuedAmt.Int64()) + require.EqualValues(t, 0, av.VoucherReedeemedAmt.Int64()) + + // Send success add funds response + mock.receiveMsgResponse(addFundsMcid, types.MessageReceipt{ + ExitCode: 0, + Return: []byte{}, + }) + + // Wait for add funds response + _, err = mgr.GetPaychWaitReady(ctx, *av.PendingWaitSentinel) + require.NoError(t, err) + + // Available funds should no longer have a wait sentinel + av, err = mgr.AvailableFunds(from, to) + require.NoError(t, err) + require.NotNil(t, av.Channel) + require.Nil(t, av.PendingWaitSentinel) + // confirmed amount now includes create and add funds amounts + require.EqualValues(t, types.BigAdd(createAmt, addFundsAmt), av.ConfirmedAmt) + require.EqualValues(t, 0, av.PendingAmt.Int64()) + require.EqualValues(t, 0, av.QueuedAmt.Int64()) + require.EqualValues(t, 0, av.VoucherReedeemedAmt.Int64()) + + // Add some vouchers + voucherAmt1 := types.NewInt(3) + voucher := createTestVoucher(t, ch, 1, 1, voucherAmt1, fromKeyPrivate) + _, err = mgr.AddVoucherOutbound(ctx, ch, voucher, nil, types.NewInt(0)) + require.NoError(t, err) + + voucherAmt2 := types.NewInt(2) + voucher = createTestVoucher(t, ch, 2, 1, voucherAmt2, fromKeyPrivate) + _, err = mgr.AddVoucherOutbound(ctx, ch, voucher, nil, types.NewInt(0)) + require.NoError(t, err) + + av, err = mgr.AvailableFunds(from, to) + require.NoError(t, err) + require.NotNil(t, av.Channel) + require.Nil(t, av.PendingWaitSentinel) + require.EqualValues(t, types.BigAdd(createAmt, addFundsAmt), av.ConfirmedAmt) + require.EqualValues(t, 0, av.PendingAmt.Int64()) + require.EqualValues(t, 0, av.QueuedAmt.Int64()) + // voucher redeemed amount now includes vouchers + require.EqualValues(t, types.BigAdd(voucherAmt1, voucherAmt2), av.VoucherReedeemedAmt) +} + // waitForQueueSize waits for the funds request queue to be of the given size func waitForQueueSize(t *testing.T, mgr *Manager, from address.Address, to address.Address, size int) { ca, err := mgr.accessorByFromTo(from, to) diff --git a/paychmgr/simple.go b/paychmgr/simple.go index b2f3c4404..99fd23334 100644 --- a/paychmgr/simple.go +++ b/paychmgr/simple.go @@ -6,6 +6,8 @@ import ( "fmt" "sync" + "github.com/filecoin-project/lotus/api" + "golang.org/x/sync/errgroup" "github.com/filecoin-project/specs-actors/actors/abi/big" @@ -191,17 +193,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 +212,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,7 +223,7 @@ 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) @@ -231,7 +233,7 @@ func (ca *channelAccessor) processQueue() { 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 +241,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,10 +295,65 @@ 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 != nil && err != ErrChannelNotTracked { + return nil, err + } + + if channelInfo == nil { + // Channel does not exist + return &api.ChannelAvailableFunds{ + Channel: nil, + ConfirmedAmt: types.NewInt(0), + PendingAmt: types.NewInt(0), + PendingWaitSentinel: nil, + QueuedAmt: queuedAmt, + VoucherReedeemedAmt: types.NewInt(0), + }, nil + } + + // 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 @@ -397,7 +456,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 +539,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 +728,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() +} From 9a5542100a4aea3d08ce3471b6ad3955ef6c467c Mon Sep 17 00:00:00 2001 From: whyrusleeping Date: Wed, 2 Sep 2020 11:43:05 -0700 Subject: [PATCH 02/41] load fewer messages from disk when serving blocksync requests --- chain/blocksync/server.go | 39 ++++++++++++++++++++++++--------------- chain/store/store.go | 15 ++++++++++++--- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/chain/blocksync/server.go b/chain/blocksync/server.go index 001f2e640..e8533f3d2 100644 --- a/chain/blocksync/server.go +++ b/chain/blocksync/server.go @@ -221,37 +221,36 @@ func collectChainSegment( func gatherMessages(cs *store.ChainStore, ts *types.TipSet) ([]*types.Message, [][]uint64, []*types.SignedMessage, [][]uint64, error) { blsmsgmap := make(map[cid.Cid]uint64) secpkmsgmap := make(map[cid.Cid]uint64) - var secpkmsgs []*types.SignedMessage - var blsmsgs []*types.Message var secpkincl, blsincl [][]uint64 + var blscids, secpkcids []cid.Cid for _, block := range ts.Blocks() { - bmsgs, smsgs, err := cs.MessagesForBlock(block) + bc, sc, err := cs.ReadMsgMetaCids(block.Messages) if err != nil { return nil, nil, nil, nil, err } // FIXME: DRY. Use `chain.Message` interface. - bmi := make([]uint64, 0, len(bmsgs)) - for _, m := range bmsgs { - i, ok := blsmsgmap[m.Cid()] + bmi := make([]uint64, 0, len(bc)) + for _, m := range bc { + i, ok := blsmsgmap[m] if !ok { - i = uint64(len(blsmsgs)) - blsmsgs = append(blsmsgs, m) - blsmsgmap[m.Cid()] = i + i = uint64(len(blscids)) + blscids = append(blscids, m) + blsmsgmap[m] = i } bmi = append(bmi, i) } blsincl = append(blsincl, bmi) - smi := make([]uint64, 0, len(smsgs)) - for _, m := range smsgs { - i, ok := secpkmsgmap[m.Cid()] + smi := make([]uint64, 0, len(sc)) + for _, m := range secpkcids { + i, ok := secpkmsgmap[m] if !ok { - i = uint64(len(secpkmsgs)) - secpkmsgs = append(secpkmsgs, m) - secpkmsgmap[m.Cid()] = i + i = uint64(len(secpkcids)) + secpkcids = append(secpkcids, m) + secpkmsgmap[m] = i } smi = append(smi, i) @@ -259,5 +258,15 @@ func gatherMessages(cs *store.ChainStore, ts *types.TipSet) ([]*types.Message, [ secpkincl = append(secpkincl, smi) } + blsmsgs, err := cs.LoadMessagesFromCids(blscids) + if err != nil { + return nil, nil, nil, nil, err + } + + secpkmsgs, err := cs.LoadSignedMessagesFromCids(secpkcids) + if err != nil { + return nil, nil, nil, nil, err + } + return blsmsgs, blsincl, secpkmsgs, secpkincl, nil } diff --git a/chain/store/store.go b/chain/store/store.go index af78ff286..2ae7fab2c 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -49,6 +49,7 @@ var chainHeadKey = dstore.NewKey("head") var blockValidationCacheKeyPrefix = dstore.NewKey("blockValidation") var DefaultTipSetCacheSize = 8192 +var DefaultMsgMetaCacheSize = 2048 func init() { if s := os.Getenv("LOTUS_CHAIN_TIPSET_CACHE"); s != "" { @@ -58,6 +59,14 @@ func init() { } DefaultTipSetCacheSize = tscs } + + if s := os.Getenv("LOTUS_CHAIN_MSGMETA_CACHE"); s != "" { + mmcs, err := strconv.Atoi(s) + if err != nil { + log.Errorf("failed to parse 'LOTUS_CHAIN_MSGMETA_CACHE' env var: %s", err) + } + DefaultMsgMetaCacheSize = mmcs + } } // ReorgNotifee represents a callback that gets called upon reorgs. @@ -97,7 +106,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, @@ -834,7 +843,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) @@ -890,7 +899,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 } From 8ba0385b3aa007ea4125459042fa337f35251be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 2 Sep 2020 21:05:38 +0200 Subject: [PATCH 03/41] Revert "adx blst" --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 700d591a6..e10895bb5 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/filecoin-project/lotus go 1.14 -replace github.com/supranational/blst => github.com/filecoin-project/blst v0.1.2-adx +replace github.com/supranational/blst => github.com/supranational/blst v0.1.2-alpha.1 require ( contrib.go.opencensus.io/exporter/jaeger v0.1.0 diff --git a/go.sum b/go.sum index 54d85c990..18df889ae 100644 --- a/go.sum +++ b/go.sum @@ -215,8 +215,6 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.8.0 h1:5bzFgL+oy7JITMTxUPJ00n7VxmYd/PdMp5mHFX40/RY= github.com/fatih/color v1.8.0/go.mod h1:3l45GVGkyrnYNl9HoIjnp2NnNWvh6hLAqD8yTfGjnw8= github.com/fd/go-nat v1.0.0/go.mod h1:BTBu/CKvMmOMUPkKVef1pngt2WFH/lg7E6yQnulfp6E= -github.com/filecoin-project/blst v0.1.2-adx h1:qyirtiGFTN/C17y4xlCFAblgw2OXhW8+wtnLwV27/cM= -github.com/filecoin-project/blst v0.1.2-adx/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/filecoin-project/chain-validation v0.0.6-0.20200813000554-40c22fe26eef h1:MtQRSnJLsQOOlmsd/Ua5KWXimpxcaa715h6FUh/eJPY= github.com/filecoin-project/chain-validation v0.0.6-0.20200813000554-40c22fe26eef/go.mod h1:SMj5VK1pYgqC8FXVEtOBRTc+9AIrYu+C+K3tAXi2Rk8= github.com/filecoin-project/go-address v0.0.0-20200107215422-da8eea2842b5/go.mod h1:SAOwJoakQ8EPjwNIsiakIQKsoKdkcbx8U3IapgCg9R0= @@ -1373,6 +1371,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= From ca8f5861fea29999db496d5355d000b38f0c50e1 Mon Sep 17 00:00:00 2001 From: Lucas Molas Date: Wed, 2 Sep 2020 17:07:39 -0300 Subject: [PATCH 04/41] Update chain/blocksync/server.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Łukasz Magiera --- chain/blocksync/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/blocksync/server.go b/chain/blocksync/server.go index e8533f3d2..ffdf79ad0 100644 --- a/chain/blocksync/server.go +++ b/chain/blocksync/server.go @@ -245,7 +245,7 @@ func gatherMessages(cs *store.ChainStore, ts *types.TipSet) ([]*types.Message, [ blsincl = append(blsincl, bmi) smi := make([]uint64, 0, len(sc)) - for _, m := range secpkcids { + for _, m := range sc { i, ok := secpkmsgmap[m] if !ok { i = uint64(len(secpkcids)) From d2a10413a1ddae9082b0fc73153035f170f8a34d Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Wed, 2 Sep 2020 13:19:39 -0700 Subject: [PATCH 05/41] feat(markets): upgrade to v0.5.9 --- go.mod | 2 +- go.sum | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e10895bb5..81bc20c07 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,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 diff --git a/go.sum b/go.sum index 18df889ae..e1e6572a7 100644 --- a/go.sum +++ b/go.sum @@ -246,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= @@ -688,6 +690,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= From a59dadccc94bd7c4471456afc5dd89d4c65dbde5 Mon Sep 17 00:00:00 2001 From: whyrusleeping Date: Wed, 2 Sep 2020 15:25:53 -0700 Subject: [PATCH 06/41] add apis and command to inspect bandwidth usage --- api/api_common.go | 5 +++ api/apistruct/struct.go | 33 ++++++++++---- cli/net.go | 89 ++++++++++++++++++++++++++++++++++++++ go.mod | 3 ++ go.sum | 4 ++ node/builder.go | 2 + node/impl/common/common.go | 19 ++++++++ 7 files changed, 147 insertions(+), 8 deletions(-) diff --git a/api/api_common.go b/api/api_common.go index 69b2df17a..33658eb2f 100644 --- a/api/api_common.go +++ b/api/api_common.go @@ -5,8 +5,10 @@ import ( "fmt" "github.com/filecoin-project/go-jsonrpc/auth" + metrics "github.com/libp2p/go-libp2p-core/metrics" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" + protocol "github.com/libp2p/go-libp2p-protocol" "github.com/filecoin-project/lotus/build" ) @@ -28,6 +30,9 @@ type Common interface { NetFindPeer(context.Context, peer.ID) (peer.AddrInfo, error) NetPubsubScores(context.Context) ([]PubsubScore, error) NetAutoNatStatus(context.Context) (NatInfo, error) + NetBandwidthStats(ctx context.Context) (metrics.Stats, error) + NetBandwidthStatsByPeer(ctx context.Context) (map[string]metrics.Stats, error) + NetBandwidthStatsByProtocol(ctx context.Context) (map[protocol.ID]metrics.Stats, error) // MethodGroup: Common diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index ad8c5d40f..6e063a207 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -6,8 +6,10 @@ import ( "time" "github.com/ipfs/go-cid" + metrics "github.com/libp2p/go-libp2p-core/metrics" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" + protocol "github.com/libp2p/go-libp2p-protocol" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-fil-markets/piecestore" @@ -42,14 +44,17 @@ 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"` ID func(context.Context) (peer.ID, error) `perm:"read"` Version func(context.Context) (api.Version, error) `perm:"read"` @@ -371,6 +376,18 @@ 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) +} + // ID implements API.ID func (c *CommonStruct) ID(ctx context.Context) (peer.ID, error) { return c.Internal.ID(ctx) diff --git a/cli/net.go b/cli/net.go index 6dd11d045..7c5b094df 100644 --- a/cli/net.go +++ b/cli/net.go @@ -6,9 +6,12 @@ import ( "os" "sort" "strings" + "text/tabwriter" "github.com/libp2p/go-libp2p-core/peer" + protocol "github.com/libp2p/go-libp2p-protocol" + "github.com/dustin/go-humanize" "github.com/urfave/cli/v2" "github.com/filecoin-project/lotus/lib/addrutil" @@ -25,6 +28,7 @@ var netCmd = &cli.Command{ netFindPeer, netScores, NetReachability, + NetBandwidthCmd, }, } @@ -228,3 +232,88 @@ var NetReachability = &cli.Command{ return nil }, } + +var NetBandwidthCmd = &cli.Command{ + Name: "bandwidth", + Usage: "Print bandwidth usage information", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "by-peer", + Usage: "list bandwidth usage by peer", + }, + &cli.BoolFlag{ + Name: "by-protocol", + Usage: "list bandwidth usage by protocol", + }, + }, + Action: func(cctx *cli.Context) error { + api, closer, err := GetAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := ReqContext(cctx) + + bypeer := cctx.Bool("by-peer") + byproto := cctx.Bool("by-protocol") + + tw := tabwriter.NewWriter(os.Stdout, 4, 4, 2, ' ', 0) + + fmt.Fprintf(tw, "Segment\tTotalIn\tTotalOut\tRateIn\tRateOut\n") + + if bypeer { + bw, err := api.NetBandwidthStatsByPeer(ctx) + if err != nil { + return err + } + + var peers []string + for p := range bw { + peers = append(peers, p) + } + + sort.Slice(peers, func(i, j int) bool { + return peers[i] < peers[j] + }) + + for _, p := range peers { + s := bw[p] + fmt.Fprintf(tw, "%s\t%s\t%s\t%s/s\t%s/s\n", p, humanize.Bytes(uint64(s.TotalIn)), humanize.Bytes(uint64(s.TotalOut)), humanize.Bytes(uint64(s.RateIn)), humanize.Bytes(uint64(s.RateOut))) + } + } else if byproto { + bw, err := api.NetBandwidthStatsByProtocol(ctx) + if err != nil { + return err + } + + var protos []protocol.ID + for p := range bw { + protos = append(protos, p) + } + + sort.Slice(protos, func(i, j int) bool { + return protos[i] < protos[j] + }) + + for _, p := range protos { + s := bw[p] + if p == "" { + p = "" + } + fmt.Fprintf(tw, "%s\t%s\t%s\t%s/s\t%s/s\n", p, humanize.Bytes(uint64(s.TotalIn)), humanize.Bytes(uint64(s.TotalOut)), humanize.Bytes(uint64(s.RateIn)), humanize.Bytes(uint64(s.RateOut))) + } + } else { + + s, err := api.NetBandwidthStats(ctx) + if err != nil { + return err + } + + fmt.Fprintf(tw, "Total\t%s\t%s\t%s/s\t%s/s\n", humanize.Bytes(uint64(s.TotalIn)), humanize.Bytes(uint64(s.TotalOut)), humanize.Bytes(uint64(s.RateIn)), humanize.Bytes(uint64(s.RateOut))) + } + + return tw.Flush() + + }, +} diff --git a/go.mod b/go.mod index 700d591a6..bfe54e0ed 100644 --- a/go.mod +++ b/go.mod @@ -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 @@ -87,9 +88,11 @@ require ( github.com/libp2p/go-libp2p-core v0.6.1 github.com/libp2p/go-libp2p-discovery v0.5.0 github.com/libp2p/go-libp2p-kad-dht v0.8.3 + github.com/libp2p/go-libp2p-metrics v0.0.1 github.com/libp2p/go-libp2p-mplex v0.2.4 github.com/libp2p/go-libp2p-noise v0.1.1 github.com/libp2p/go-libp2p-peerstore v0.2.6 + github.com/libp2p/go-libp2p-protocol v0.1.0 github.com/libp2p/go-libp2p-pubsub v0.3.6-0.20200901174250-06a12f17b7de github.com/libp2p/go-libp2p-quic-transport v0.8.0 github.com/libp2p/go-libp2p-record v0.1.3 diff --git a/go.sum b/go.sum index 54d85c990..1893d32ed 100644 --- a/go.sum +++ b/go.sum @@ -827,6 +827,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= @@ -852,6 +853,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= @@ -874,6 +876,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= @@ -890,6 +893,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= diff --git a/node/builder.go b/node/builder.go index 86d2aff7a..5b6966cd4 100644 --- a/node/builder.go +++ b/node/builder.go @@ -86,6 +86,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 @@ -181,6 +182,7 @@ func libp2p() Option { Override(new(routing.Routing), lp2p.Routing), Override(NatPortMapKey, lp2p.NatPortMap), + Override(BandwidthReporterKey, lp2p.BandwidthCounter), Override(ConnectionManagerKey, lp2p.ConnectionManager(50, 200, 20*time.Second, nil)), Override(AutoNATSvcKey, lp2p.AutoNATService), diff --git a/node/impl/common/common.go b/node/impl/common/common.go index d149486b1..0a07bde07 100644 --- a/node/impl/common/common.go +++ b/node/impl/common/common.go @@ -9,8 +9,10 @@ import ( "github.com/gbrlsnchs/jwt/v3" "github.com/libp2p/go-libp2p-core/host" + metrics "github.com/libp2p/go-libp2p-core/metrics" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" + protocol "github.com/libp2p/go-libp2p-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,22 @@ func (a *CommonAPI) NetAutoNatStatus(ctx context.Context) (i api.NatInfo, err er }, 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 } From d196e4a8dfec48d7569653557e7733c2825aeccd Mon Sep 17 00:00:00 2001 From: whyrusleeping Date: Wed, 2 Sep 2020 15:45:57 -0700 Subject: [PATCH 07/41] docs, mod tidy --- api/api_common.go | 9 +++++++++ api/docgen/docgen.go | 18 ++++++++++++++++++ go.mod | 1 - 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/api/api_common.go b/api/api_common.go index 33658eb2f..973eed31b 100644 --- a/api/api_common.go +++ b/api/api_common.go @@ -30,8 +30,17 @@ type Common interface { NetFindPeer(context.Context, peer.ID) (peer.AddrInfo, error) NetPubsubScores(context.Context) ([]PubsubScore, error) NetAutoNatStatus(context.Context) (NatInfo, error) + + // NetBandwidthStats returns statistics about the nodes total bandwidth + // usage and current rate across all peers and protocols. NetBandwidthStats(ctx context.Context) (metrics.Stats, error) + + // NetBandwidthStatsByPeer returns statistics about the nodes bandwidth + // usage and current rate per peer NetBandwidthStatsByPeer(ctx context.Context) (map[string]metrics.Stats, error) + + // NetBandwidthStatsByProtocol returns statistics about the nodes bandwidth + // usage and current rate per protocol NetBandwidthStatsByProtocol(ctx context.Context) (map[protocol.ID]metrics.Stats, error) // MethodGroup: Common diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 5c74ef23b..4810fa39a 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -14,8 +14,10 @@ import ( "github.com/ipfs/go-cid" "github.com/ipfs/go-filestore" + metrics "github.com/libp2p/go-libp2p-core/metrics" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" + protocol "github.com/libp2p/go-libp2p-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": metrics.Stats{ + RateIn: 100, + RateOut: 50, + TotalIn: 174000, + TotalOut: 12500, + }, + }) + addExample(map[protocol.ID]metrics.Stats{ + "/fil/hello/1.0.0": metrics.Stats{ + RateIn: 100, + RateOut: 50, + TotalIn: 174000, + TotalOut: 12500, + }, + }) maddr, err := multiaddr.NewMultiaddr("/ip4/52.36.61.156/tcp/1347/p2p/12D3KooWFETiESTf1v4PGUvtnxMAcEFMzLZbJGg4tjWfGEimYior") if err != nil { diff --git a/go.mod b/go.mod index bfe54e0ed..5051dc058 100644 --- a/go.mod +++ b/go.mod @@ -88,7 +88,6 @@ require ( github.com/libp2p/go-libp2p-core v0.6.1 github.com/libp2p/go-libp2p-discovery v0.5.0 github.com/libp2p/go-libp2p-kad-dht v0.8.3 - github.com/libp2p/go-libp2p-metrics v0.0.1 github.com/libp2p/go-libp2p-mplex v0.2.4 github.com/libp2p/go-libp2p-noise v0.1.1 github.com/libp2p/go-libp2p-peerstore v0.2.6 From 0e39d62c9d58419a80cbb814131201320e0c3e9a Mon Sep 17 00:00:00 2001 From: whyrusleeping Date: Wed, 2 Sep 2020 16:31:41 -0700 Subject: [PATCH 08/41] Appease the CI overlords --- api/api_common.go | 2 +- api/apistruct/struct.go | 2 +- api/docgen/docgen.go | 2 +- cli/net.go | 2 +- documentation/en/api-methods.md | 58 +++++++++++++++++++++++++++++++++ node/impl/common/common.go | 2 +- 6 files changed, 63 insertions(+), 5 deletions(-) diff --git a/api/api_common.go b/api/api_common.go index 973eed31b..ec410bac7 100644 --- a/api/api_common.go +++ b/api/api_common.go @@ -8,7 +8,7 @@ import ( 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-protocol" + protocol "github.com/libp2p/go-libp2p-core/protocol" "github.com/filecoin-project/lotus/build" ) diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 6e063a207..ac7e1b4a3 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -9,7 +9,7 @@ import ( 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-protocol" + protocol "github.com/libp2p/go-libp2p-core/protocol" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-fil-markets/piecestore" diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 4810fa39a..f5414af90 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -17,7 +17,7 @@ import ( 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-protocol" + protocol "github.com/libp2p/go-libp2p-core/protocol" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/multiformats/go-multiaddr" diff --git a/cli/net.go b/cli/net.go index 7c5b094df..7730bdf91 100644 --- a/cli/net.go +++ b/cli/net.go @@ -9,7 +9,7 @@ import ( "text/tabwriter" "github.com/libp2p/go-libp2p-core/peer" - protocol "github.com/libp2p/go-libp2p-protocol" + protocol "github.com/libp2p/go-libp2p-core/protocol" "github.com/dustin/go-humanize" "github.com/urfave/cli/v2" diff --git a/documentation/en/api-methods.md b/documentation/en/api-methods.md index d60e87eb1..2127332e5 100644 --- a/documentation/en/api-methods.md +++ b/documentation/en/api-methods.md @@ -85,6 +85,9 @@ * [Net](#Net) * [NetAddrsListen](#NetAddrsListen) * [NetAutoNatStatus](#NetAutoNatStatus) + * [NetBandwidthStats](#NetBandwidthStats) + * [NetBandwidthStatsByPeer](#NetBandwidthStatsByPeer) + * [NetBandwidthStatsByProtocol](#NetBandwidthStatsByProtocol) * [NetConnect](#NetConnect) * [NetConnectedness](#NetConnectedness) * [NetDisconnect](#NetDisconnect) @@ -2059,6 +2062,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 diff --git a/node/impl/common/common.go b/node/impl/common/common.go index 0a07bde07..cd1812058 100644 --- a/node/impl/common/common.go +++ b/node/impl/common/common.go @@ -12,7 +12,7 @@ import ( 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-protocol" + 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" From f03f4775fc52e52029635bdb1525f6ba3c2fcc77 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Wed, 2 Sep 2020 19:42:19 -0400 Subject: [PATCH 09/41] Lotus version 0.5.8 --- CHANGELOG.md | 20 ++++++++++++++++++++ build/version.go | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 128bc0ec6..24017fc4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Lotus changelog +# 0.5.8 / 2020-09-02 + +This patch includes some bugfixes to the sector sealing process, and updates go-fil-markets. It also improves the performance of blocksync, adds a method to export chain state trees, and improves chainwatch. + +## Changes + +- Upgrade markets to v0.5.9 (https://github.com/filecoin-project/lotus/pull/3496) +- Improve blocksync to load fewer messages: (https://github.com/filecoin-project/lotus/pull/3494) +- Fix a panic in the ffi-wrapper's `ReadPiece` (https://github.com/filecoin-project/lotus/pull/3492/files) +- Fix a deadlock in the sealing scheduler (https://github.com/filecoin-project/lotus/pull/3489) +- Add test vectors for tipset tests (https://github.com/filecoin-project/lotus/pull/3485/files) +- Improve the advance-block debug command (https://github.com/filecoin-project/lotus/pull/3476) +- Add toggle for message processing to Lotus PCR (https://github.com/filecoin-project/lotus/pull/3470) +- Allow exporting recent chain state trees (https://github.com/filecoin-project/lotus/pull/3463) +- Remove height from chain rand (https://github.com/filecoin-project/lotus/pull/3458) +- Disable GC on chain badger datastore (https://github.com/filecoin-project/lotus/pull/3457) +- Account for `GasPremium` in `GasEstimateFeeCap` (https://github.com/filecoin-project/lotus/pull/3456) +- Update go-libp2p-pubsub to `master` (https://github.com/filecoin-project/lotus/pull/3455) +- Chainwatch improvements (https://github.com/filecoin-project/lotus/pull/3442) + # 0.5.7 / 2020-08-31 This patch release includes some bugfixes and enhancements to the sector lifecycle and message pool logic. diff --git a/build/version.go b/build/version.go index 5d29cd830..6062d2739 100644 --- a/build/version.go +++ b/build/version.go @@ -25,7 +25,7 @@ func buildType() string { } // BuildVersion is the local build version, set by build system -const BuildVersion = "0.5.7" +const BuildVersion = "0.5.8" func UserVersion() string { return BuildVersion + buildType() + CurrentCommit From da7fc8dbb305ec2c2bc725e9047dfaf5128a2d47 Mon Sep 17 00:00:00 2001 From: whyrusleeping Date: Wed, 2 Sep 2020 16:42:55 -0700 Subject: [PATCH 10/41] further appeasement --- api/docgen/docgen.go | 4 ++-- go.mod | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index f5414af90..c3b4962d5 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -136,7 +136,7 @@ func init() { }, }) addExample(map[string]metrics.Stats{ - "12D3KooWSXmXLJmBR1M7i9RW9GQPNUhZSzXKzxDHWtAgNuJAbyEJ": metrics.Stats{ + "12D3KooWSXmXLJmBR1M7i9RW9GQPNUhZSzXKzxDHWtAgNuJAbyEJ": { RateIn: 100, RateOut: 50, TotalIn: 174000, @@ -144,7 +144,7 @@ func init() { }, }) addExample(map[protocol.ID]metrics.Stats{ - "/fil/hello/1.0.0": metrics.Stats{ + "/fil/hello/1.0.0": { RateIn: 100, RateOut: 50, TotalIn: 174000, diff --git a/go.mod b/go.mod index 5051dc058..0ecdbacbf 100644 --- a/go.mod +++ b/go.mod @@ -91,7 +91,6 @@ require ( github.com/libp2p/go-libp2p-mplex v0.2.4 github.com/libp2p/go-libp2p-noise v0.1.1 github.com/libp2p/go-libp2p-peerstore v0.2.6 - github.com/libp2p/go-libp2p-protocol v0.1.0 github.com/libp2p/go-libp2p-pubsub v0.3.6-0.20200901174250-06a12f17b7de github.com/libp2p/go-libp2p-quic-transport v0.8.0 github.com/libp2p/go-libp2p-record v0.1.3 From 860a6a231d3ee897dab13dd78825400190e0483c Mon Sep 17 00:00:00 2001 From: jennijuju Date: Mon, 31 Aug 2020 01:43:58 -0400 Subject: [PATCH 11/41] 3388: Added target height to sync wait --- cli/sync.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cli/sync.go b/cli/sync.go index 57553a068..27957ac35 100644 --- a/cli/sync.go +++ b/cli/sync.go @@ -180,11 +180,13 @@ func SyncWait(ctx context.Context, napi api.FullNode) error { ss := state.ActiveSyncs[working] var target []cid.Cid + var theight abi.ChainEpoch if ss.Target != nil { target = ss.Target.Cids() + theight = ss.Target.Height() } - fmt.Printf("\r\x1b[2KWorker %d: Target: %s\tState: %s\tHeight: %d", working, target, chain.SyncStageString(ss.Stage), ss.Height) + fmt.Printf("\r\x1b[2KWorker %d: Target Height: %d\tTarget: %s\tState: %s\tHeight: %d", working, theight, target, chain.SyncStageString(ss.Stage), ss.Height) if time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs) { fmt.Println("\nDone!") From 3daa0b5e630e4573861e68eaa79014b904c373a0 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Thu, 3 Sep 2020 08:20:08 +0200 Subject: [PATCH 12/41] fix: paych - clarify behaviour of current available funds for non-existent channel --- paychmgr/simple.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/paychmgr/simple.go b/paychmgr/simple.go index 99fd23334..dc089d3fa 100644 --- a/paychmgr/simple.go +++ b/paychmgr/simple.go @@ -301,12 +301,10 @@ func (ca *channelAccessor) msgWaitComplete(mcid cid.Cid, err error) { func (ca *channelAccessor) currentAvailableFunds(queuedAmt types.BigInt) (*api.ChannelAvailableFunds, error) { channelInfo, err := ca.store.OutboundActiveByFromTo(ca.from, ca.to) - if err != nil && err != ErrChannelNotTracked { - return nil, err - } - - if channelInfo == nil { - // Channel does not exist + 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), @@ -316,6 +314,9 @@ func (ca *channelAccessor) currentAvailableFunds(queuedAmt types.BigInt) (*api.C 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 From 1d3a21f6c8919d403ef7f8da05f908c3dcd18454 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Thu, 3 Sep 2020 08:22:40 +0200 Subject: [PATCH 13/41] docs: paych - correct comments on store fields --- paychmgr/store.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/paychmgr/store.go b/paychmgr/store.go index 0ae81da57..4a5a4f49f 100644 --- a/paychmgr/store.go +++ b/paychmgr/store.go @@ -59,12 +59,12 @@ type ChannelInfo struct { ChannelID string // Channel address - may be nil if the channel hasn't been created yet Channel *address.Address - // Control is the address of the account that created the channel + // Control is the address of the local node Control address.Address - // Target is the address of the account on the other end of the channel + // Target is the address of the remote node (on the other end of the channel) Target address.Address - // Direction indicates if the channel is inbound (this node is the Target) - // or outbound (this node is the Control) + // Direction indicates if the channel is inbound (Control is the "to" address) + // or outbound (Control is the "from" address) Direction uint64 // Vouchers is a list of all vouchers sent on the channel Vouchers []*VoucherInfo From ea6ee8e8998c5f8a14d2655f5cf95e593ab7f9e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 3 Sep 2020 13:39:16 +0200 Subject: [PATCH 14/41] Disable codecov annotations --- .codecov.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.codecov.yml b/.codecov.yml index cf409a6b6..1551f2276 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,3 +1,5 @@ comment: off ignore: - "cbor_gen.go" +github_checks: + annotations: false From d35b273dbf53455bfe1e60b35a1d45deab942319 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 3 Sep 2020 13:49:50 +0200 Subject: [PATCH 15/41] Cap fees to reasonable level by default Signed-off-by: Jakub Sztandera --- node/impl/full/gas.go | 41 +++++++++++++++++++++++------------------ node/impl/full/mpool.go | 24 ------------------------ 2 files changed, 23 insertions(+), 42 deletions(-) diff --git a/node/impl/full/gas.go b/node/impl/full/gas.go index 54d9ef1b1..d0fae9afc 100644 --- a/node/impl/full/gas.go +++ b/node/impl/full/gas.go @@ -14,6 +14,7 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/specs-actors/actors/abi" "github.com/filecoin-project/specs-actors/actors/abi/big" "github.com/filecoin-project/specs-actors/actors/builtin" "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" @@ -36,28 +37,11 @@ func (a *GasAPI) GasEstimateFeeCap(ctx context.Context, msg *types.Message, maxq tsk types.TipSetKey) (types.BigInt, error) { ts := a.Chain.GetHeaviestTipSet() - var act types.Actor - err := a.Stmgr.WithParentState(ts, a.Stmgr.WithActor(msg.From, stmgr.GetActor(&act))) - if err != nil { - return types.NewInt(0), xerrors.Errorf("getting actor: %w", err) - } - parentBaseFee := ts.Blocks()[0].ParentBaseFee increaseFactor := math.Pow(1.+1./float64(build.BaseFeeMaxChangeDenom), float64(maxqueueblks)) feeInFuture := types.BigMul(parentBaseFee, types.NewInt(uint64(increaseFactor*(1<<8)))) - feeInFuture = types.BigDiv(feeInFuture, types.NewInt(1<<8)) - - gasLimitBig := types.NewInt(uint64(msg.GasLimit)) - maxAccepted := types.BigDiv(act.Balance, types.NewInt(MaxSpendOnFeeDenom)) - expectedFee := types.BigMul(feeInFuture, gasLimitBig) - - out := feeInFuture - if types.BigCmp(expectedFee, maxAccepted) > 0 { - log.Warnf("Expected fee for message higher than tolerance: %s > %s, setting to tolerance", - types.FIL(expectedFee), types.FIL(maxAccepted)) - out = types.BigDiv(maxAccepted, gasLimitBig) - } + out := types.BigDiv(feeInFuture, types.NewInt(1<<8)) if msg.GasPremium != types.EmptyInt { out = types.BigAdd(out, msg.GasPremium) @@ -225,3 +209,24 @@ func (a *GasAPI) GasEstimateMessageGas(ctx context.Context, msg *types.Message, return msg, nil } + +func capGasFee(msg *types.Message, maxFee abi.TokenAmount) { + if maxFee.Equals(big.Zero()) { + maxFee = types.NewInt(build.FilecoinPrecision / 10) + } + + gl := types.NewInt(uint64(msg.GasLimit)) + totalFee := types.BigMul(msg.GasFeeCap, gl) + minerFee := types.BigMul(msg.GasPremium, gl) + + if totalFee.LessThanEqual(maxFee) { + return + } + + // scale chain/miner fee down proportionally to fit in our budget + // TODO: there are probably smarter things we can do here to optimize + // message inclusion latency + + msg.GasFeeCap = big.Div(maxFee, gl) + msg.GasPremium = big.Div(big.Div(big.Mul(minerFee, maxFee), totalFee), gl) +} diff --git a/node/impl/full/mpool.go b/node/impl/full/mpool.go index 735b982d0..bfb7439bb 100644 --- a/node/impl/full/mpool.go +++ b/node/impl/full/mpool.go @@ -8,9 +8,6 @@ import ( "go.uber.org/fx" "golang.org/x/xerrors" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/messagepool" "github.com/filecoin-project/lotus/chain/store" @@ -115,27 +112,6 @@ func (a *MpoolAPI) MpoolPush(ctx context.Context, smsg *types.SignedMessage) (ci return a.Mpool.Push(smsg) } -func capGasFee(msg *types.Message, maxFee abi.TokenAmount) { - if maxFee.Equals(big.Zero()) { - return - } - - gl := types.NewInt(uint64(msg.GasLimit)) - totalFee := types.BigMul(msg.GasFeeCap, gl) - minerFee := types.BigMul(msg.GasPremium, gl) - - if totalFee.LessThanEqual(maxFee) { - return - } - - // scale chain/miner fee down proportionally to fit in our budget - // TODO: there are probably smarter things we can do here to optimize - // message inclusion latency - - msg.GasFeeCap = big.Div(maxFee, gl) - msg.GasPremium = big.Div(big.Div(big.Mul(minerFee, maxFee), totalFee), gl) -} - func (a *MpoolAPI) MpoolPushMessage(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec) (*types.SignedMessage, error) { { fromA, err := a.Stmgr.ResolveToKeyAddress(ctx, msg.From, nil) From 4c489d18a22b642721fac2feae4339e43ec1b9ef Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 1 Sep 2020 17:57:44 +0300 Subject: [PATCH 16/41] track expected nonce in mpool, refuse messages with large gaps --- chain/messagepool/messagepool.go | 142 ++++++++++++++++++++++++------- chain/messagepool/pruning.go | 2 +- 2 files changed, 111 insertions(+), 33 deletions(-) diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index bef0ed271..4b1204c4a 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -7,6 +7,7 @@ import ( "fmt" "math" stdbig "math/big" + "runtime" "sort" "sync" "time" @@ -52,6 +53,8 @@ var minimumBaseFee = types.NewInt(uint64(build.MinimumBaseFee)) var MaxActorPendingMessages = 1000 +var MaxNonceGap = uint64(runtime.NumCPU()) + var ( ErrMessageTooBig = errors.New("message too big") @@ -68,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") ) @@ -131,19 +135,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 @@ -165,11 +189,12 @@ func (ms *msgSet) add(m *types.SignedMessage, mp *MessagePool, limit bool) (bool //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 } + 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) @@ -177,12 +202,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 } } @@ -476,6 +527,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() @@ -483,7 +568,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) @@ -501,11 +586,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 @@ -664,14 +754,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 @@ -688,22 +778,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 } } @@ -771,7 +849,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 } @@ -780,7 +858,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) { @@ -1082,7 +1160,7 @@ func (mp *MessagePool) loadLocal() error { return xerrors.Errorf("unmarshaling local message: %w", err) } - if err := mp.Add(&sm); err != nil { + if err := mp.addLoaded(&sm); err != nil { if xerrors.Is(err, ErrNonceTooLow) { continue // todo: drop the message from local cache (if above certain confidence threshold) } diff --git a/chain/messagepool/pruning.go b/chain/messagepool/pruning.go index 143dd029e..d1290e386 100644 --- a/chain/messagepool/pruning.go +++ b/chain/messagepool/pruning.go @@ -98,7 +98,7 @@ keepLoop: // and remove all messages that are still in pruneMsgs after processing the chains log.Infof("Pruning %d messages", len(pruneMsgs)) for _, m := range pruneMsgs { - mp.remove(m.Message.From, m.Message.Nonce) + mp.remove(m.Message.From, m.Message.Nonce, false) } return nil From da6d384300d5db8cec3c825e5b93d51405964315 Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 1 Sep 2020 17:57:52 +0300 Subject: [PATCH 17/41] fix tests --- chain/messagepool/messagepool_test.go | 6 ++++++ chain/messagepool/selection_test.go | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/chain/messagepool/messagepool_test.go b/chain/messagepool/messagepool_test.go index bab4b81e3..484c72746 100644 --- a/chain/messagepool/messagepool_test.go +++ b/chain/messagepool/messagepool_test.go @@ -352,6 +352,12 @@ func TestRevertMessages(t *testing.T) { } func TestPruningSimple(t *testing.T) { + oldMaxNonceGap := MaxNonceGap + MaxNonceGap = 1000 + defer func() { + MaxNonceGap = oldMaxNonceGap + }() + tma := newTestMpoolAPI() w, err := wallet.NewWallet(wallet.NewMemKeyStore()) diff --git a/chain/messagepool/selection_test.go b/chain/messagepool/selection_test.go index f22cd095d..a9797a466 100644 --- a/chain/messagepool/selection_test.go +++ b/chain/messagepool/selection_test.go @@ -369,6 +369,12 @@ func TestMessageChainSkipping(t *testing.T) { } func TestBasicMessageSelection(t *testing.T) { + oldMaxNonceGap := MaxNonceGap + MaxNonceGap = 1000 + defer func() { + MaxNonceGap = oldMaxNonceGap + }() + mp, tma := makeTestMpool() // the actors From d76a3b87c5d77c7ee7d3bf7ae10972159c1af0d4 Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 1 Sep 2020 17:59:44 +0300 Subject: [PATCH 18/41] ignore messages with large nonce gaps --- chain/sub/incoming.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chain/sub/incoming.go b/chain/sub/incoming.go index 017590463..5c28aa835 100644 --- a/chain/sub/incoming.go +++ b/chain/sub/incoming.go @@ -555,6 +555,8 @@ func (mv *MessageValidator) Validate(ctx context.Context, pid peer.ID, msg *pubs fallthrough case xerrors.Is(err, messagepool.ErrTooManyPendingMessages): fallthrough + case xerrors.Is(err, messagepool.ErrNonceGap): + fallthrough case xerrors.Is(err, messagepool.ErrNonceTooLow): return pubsub.ValidationIgnore default: From 7b76aa2078d0b3419edccf37ca8634acca12fd6c Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 1 Sep 2020 22:46:36 +0300 Subject: [PATCH 19/41] warn when adding a nonce-gapped message to the mpool --- chain/messagepool/messagepool.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index 4b1204c4a..951c74a2c 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -194,6 +194,11 @@ func (ms *msgSet) add(m *types.SignedMessage, mp *MessagePool, strict bool) (boo 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) From f53d2e3a4653a806d3dde839ed104b41007f4af1 Mon Sep 17 00:00:00 2001 From: vyzo Date: Wed, 2 Sep 2020 00:12:43 +0300 Subject: [PATCH 20/41] cap MaxNonceGap to 16 --- chain/messagepool/messagepool.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index 951c74a2c..556c20198 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -53,7 +53,7 @@ var minimumBaseFee = types.NewInt(uint64(build.MinimumBaseFee)) var MaxActorPendingMessages = 1000 -var MaxNonceGap = uint64(runtime.NumCPU()) +var MaxNonceGap = uint64(16) var ( ErrMessageTooBig = errors.New("message too big") @@ -82,6 +82,13 @@ const ( localUpdates = "update" ) +func init() { + numcpus := uint64(runtime.NumCPU()) + if numcpus < MaxNonceGap { + MaxNonceGap = numcpus + } +} + type MessagePool struct { lk sync.Mutex From 28f57667f0e48bc370017e3e37f125126d9d736a Mon Sep 17 00:00:00 2001 From: vyzo Date: Wed, 2 Sep 2020 01:17:22 +0300 Subject: [PATCH 21/41] cap MaxNonceGap to 4, add delay between batch messages during republish --- chain/messagepool/messagepool.go | 10 +--------- chain/messagepool/repub.go | 9 +++++++++ chain/messagepool/repub_test.go | 6 ++++++ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index 556c20198..7fc4f2b9d 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -7,7 +7,6 @@ import ( "fmt" "math" stdbig "math/big" - "runtime" "sort" "sync" "time" @@ -53,7 +52,7 @@ var minimumBaseFee = types.NewInt(uint64(build.MinimumBaseFee)) var MaxActorPendingMessages = 1000 -var MaxNonceGap = uint64(16) +var MaxNonceGap = uint64(4) var ( ErrMessageTooBig = errors.New("message too big") @@ -82,13 +81,6 @@ const ( localUpdates = "update" ) -func init() { - numcpus := uint64(runtime.NumCPU()) - if numcpus < MaxNonceGap { - MaxNonceGap = numcpus - } -} - type MessagePool struct { lk sync.Mutex diff --git a/chain/messagepool/repub.go b/chain/messagepool/repub.go index acbf23892..1173bdb48 100644 --- a/chain/messagepool/repub.go +++ b/chain/messagepool/repub.go @@ -3,6 +3,7 @@ package messagepool import ( "context" "sort" + "time" "golang.org/x/xerrors" @@ -15,6 +16,8 @@ import ( const repubMsgLimit = 30 +var RepublishBatchDelay = 200 * time.Millisecond + func (mp *MessagePool) republishPendingMessages() error { mp.curTsLk.Lock() ts := mp.curTs @@ -131,6 +134,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) + } } // track most recently republished messages diff --git a/chain/messagepool/repub_test.go b/chain/messagepool/repub_test.go index 28a69c92a..703301601 100644 --- a/chain/messagepool/repub_test.go +++ b/chain/messagepool/repub_test.go @@ -12,6 +12,12 @@ import ( ) func TestRepubMessages(t *testing.T) { + oldRepublishBatchDelay = RepublishBatchDelay + RepublishBatchDelay = time.Microsecond + defer func() { + RepublishBatchDelay = oldRepublishBatchDelay + }() + tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() From a4c650316ad9d8a9169b9bdf8640455dc2ae2dee Mon Sep 17 00:00:00 2001 From: vyzo Date: Wed, 2 Sep 2020 09:14:35 +0300 Subject: [PATCH 22/41] fix test --- chain/messagepool/repub_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/messagepool/repub_test.go b/chain/messagepool/repub_test.go index 703301601..c89367f0e 100644 --- a/chain/messagepool/repub_test.go +++ b/chain/messagepool/repub_test.go @@ -12,7 +12,7 @@ import ( ) func TestRepubMessages(t *testing.T) { - oldRepublishBatchDelay = RepublishBatchDelay + oldRepublishBatchDelay := RepublishBatchDelay RepublishBatchDelay = time.Microsecond defer func() { RepublishBatchDelay = oldRepublishBatchDelay From d20b6adfd2744bbde4774794fe85bf22755c3418 Mon Sep 17 00:00:00 2001 From: vyzo Date: Thu, 3 Sep 2020 09:00:03 +0300 Subject: [PATCH 23/41] refuse to add duplicates in the mpool --- chain/messagepool/messagepool.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index 7fc4f2b9d..e41e8b0c7 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -182,6 +182,9 @@ func (ms *msgSet) add(m *types.SignedMessage, mp *MessagePool, strict bool) (boo 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) From 8423325a6e462f4ba540a328e729f135dcc64025 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Thu, 3 Sep 2020 15:31:49 +0200 Subject: [PATCH 24/41] refactor: paych - use channel accessor from/to instead of passing them around as params --- paychmgr/manager.go | 2 +- paychmgr/simple.go | 39 +++++++++++---------------------------- 2 files changed, 12 insertions(+), 29 deletions(-) diff --git a/paychmgr/manager.go b/paychmgr/manager.go index 093afc0e9..d1fd715ef 100644 --- a/paychmgr/manager.go +++ b/paychmgr/manager.go @@ -138,7 +138,7 @@ 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) { diff --git a/paychmgr/simple.go b/paychmgr/simple.go index dc089d3fa..88d94645e 100644 --- a/paychmgr/simple.go +++ b/paychmgr/simple.go @@ -36,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 @@ -47,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, } @@ -150,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) @@ -180,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: @@ -226,7 +214,7 @@ func (ca *channelAccessor) processQueue() (*api.ChannelAvailableFunds, error) { 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 @@ -360,23 +348,18 @@ func (ca *channelAccessor) currentAvailableFunds(queuedAmt types.BigInt) (*api.C // 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} } @@ -408,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 } @@ -424,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, @@ -437,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 From f84b1d0de917f682276fec975d4642c745e7d8f3 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Thu, 3 Sep 2020 15:45:09 +0200 Subject: [PATCH 25/41] feat: paych - rename CLI command from "paych get" to "paych add-funds" --- cli/paych.go | 8 ++++---- cli/paych_test.go | 8 ++++---- documentation/en/payment-channels.md | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cli/paych.go b/cli/paych.go index ff4d769da..a882a4a01 100644 --- a/cli/paych.go +++ b/cli/paych.go @@ -21,7 +21,7 @@ var paychCmd = &cli.Command{ Name: "paych", Usage: "Manage payment channels", Subcommands: []*cli.Command{ - paychGetCmd, + paychAddFundsCmd, paychListCmd, paychVoucherCmd, paychSettleCmd, @@ -29,9 +29,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 { diff --git a/cli/paych_test.go b/cli/paych_test.go index 11a08fb2c..9820aa015 100644 --- a/cli/paych_test.go +++ b/cli/paych_test.go @@ -61,10 +61,10 @@ func TestPaymentChannels(t *testing.T) { creatorCLI := mockCLI.client(paymentCreator.ListenAddr) receiverCLI := mockCLI.client(paymentReceiver.ListenAddr) - // creator: paych get + // creator: paych add-funds channelAmt := "100000" cmd := []string{creatorAddr.String(), receiverAddr.String(), channelAmt} - chstr := creatorCLI.runCmd(paychGetCmd, cmd) + chstr := creatorCLI.runCmd(paychAddFundsCmd, cmd) chAddr, err := address.NewFromString(chstr) require.NoError(t, err) @@ -116,10 +116,10 @@ func TestPaymentChannelVouchers(t *testing.T) { creatorCLI := mockCLI.client(paymentCreator.ListenAddr) receiverCLI := mockCLI.client(paymentReceiver.ListenAddr) - // creator: paych get + // creator: paych add-funds channelAmt := "100000" cmd := []string{creatorAddr.String(), receiverAddr.String(), channelAmt} - chstr := creatorCLI.runCmd(paychGetCmd, cmd) + chstr := creatorCLI.runCmd(paychAddFundsCmd, cmd) chAddr, err := address.NewFromString(chstr) require.NoError(t, err) diff --git a/documentation/en/payment-channels.md b/documentation/en/payment-channels.md index 44f9affd9..7179da916 100644 --- a/documentation/en/payment-channels.md +++ b/documentation/en/payment-channels.md @@ -22,7 +22,7 @@ Note that payment channels and vouchers can be used for any situation in which t For example a client creates a payment channel to a provider with value 10 FIL. ```sh -$ lotus paych get 10 +$ lotus paych add-funds 10 ``` @@ -51,10 +51,10 @@ $ lotus paych voucher create 4 The client sends the voucher to the provider and the provider adds the voucher and sends back more data. etc. -The client can add value to the channel after it has been created by calling `paych get` with the same client and provider addresses. +The client can add value to the channel after it has been created by calling `paych add-funds` with the same client and provider addresses. ```sh -$ lotus paych get 5 +$ lotus paych add-funds 5 # Same address as above. Channel now has 15 ``` From 930be3d0e56521c01b0faac75669d0177082ad73 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Thu, 3 Sep 2020 18:03:42 +0200 Subject: [PATCH 26/41] feat: paych CLI - lotus paych status --- cli/paych.go | 87 +++++++++++++++++++++++++++++++++++++++++++++++ cli/paych_test.go | 81 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 167 insertions(+), 1 deletion(-) diff --git a/cli/paych.go b/cli/paych.go index ce92af0bc..26bb76127 100644 --- a/cli/paych.go +++ b/cli/paych.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "sort" + "strings" "github.com/filecoin-project/lotus/paychmgr" @@ -79,6 +80,92 @@ var paychGetCmd = &cli.Command{ }, } +var paychStatusCmd = &cli.Command{ + Name: "status", + Usage: "Show the status of an outbound payment channel between fromAddress and toAddress", + ArgsUsage: "[fromAddress toAddress]", + Action: func(cctx *cli.Context) error { + if cctx.Args().Len() != 2 { + return ShowHelp(cctx, fmt.Errorf("must pass two arguments: ")) + } + + from, err := address.NewFromString(cctx.Args().Get(0)) + if err != nil { + return ShowHelp(cctx, fmt.Errorf("failed to parse from address: %s", err)) + } + + to, err := address.NewFromString(cctx.Args().Get(1)) + if err != nil { + return ShowHelp(cctx, fmt.Errorf("failed to parse to address: %s", err)) + } + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + + avail, err := api.PaychAvailableFunds(from, to) + if err != nil { + return err + } + + if avail.Channel == nil { + if avail.PendingWaitSentinel != nil { + fmt.Fprint(cctx.App.Writer, "Creating channel\n") + fmt.Fprintf(cctx.App.Writer, " From: %s\n", from) + fmt.Fprintf(cctx.App.Writer, " To: %s\n", to) + fmt.Fprintf(cctx.App.Writer, " Pending Amt: %d\n", avail.PendingAmt) + fmt.Fprintf(cctx.App.Writer, " Wait Sentinel: %s\n", avail.PendingWaitSentinel) + return nil + } + fmt.Fprint(cctx.App.Writer, "Channel does not exist\n") + fmt.Fprintf(cctx.App.Writer, " From: %s\n", from) + fmt.Fprintf(cctx.App.Writer, " To: %s\n", to) + return nil + } + + if avail.PendingWaitSentinel != nil { + fmt.Fprint(cctx.App.Writer, "Adding Funds to channel\n") + } else { + fmt.Fprint(cctx.App.Writer, "Channel exists\n") + } + + nameValues := [][]string{ + {"Channel", avail.Channel.String()}, + {"From", from.String()}, + {"To", to.String()}, + {"Confirmed Amt", fmt.Sprintf("%d", avail.ConfirmedAmt)}, + {"Pending Amt", fmt.Sprintf("%d", avail.PendingAmt)}, + {"Queued Amt", fmt.Sprintf("%d", avail.QueuedAmt)}, + {"Voucher Redeemed Amt", fmt.Sprintf("%d", avail.VoucherReedeemedAmt)}, + } + if avail.PendingWaitSentinel != nil { + nameValues = append(nameValues, []string{ + "Add Funds Wait Sentinel", + avail.PendingWaitSentinel.String(), + }) + } + fmt.Fprint(cctx.App.Writer, formatNameValues(nameValues)) + return nil + }, +} + +func formatNameValues(nameValues [][]string) string { + maxLen := 0 + for _, nv := range nameValues { + if len(nv[0]) > maxLen { + maxLen = len(nv[0]) + } + } + out := make([]string, len(nameValues)) + for i, nv := range nameValues { + namePad := strings.Repeat(" ", maxLen-len(nv[0])) + out[i] = " " + nv[0] + ": " + namePad + nv[1] + } + return strings.Join(out, "\n") + "\n" +} + var paychListCmd = &cli.Command{ Name: "list", Usage: "List all locally registered payment channels", diff --git a/cli/paych_test.go b/cli/paych_test.go index 31d41e36a..f03fa7b41 100644 --- a/cli/paych_test.go +++ b/cli/paych_test.go @@ -12,6 +12,8 @@ import ( "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" @@ -53,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] @@ -99,6 +101,83 @@ type voucherSpec struct { lane int } +// TestPaymentChannelStatus tests the payment channel status CLI command +func TestPaymentChannelStatus(t *testing.T) { + _ = os.Setenv("BELLMAN_NO_GPU", "1") + + blocktime := 5 * time.Millisecond + ctx := context.Background() + nodes, addrs := startTwoNodesOneMiner(ctx, t, blocktime) + paymentCreator := nodes[0] + creatorAddr := addrs[0] + receiverAddr := addrs[1] + + // Create mock CLI + mockCLI := newMockCLI(t) + creatorCLI := mockCLI.client(paymentCreator.ListenAddr) + + cmd := []string{creatorAddr.String(), receiverAddr.String()} + out := creatorCLI.runCmd(paychStatusCmd, cmd) + fmt.Println(out) + noChannelState := "Channel does not exist" + require.Regexp(t, regexp.MustCompile(noChannelState), out) + + channelAmt := uint64(100) + create := make(chan string) + go func() { + // creator: paych get + cmd = []string{creatorAddr.String(), receiverAddr.String(), fmt.Sprintf("%d", channelAmt)} + create <- creatorCLI.runCmd(paychGetCmd, cmd) + }() + + // Wait for the output to stop being "Channel does not exist" + for regexp.MustCompile(noChannelState).MatchString(out) { + cmd = []string{creatorAddr.String(), receiverAddr.String()} + out = creatorCLI.runCmd(paychStatusCmd, cmd) + } + fmt.Println(out) + + // The next state should be creating channel or channel created, depending + // on timing + stateCreating := regexp.MustCompile("Creating channel").MatchString(out) + stateCreated := regexp.MustCompile("Channel exists").MatchString(out) + require.True(t, stateCreating || stateCreated) + + channelAmtAtto := types.BigMul(types.NewInt(channelAmt), types.NewInt(build.FilecoinPrecision)) + channelAmtStr := fmt.Sprintf("%d", channelAmtAtto) + if stateCreating { + // If we're in the creating state (most likely) the amount should be pending + require.Regexp(t, regexp.MustCompile("Pending.*"+channelAmtStr), out) + } + + // Wait for create channel to complete + chstr := <-create + + cmd = []string{creatorAddr.String(), receiverAddr.String()} + out = creatorCLI.runCmd(paychStatusCmd, cmd) + fmt.Println(out) + // Output should have the channel address + require.Regexp(t, regexp.MustCompile("Channel.*"+chstr), out) + // Output should have confirmed amount + require.Regexp(t, regexp.MustCompile("Confirmed.*"+channelAmtStr), out) + + chAddr, err := address.NewFromString(chstr) + require.NoError(t, err) + + // creator: paych voucher create + voucherAmt := uint64(10) + cmd = []string{chAddr.String(), fmt.Sprintf("%d", voucherAmt)} + creatorCLI.runCmd(paychVoucherCreateCmd, cmd) + + cmd = []string{creatorAddr.String(), receiverAddr.String()} + out = creatorCLI.runCmd(paychStatusCmd, cmd) + fmt.Println(out) + voucherAmtAtto := types.BigMul(types.NewInt(voucherAmt), types.NewInt(build.FilecoinPrecision)) + voucherAmtStr := fmt.Sprintf("%d", voucherAmtAtto) + // Output should include voucher amount + require.Regexp(t, regexp.MustCompile("Voucher.*"+voucherAmtStr), out) +} + // TestPaymentChannelVouchers does a basic test to exercise some payment // channel voucher commands func TestPaymentChannelVouchers(t *testing.T) { From 23ec780a13b14f350c3540db12fe9458af1ec268 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Thu, 3 Sep 2020 12:48:20 -0400 Subject: [PATCH 27/41] Lotus version v0.5.9 --- CHANGELOG.md | 12 ++++++++++++ build/version.go | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24017fc4e..37b2d6024 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Lotus changelog +# 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. diff --git a/build/version.go b/build/version.go index 6062d2739..076071a69 100644 --- a/build/version.go +++ b/build/version.go @@ -25,7 +25,7 @@ func buildType() string { } // BuildVersion is the local build version, set by build system -const BuildVersion = "0.5.8" +const BuildVersion = "0.5.9" func UserVersion() string { return BuildVersion + buildType() + CurrentCommit From 1608cd2d53932bab97c7b77f8f9feb4a38f58372 Mon Sep 17 00:00:00 2001 From: Ingar Shu Date: Wed, 2 Sep 2020 10:58:44 -0700 Subject: [PATCH 28/41] Add watch option to "lotus-miner storage-deals list" --- api/api_storage.go | 2 +- api/apistruct/struct.go | 6 +- api/test/deals.go | 26 ++++---- cmd/lotus-storage-miner/market.go | 104 +++++++++++++++++++++--------- node/impl/storminer.go | 10 ++- 5 files changed, 97 insertions(+), 51 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index 14aa5ff97..48f6e9e45 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -73,7 +73,7 @@ type StorageMiner interface { MarketImportDealData(ctx context.Context, propcid cid.Cid, path string) error MarketListDeals(ctx context.Context) ([]storagemarket.StorageDeal, error) MarketListRetrievalDeals(ctx context.Context) ([]retrievalmarket.ProviderDealState, error) - MarketGetDealUpdates(ctx context.Context, d cid.Cid) (<-chan storagemarket.MinerDeal, error) + MarketGetDealUpdates(ctx context.Context) (<-chan storagemarket.MinerDeal, error) MarketListIncompleteDeals(ctx context.Context) ([]storagemarket.MinerDeal, error) MarketSetAsk(ctx context.Context, price types.BigInt, verifiedPrice types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error MarketGetAsk(ctx context.Context) (*storagemarket.SignedStorageAsk, error) diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index ac7e1b4a3..2b173e038 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -240,7 +240,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"` @@ -1087,8 +1087,8 @@ func (c *StorageMinerStruct) MarketListRetrievalDeals(ctx context.Context) ([]re return c.Internal.MarketListRetrievalDeals(ctx) } -func (c *StorageMinerStruct) MarketGetDealUpdates(ctx context.Context, d cid.Cid) (<-chan storagemarket.MinerDeal, error) { - return c.Internal.MarketGetDealUpdates(ctx, d) +func (c *StorageMinerStruct) MarketGetDealUpdates(ctx context.Context) (<-chan storagemarket.MinerDeal, error) { + return c.Internal.MarketGetDealUpdates(ctx) } func (c *StorageMinerStruct) MarketListIncompleteDeals(ctx context.Context) ([]storagemarket.MinerDeal, error) { diff --git a/api/test/deals.go b/api/test/deals.go index 00786e785..1dcc1c8d7 100644 --- a/api/test/deals.go +++ b/api/test/deals.go @@ -334,7 +334,7 @@ loop: func waitDealPublished(t *testing.T, ctx context.Context, miner TestStorageNode, deal *cid.Cid) { subCtx, cancel := context.WithCancel(ctx) defer cancel() - updates, err := miner.MarketGetDealUpdates(subCtx, *deal) + updates, err := miner.MarketGetDealUpdates(subCtx) if err != nil { t.Fatal(err) } @@ -343,18 +343,20 @@ func waitDealPublished(t *testing.T, ctx context.Context, miner TestStorageNode, case <-ctx.Done(): t.Fatal("context timeout") case di := <-updates: - switch di.State { - case storagemarket.StorageDealProposalRejected: - t.Fatal("deal rejected") - case storagemarket.StorageDealFailing: - t.Fatal("deal failed") - case storagemarket.StorageDealError: - t.Fatal("deal errored", di.Message) - case storagemarket.StorageDealFinalizing, storagemarket.StorageDealSealing, storagemarket.StorageDealActive: - fmt.Println("COMPLETE", di) - return + if deal.Equals(di.ProposalCid) { + switch di.State { + case storagemarket.StorageDealProposalRejected: + t.Fatal("deal rejected") + case storagemarket.StorageDealFailing: + t.Fatal("deal failed") + case storagemarket.StorageDealError: + t.Fatal("deal errored", di.Message) + case storagemarket.StorageDealFinalizing, storagemarket.StorageDealSealing, storagemarket.StorageDealActive: + fmt.Println("COMPLETE", di) + return + } + fmt.Println("Deal state: ", storagemarket.DealStates[di.State]) } - fmt.Println("Deal state: ", storagemarket.DealStates[di.State]) } } } diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index 828fcd2c3..39671f74c 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -3,6 +3,7 @@ package main import ( "bufio" "fmt" + "io" "os" "path/filepath" "sort" @@ -345,6 +346,10 @@ var dealsListCmd = &cli.Command{ Name: "verbose", Aliases: []string{"v"}, }, + &cli.BoolFlag{ + Name: "watch", + Usage: "watch deal updates in real-time, rather than a one time list", + }, }, Action: func(cctx *cli.Context) error { api, closer, err := lcli.GetStorageMinerAPI(cctx) @@ -360,42 +365,83 @@ var dealsListCmd = &cli.Command{ return err } - sort.Slice(deals, func(i, j int) bool { - return deals[i].CreationTime.Time().Before(deals[j].CreationTime.Time()) - }) - - w := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) - verbose := cctx.Bool("verbose") + watch := cctx.Bool("watch") + + if watch { + updates, err := api.MarketGetDealUpdates(ctx) + if err != nil { + return err + } + + for { + tm.Clear() + tm.MoveCursor(1, 1) + + err = outputStorageDeals(tm.Output, deals, verbose) + if err != nil { + return err + } + + tm.Flush() + + select { + case <-ctx.Done(): + return nil + case updated := <-updates: + var found bool + for i, existing := range deals { + if existing.ProposalCid.Equals(updated.ProposalCid) { + deals[i] = updated + found = true + break + } + } + if !found { + deals = append(deals, updated) + } + } + } + } + + return outputStorageDeals(os.Stdout, deals, verbose) + }, +} + +func outputStorageDeals(out io.Writer, deals []storagemarket.MinerDeal, verbose bool) error { + sort.Slice(deals, func(i, j int) bool { + return deals[i].CreationTime.Time().Before(deals[j].CreationTime.Time()) + }) + + w := tabwriter.NewWriter(out, 2, 4, 2, ' ', 0) + + if verbose { + _, _ = fmt.Fprintf(w, "Creation\tProposalCid\tDealId\tState\tClient\tSize\tPrice\tDuration\tMessage\n") + } else { + _, _ = fmt.Fprintf(w, "ProposalCid\tDealId\tState\tClient\tSize\tPrice\tDuration\n") + } + + for _, deal := range deals { + propcid := deal.ProposalCid.String() + if !verbose { + propcid = "..." + propcid[len(propcid)-8:] + } + + fil := types.FIL(types.BigMul(deal.Proposal.StoragePricePerEpoch, types.NewInt(uint64(deal.Proposal.Duration())))) if verbose { - _, _ = fmt.Fprintf(w, "Creation\tProposalCid\tDealId\tState\tClient\tSize\tPrice\tDuration\tMessage\n") - } else { - _, _ = fmt.Fprintf(w, "ProposalCid\tDealId\tState\tClient\tSize\tPrice\tDuration\n") + _, _ = fmt.Fprintf(w, "%s\t", deal.CreationTime.Time().Format(time.Stamp)) } - for _, deal := range deals { - propcid := deal.ProposalCid.String() - if !verbose { - propcid = "..." + propcid[len(propcid)-8:] - } - - fil := types.FIL(types.BigMul(deal.Proposal.StoragePricePerEpoch, types.NewInt(uint64(deal.Proposal.Duration())))) - - if verbose { - _, _ = fmt.Fprintf(w, "%s\t", deal.CreationTime.Time().Format(time.Stamp)) - } - - _, _ = fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%s\t%s", propcid, deal.DealID, storagemarket.DealStates[deal.State], deal.Proposal.Client, units.BytesSize(float64(deal.Proposal.PieceSize)), fil, deal.Proposal.Duration()) - if verbose { - _, _ = fmt.Fprintf(w, "\t%s", deal.Message) - } - - _, _ = fmt.Fprintln(w) + _, _ = fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%s\t%s", propcid, deal.DealID, storagemarket.DealStates[deal.State], deal.Proposal.Client, units.BytesSize(float64(deal.Proposal.PieceSize)), fil, deal.Proposal.Duration()) + if verbose { + _, _ = fmt.Fprintf(w, "\t%s", deal.Message) } - return w.Flush() - }, + _, _ = fmt.Fprintln(w) + } + + return w.Flush() } var getBlocklistCmd = &cli.Command{ diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 3507453dd..c688ff677 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -320,14 +320,12 @@ func (sm *StorageMinerAPI) MarketListRetrievalDeals(ctx context.Context) ([]retr return out, nil } -func (sm *StorageMinerAPI) MarketGetDealUpdates(ctx context.Context, d cid.Cid) (<-chan storagemarket.MinerDeal, error) { +func (sm *StorageMinerAPI) MarketGetDealUpdates(ctx context.Context) (<-chan storagemarket.MinerDeal, error) { results := make(chan storagemarket.MinerDeal) unsub := sm.StorageProvider.SubscribeToEvents(func(evt storagemarket.ProviderEvent, deal storagemarket.MinerDeal) { - if deal.ProposalCid.Equals(d) { - select { - case results <- deal: - case <-ctx.Done(): - } + select { + case results <- deal: + case <-ctx.Done(): } }) go func() { From 5074b526d0c30b2540cb62018dfb05c767f99d79 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 3 Sep 2020 19:36:03 +0200 Subject: [PATCH 29/41] Fix calculation of GasReward in messagepool Signed-off-by: Jakub Sztandera --- chain/messagepool/selection.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chain/messagepool/selection.go b/chain/messagepool/selection.go index 7f7babba0..6c0fcd265 100644 --- a/chain/messagepool/selection.go +++ b/chain/messagepool/selection.go @@ -587,9 +587,11 @@ func (mp *MessagePool) getPendingMessages(curTs, ts *types.TipSet) (map[address. func (mp *MessagePool) getGasReward(msg *types.SignedMessage, baseFee types.BigInt, ts *types.TipSet) *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 } From 8bb471bccd784159ee86081dc6cfd08cb56d2a27 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 3 Sep 2020 19:50:35 +0200 Subject: [PATCH 30/41] Add getGasPerf test Signed-off-by: Jakub Sztandera --- chain/messagepool/selection.go | 8 +++--- chain/messagepool/selection_test.go | 39 ++++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/chain/messagepool/selection.go b/chain/messagepool/selection.go index 6c0fcd265..7accb39ff 100644 --- a/chain/messagepool/selection.go +++ b/chain/messagepool/selection.go @@ -585,7 +585,7 @@ 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 { @@ -596,7 +596,7 @@ func (mp *MessagePool) getGasReward(msg *types.SignedMessage, baseFee types.BigI 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) @@ -674,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) } @@ -778,7 +778,7 @@ func (mc *msgChain) Before(other *msgChain) bool { func (mc *msgChain) Trim(gasLimit int64, mp *MessagePool, baseFee types.BigInt, ts *types.TipSet) { i := len(mc.msgs) - 1 for i >= 0 && (mc.gasLimit > gasLimit || mc.gasPerf < 0) { - gasReward := mp.getGasReward(mc.msgs[i], baseFee, ts) + gasReward := mp.getGasReward(mc.msgs[i], baseFee) mc.gasReward = new(big.Int).Sub(mc.gasReward, gasReward) mc.gasLimit -= mc.msgs[i].Message.GasLimit if mc.gasLimit > 0 { diff --git a/chain/messagepool/selection_test.go b/chain/messagepool/selection_test.go index f22cd095d..b17ecc3ed 100644 --- a/chain/messagepool/selection_test.go +++ b/chain/messagepool/selection_test.go @@ -2,6 +2,7 @@ package messagepool import ( "context" + "fmt" "math" "math/big" "math/rand" @@ -1055,17 +1056,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 +1147,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) + } + }) + } +} From 8111b227c44077cc78900beb3d23bb035e71ce79 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 3 Sep 2020 19:53:49 +0200 Subject: [PATCH 31/41] Appease the linter Signed-off-by: Jakub Sztandera --- chain/messagepool/selection.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chain/messagepool/selection.go b/chain/messagepool/selection.go index 7accb39ff..5ba679d76 100644 --- a/chain/messagepool/selection.go +++ b/chain/messagepool/selection.go @@ -585,7 +585,7 @@ func (mp *MessagePool) getPendingMessages(curTs, ts *types.TipSet) (map[address. return result, nil } -func (_ *MessagePool) getGasReward(msg *types.SignedMessage, baseFee types.BigInt) *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 { @@ -596,7 +596,7 @@ func (_ *MessagePool) getGasReward(msg *types.SignedMessage, baseFee types.BigIn return gasReward.Int } -func (_ *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) From 94173c273a6f3c3c1157c8ab971fe1091bbb4491 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Thu, 3 Sep 2020 14:57:58 -0400 Subject: [PATCH 32/41] Lotus version 0.9.10 --- CHANGELOG.md | 8 ++++++++ build/version.go | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37b2d6024..ccefeda10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # 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. diff --git a/build/version.go b/build/version.go index 076071a69..a3c5d1552 100644 --- a/build/version.go +++ b/build/version.go @@ -25,7 +25,7 @@ func buildType() string { } // BuildVersion is the local build version, set by build system -const BuildVersion = "0.5.9" +const BuildVersion = "0.5.10" func UserVersion() string { return BuildVersion + buildType() + CurrentCommit From 86cb865fdd2aacd08b8e506061e98d5bf0ae5835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 3 Sep 2020 21:21:10 +0200 Subject: [PATCH 33/41] Make chain export ~1000x times faster --- node/impl/full/chain.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/node/impl/full/chain.go b/node/impl/full/chain.go index 61eb4d3f5..ce5e3822a 100644 --- a/node/impl/full/chain.go +++ b/node/impl/full/chain.go @@ -1,6 +1,7 @@ package full import ( + "bufio" "bytes" "context" "encoding/json" @@ -503,7 +504,11 @@ func (a *ChainAPI) ChainExport(ctx context.Context, nroots abi.ChainEpoch, tsk t out := make(chan []byte) go func() { defer w.Close() //nolint:errcheck // it is a pipe - if err := a.Chain.Export(ctx, ts, nroots, w); err != nil { + + bw := bufio.NewWriterSize(w, 1<<20) + defer bw.Flush() //nolint:errcheck // it is a write to a pipe + + if err := a.Chain.Export(ctx, ts, nroots, bw); err != nil { log.Errorf("chain export call failed: %s", err) return } @@ -512,7 +517,7 @@ func (a *ChainAPI) ChainExport(ctx context.Context, nroots abi.ChainEpoch, tsk t go func() { defer close(out) for { - buf := make([]byte, 4096) + buf := make([]byte, 1<<20) n, err := r.Read(buf) if err != nil && err != io.EOF { log.Errorf("chain export pipe read failed: %s", err) @@ -522,6 +527,7 @@ func (a *ChainAPI) ChainExport(ctx context.Context, nroots abi.ChainEpoch, tsk t case out <- buf[:n]: case <-ctx.Done(): log.Warnf("export writer failed: %s", ctx.Err()) + return } if err == io.EOF { return From 8e7a8d8c970a51706bbb03338115cbf975d7615b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 3 Sep 2020 22:54:19 +0100 Subject: [PATCH 34/41] conformance: tipset-class driver: allow actor msgs, dummy-sign secp msgs. --- conformance/driver.go | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/conformance/driver.go b/conformance/driver.go index b2a902a0c..41e1b6797 100644 --- a/conformance/driver.go +++ b/conformance/driver.go @@ -2,7 +2,8 @@ package conformance import ( "context" - "fmt" + + "github.com/filecoin-project/specs-actors/actors/crypto" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" @@ -80,11 +81,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) @@ -143,7 +147,7 @@ func (d *Driver) ExecuteMessage(bs blockstore.Blockstore, preroot cid.Cid, epoch 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 +155,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 +} From ada5e6ae68cf8c77726a0a7a1bdfaa4ac15f700e Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Wed, 2 Sep 2020 15:57:19 +0100 Subject: [PATCH 35/41] refactor: remove puppet actor from conformance tests The puppet actor has been subsumed into the chaos actor and test-vector tests no longer use it. --- conformance/driver.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/conformance/driver.go b/conformance/driver.go index 41e1b6797..218198a05 100644 --- a/conformance/driver.go +++ b/conformance/driver.go @@ -13,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" @@ -137,10 +136,7 @@ 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{}) } From c723554d2e34fc126a649e25f2bab340571afd48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 3 Sep 2020 23:31:27 +0100 Subject: [PATCH 36/41] upgrade test-vectors deps. --- extern/test-vectors | 2 +- go.mod | 2 +- go.sum | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extern/test-vectors b/extern/test-vectors index 9806d09b0..84da0a5ea 160000 --- a/extern/test-vectors +++ b/extern/test-vectors @@ -1 +1 @@ -Subproject commit 9806d09b005dbaa0d08a6944aca67dd5ad2cd3b3 +Subproject commit 84da0a5ea1256a6e66bcbf73542c93e4916d6356 diff --git a/go.mod b/go.mod index 700d591a6..49e078897 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/filecoin-project/specs-actors v0.9.3 github.com/filecoin-project/specs-storage v0.1.1-0.20200730063404-f7db367e9401 github.com/filecoin-project/statediff v0.0.1 - github.com/filecoin-project/test-vectors v0.0.0-20200902131127-9806d09b005d + github.com/filecoin-project/test-vectors v0.0.0-20200903223506-84da0a5ea125 github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 github.com/go-kit/kit v0.10.0 github.com/google/uuid v1.1.1 diff --git a/go.sum b/go.sum index 54d85c990..038121996 100644 --- a/go.sum +++ b/go.sum @@ -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= From ddc5e57d4d25fbefd484931d2a8f8fda78cda1c5 Mon Sep 17 00:00:00 2001 From: Mike Greenberg Date: Thu, 3 Sep 2020 12:44:36 -0400 Subject: [PATCH 37/41] fix(chainwatch): Error instead of panic during processing --- cmd/lotus-chainwatch/processor/messages.go | 12 ++++++++--- cmd/lotus-chainwatch/processor/processor.go | 22 +++++++++++++++------ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/cmd/lotus-chainwatch/processor/messages.go b/cmd/lotus-chainwatch/processor/messages.go index e3d23f219..333477c6a 100644 --- a/cmd/lotus-chainwatch/processor/messages.go +++ b/cmd/lotus-chainwatch/processor/messages.go @@ -254,7 +254,9 @@ func (p *Processor) fetchMessages(ctx context.Context, blocks map[cid.Cid]*types parmap.Par(50, parmap.MapArr(blocks), func(header *types.BlockHeader) { msgs, err := p.node.ChainGetBlockMessages(ctx, header.Cid()) if err != nil { - panic(err) + log.Error(err) + log.Debugw("ChainGetBlockMessages", "header_cid", header.Cid()) + return } vmm := make([]*types.Message, 0, len(msgs.Cids)) @@ -290,11 +292,15 @@ func (p *Processor) fetchParentReceipts(ctx context.Context, toSync map[cid.Cid] parmap.Par(50, parmap.MapArr(toSync), func(header *types.BlockHeader) { recs, err := p.node.ChainGetParentReceipts(ctx, header.Cid()) if err != nil { - panic(err) + log.Error(err) + log.Debugw("ChainGetParentReceipts", "header_cid", header.Cid()) + return } msgs, err := p.node.ChainGetParentMessages(ctx, header.Cid()) if err != nil { - panic(err) + log.Error(err) + log.Debugw("ChainGetParentMessages", "header_cid", header.Cid()) + return } lk.Lock() diff --git a/cmd/lotus-chainwatch/processor/processor.go b/cmd/lotus-chainwatch/processor/processor.go index 59e3bbb7e..59de94911 100644 --- a/cmd/lotus-chainwatch/processor/processor.go +++ b/cmd/lotus-chainwatch/processor/processor.go @@ -246,7 +246,8 @@ func (p *Processor) collectActorChanges(ctx context.Context, toProcess map[cid.C pts, err := p.node.ChainGetTipSet(ctx, types.NewTipSetKey(bh.Parents...)) if err != nil { - panic(err) + log.Error(err) + return } if pts.ParentState().Equals(bh.ParentStateRoot) { @@ -260,7 +261,9 @@ func (p *Processor) collectActorChanges(ctx context.Context, toProcess map[cid.C // a separate strategy for deleted actors changes, err = p.node.StateChangedActors(ctx, pts.ParentState(), bh.ParentStateRoot) if err != nil { - panic(err) + log.Error(err) + log.Debugw("StateChangedActors", "grandparent_state", pts.ParentState(), "parent_state", bh.ParentStateRoot) + return } // record the state of all actors that have changed @@ -271,7 +274,9 @@ func (p *Processor) collectActorChanges(ctx context.Context, toProcess map[cid.C // ignore actors that were deleted. has, err := p.node.ChainHasObj(ctx, act.Head) if err != nil { - log.Fatal(err) + log.Error(err) + log.Debugw("ChanHasObj", "actor_head", act.Head) + return } if !has { continue @@ -279,19 +284,24 @@ func (p *Processor) collectActorChanges(ctx context.Context, toProcess map[cid.C addr, err := address.NewFromString(a) if err != nil { - log.Fatal(err.Error()) + log.Error(err) + log.Debugw("NewFromString", "address_string", a) + return } ast, err := p.node.StateReadState(ctx, addr, pts.Key()) if err != nil { - log.Fatal(err.Error()) + log.Error(err) + log.Debugw("StateReadState", "address_string", a, "parent_tipset_key", pts.Key()) + return } // TODO look here for an empty state, maybe thats a sign the actor was deleted? state, err := json.Marshal(ast.State) if err != nil { - panic(err) + log.Error(err) + return } outMu.Lock() From abaef98fd88d082cd37dccf4fe1e17eb55b63763 Mon Sep 17 00:00:00 2001 From: Mike Greenberg Date: Thu, 3 Sep 2020 12:45:17 -0400 Subject: [PATCH 38/41] fix(chainwatch): Stop SyncIncomingBlocks from leaking into processing The SQL query was anchoring data from the `blocks` table, which includes all blocks seen from SyncIncomingBlocks which isn't always available in the chainstate via the API. In order to prevent these blocks from leaking into normal processing (which errors anyway), the join was changed to allow `blocks_synced` to anchor the set as we originally intended. --- cmd/lotus-chainwatch/processor/processor.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-chainwatch/processor/processor.go b/cmd/lotus-chainwatch/processor/processor.go index 59de94911..e6c2ffb94 100644 --- a/cmd/lotus-chainwatch/processor/processor.go +++ b/cmd/lotus-chainwatch/processor/processor.go @@ -334,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 From ce8bcf7932dc9d56f69be78fdcedfa53a056d6b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 4 Sep 2020 01:35:53 +0200 Subject: [PATCH 39/41] Add agent flag to net peers --- api/api_common.go | 1 + api/apistruct/struct.go | 5 +++ cli/net.go | 19 +++++++- documentation/en/api-methods.md | 77 +++++++++++++++++++++++++-------- node/impl/common/common.go | 13 ++++++ 5 files changed, 97 insertions(+), 18 deletions(-) diff --git a/api/api_common.go b/api/api_common.go index ec410bac7..f8fcbe8c5 100644 --- a/api/api_common.go +++ b/api/api_common.go @@ -30,6 +30,7 @@ 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. diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 02c2ee89f..2bc7d57c8 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -55,6 +55,7 @@ type CommonStruct struct { 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"` @@ -389,6 +390,10 @@ func (c *CommonStruct) NetBandwidthStatsByProtocol(ctx context.Context) (map[pro 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) diff --git a/cli/net.go b/cli/net.go index 7730bdf91..f3b5ae2e9 100644 --- a/cli/net.go +++ b/cli/net.go @@ -35,6 +35,13 @@ var netCmd = &cli.Command{ 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 { @@ -52,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 diff --git a/documentation/en/api-methods.md b/documentation/en/api-methods.md index 2127332e5..bba212d45 100644 --- a/documentation/en/api-methods.md +++ b/documentation/en/api-methods.md @@ -84,6 +84,7 @@ * [MsigSwapPropose](#MsigSwapPropose) * [Net](#Net) * [NetAddrsListen](#NetAddrsListen) + * [NetAgentVersion](#NetAgentVersion) * [NetAutoNatStatus](#NetAutoNatStatus) * [NetBandwidthStats](#NetBandwidthStats) * [NetBandwidthStatsByPeer](#NetBandwidthStatsByPeer) @@ -96,6 +97,7 @@ * [NetPubsubScores](#NetPubsubScores) * [Paych](#Paych) * [PaychAllocateLane](#PaychAllocateLane) + * [PaychAvailableFunds](#PaychAvailableFunds) * [PaychCollect](#PaychCollect) * [PaychGet](#PaychGet) * [PaychGetWaitReady](#PaychGetWaitReady) @@ -2047,6 +2049,20 @@ Response: } ``` +### NetAgentVersion + + +Perms: read + +Inputs: +```json +[ + "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf" +] +``` + +Response: `"string value"` + ### NetAutoNatStatus @@ -2218,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. @@ -2473,24 +2513,27 @@ Inputs: Response: ```json { - "ChannelAddr": "t01234", - "TimeLockMin": 10101, - "TimeLockMax": 10101, - "SecretPreimage": "Ynl0ZSBhcnJheQ==", - "Extra": { - "Actor": "t01234", - "Method": 1, - "Data": "Ynl0ZSBhcnJheQ==" + "Voucher": { + "ChannelAddr": "t01234", + "TimeLockMin": 10101, + "TimeLockMax": 10101, + "SecretPreimage": "Ynl0ZSBhcnJheQ==", + "Extra": { + "Actor": "t01234", + "Method": 1, + "Data": "Ynl0ZSBhcnJheQ==" + }, + "Lane": 42, + "Nonce": 42, + "Amount": "0", + "MinSettleHeight": 10101, + "Merges": null, + "Signature": { + "Type": 2, + "Data": "Ynl0ZSBhcnJheQ==" + } }, - "Lane": 42, - "Nonce": 42, - "Amount": "0", - "MinSettleHeight": 10101, - "Merges": null, - "Signature": { - "Type": 2, - "Data": "Ynl0ZSBhcnJheQ==" - } + "Shortfall": "0" } ``` diff --git a/node/impl/common/common.go b/node/impl/common/common.go index cd1812058..6a69b2a7a 100644 --- a/node/impl/common/common.go +++ b/node/impl/common/common.go @@ -136,6 +136,19 @@ 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 } From 8778787091e468f502ee49e72f69819e28909ce8 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 3 Sep 2020 17:17:00 -0700 Subject: [PATCH 40/41] [cli/state] Robust actor lookup This probably isn't critical, but I noticed it, so I fixed it. --- cli/state.go | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/cli/state.go b/cli/state.go index 1e75cc7cd..4d6253efc 100644 --- a/cli/state.go +++ b/cli/state.go @@ -26,11 +26,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/specs-actors/actors/abi" "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/builtin/market" - miner2 "github.com/filecoin-project/specs-actors/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/actors/builtin/multisig" - "github.com/filecoin-project/specs-actors/actors/builtin/paych" - "github.com/filecoin-project/specs-actors/actors/builtin/power" + "github.com/filecoin-project/specs-actors/actors/builtin/exported" "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" "github.com/filecoin-project/lotus/api" @@ -1460,21 +1456,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 { From 6e711ed7399e40ec1b35ae932d00fbeb8f8abfdd Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 3 Sep 2020 17:22:18 -0700 Subject: [PATCH 41/41] [cli/state] Robust actor name lookup --- cli/state.go | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/cli/state.go b/cli/state.go index 4d6253efc..a0256c2e3 100644 --- a/cli/state.go +++ b/cli/state.go @@ -564,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))