diff --git a/blockstore/splitstore/splitstore_compact.go b/blockstore/splitstore/splitstore_compact.go index f96f9d370..534565bf3 100644 --- a/blockstore/splitstore/splitstore_compact.go +++ b/blockstore/splitstore/splitstore_compact.go @@ -1114,13 +1114,17 @@ func (s *SplitStore) walkChain(ts *types.TipSet, inclState, inclMsgs abi.ChainEp if err := walkBlock(c); err != nil { return xerrors.Errorf("error walking block (cid: %s): %w", c, err) } + + if err := s.checkYield(); err != nil { + return xerrors.Errorf("check yield: %w", err) + } } return nil }) } if err := g.Wait(); err != nil { - return err + return xerrors.Errorf("walkBlock workers errored: %w", err) } } @@ -1153,8 +1157,8 @@ func (s *SplitStore) walkObject(c cid.Cid, visitor ObjectVisitor, f func(cid.Cid } // check this before recursing - if err := s.checkYield(); err != nil { - return 0, err + if err := s.checkClosing(); err != nil { + return 0, xerrors.Errorf("check closing: %w", err) } var links []cid.Cid @@ -1222,8 +1226,8 @@ func (s *SplitStore) walkObjectIncomplete(c cid.Cid, visitor ObjectVisitor, f, m } // check this before recursing - if err := s.checkYield(); err != nil { - return sz, err + if err := s.checkClosing(); err != nil { + return sz, xerrors.Errorf("check closing: %w", err) } var links []cid.Cid diff --git a/build/params_interop.go b/build/params_interop.go index 2d8d366e9..04fc777f5 100644 --- a/build/params_interop.go +++ b/build/params_interop.go @@ -49,16 +49,11 @@ var UpgradeHyperdriveHeight = abi.ChainEpoch(-16) var UpgradeChocolateHeight = abi.ChainEpoch(-17) var UpgradeOhSnapHeight = abi.ChainEpoch(-18) var UpgradeSkyrHeight = abi.ChainEpoch(-19) +var UpgradeSharkHeight = abi.ChainEpoch(-20) +var UpgradeHyggeHeight = abi.ChainEpoch(-21) +var UpgradeLightningHeight = abi.ChainEpoch(-22) -const UpgradeSharkHeight = abi.ChainEpoch(-20) - -const UpgradeHyggeHeight = abi.ChainEpoch(100) - -// ?????????? -const UpgradeLightningHeight = 200 - -// ?????????????????? -const UpgradeThunderHeight = 300 +const UpgradeThunderHeight = 50 var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ 0: DrandMainnet, diff --git a/chain/messagepool/check.go b/chain/messagepool/check.go index 17829057a..fdec910c4 100644 --- a/chain/messagepool/check.go +++ b/chain/messagepool/check.go @@ -35,6 +35,7 @@ func (mp *MessagePool) CheckPendingMessages(ctx context.Context, from address.Ad mp.lk.RLock() mset, ok, err := mp.getPendingMset(ctx, from) if err != nil { + mp.lk.RUnlock() return nil, xerrors.Errorf("errored while getting pending mset: %w", err) } if ok { @@ -70,6 +71,7 @@ func (mp *MessagePool) CheckReplaceMessages(ctx context.Context, replace []*type msgMap[m.From] = mmap mset, ok, err := mp.getPendingMset(ctx, m.From) if err != nil { + mp.lk.RUnlock() return nil, xerrors.Errorf("errored while getting pending mset: %w", err) } if ok { @@ -153,6 +155,7 @@ func (mp *MessagePool) checkMessages(ctx context.Context, msgs []*types.Message, mp.lk.RLock() mset, ok, err := mp.getPendingMset(ctx, m.From) if err != nil { + mp.lk.RUnlock() return nil, xerrors.Errorf("errored while getting pending mset: %w", err) } if ok && !interned { diff --git a/chain/stmgr/utils.go b/chain/stmgr/utils.go index c93267d50..5e3bbd278 100644 --- a/chain/stmgr/utils.go +++ b/chain/stmgr/utils.go @@ -72,7 +72,7 @@ func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch, base, trace, err := sm.ExecutionTrace(ctx, ts) if err != nil { - return cid.Undef, nil, err + return cid.Undef, nil, xerrors.Errorf("failed to compute base state: %w", err) } for i := ts.Height(); i < height; i++ { @@ -116,6 +116,21 @@ func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch, if ret.ExitCode != 0 { log.Infof("compute state apply message %d failed (exit: %d): %s", i, ret.ExitCode, ret.ActorErr) } + + ir := &api.InvocResult{ + MsgCid: msg.Cid(), + Msg: msg, + MsgRct: &ret.MessageReceipt, + ExecutionTrace: ret.ExecutionTrace, + Duration: ret.Duration, + } + if ret.ActorErr != nil { + ir.Error = ret.ActorErr.Error() + } + if ret.GasCosts != nil { + ir.GasCost = MakeMsgGasCost(msg, ret) + } + trace = append(trace, ir) } root, err := vmi.Flush(ctx) diff --git a/chain/store/index.go b/chain/store/index.go index 620cb2dee..8361f4db9 100644 --- a/chain/store/index.go +++ b/chain/store/index.go @@ -2,18 +2,21 @@ package store import ( "context" + "hash/maphash" "os" "strconv" - "sync" + "github.com/puzpuzpuz/xsync/v2" "golang.org/x/xerrors" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/lib/shardedmutex" ) -var DefaultChainIndexCacheSize = 32 << 15 +// DefaultChainIndexCacheSize no longer sets the maximum size, just the initial size of the map. +var DefaultChainIndexCacheSize = 1 << 15 func init() { if s := os.Getenv("LOTUS_CHAIN_INDEX_CACHE"); s != "" { @@ -27,8 +30,9 @@ func init() { } type ChainIndex struct { - indexCacheLk sync.Mutex - indexCache map[types.TipSetKey]*lbEntry + indexCache *xsync.MapOf[types.TipSetKey, *lbEntry] + + fillCacheLock shardedmutex.ShardedMutexFor[types.TipSetKey] loadTipSet loadTipSetFunc @@ -36,11 +40,16 @@ type ChainIndex struct { } type loadTipSetFunc func(context.Context, types.TipSetKey) (*types.TipSet, error) +func maphashTSK(s maphash.Seed, tsk types.TipSetKey) uint64 { + return maphash.Bytes(s, tsk.Bytes()) +} + func NewChainIndex(lts loadTipSetFunc) *ChainIndex { return &ChainIndex{ - indexCache: make(map[types.TipSetKey]*lbEntry, DefaultChainIndexCacheSize), - loadTipSet: lts, - skipLength: 20, + indexCache: xsync.NewTypedMapOfPresized[types.TipSetKey, *lbEntry](maphashTSK, DefaultChainIndexCacheSize), + fillCacheLock: shardedmutex.NewFor(maphashTSK, 32), + loadTipSet: lts, + skipLength: 20, } } @@ -59,17 +68,23 @@ func (ci *ChainIndex) GetTipsetByHeight(ctx context.Context, from *types.TipSet, return nil, xerrors.Errorf("failed to round down: %w", err) } - ci.indexCacheLk.Lock() - defer ci.indexCacheLk.Unlock() cur := rounded.Key() for { - lbe, ok := ci.indexCache[cur] + lbe, ok := ci.indexCache.Load(cur) // check the cache if !ok { - fc, err := ci.fillCache(ctx, cur) - if err != nil { - return nil, xerrors.Errorf("failed to fill cache: %w", err) + lk := ci.fillCacheLock.GetLock(cur) + lk.Lock() // if entry is missing, take the lock + lbe, ok = ci.indexCache.Load(cur) // check if someone else added it while we waited for lock + if !ok { + fc, err := ci.fillCache(ctx, cur) + if err != nil { + lk.Unlock() + return nil, xerrors.Errorf("failed to fill cache: %w", err) + } + lbe = fc + ci.indexCache.Store(cur, lbe) } - lbe = fc + lk.Unlock() } if to == lbe.targetHeight { @@ -137,7 +152,6 @@ func (ci *ChainIndex) fillCache(ctx context.Context, tsk types.TipSetKey) (*lbEn targetHeight: skipTarget.Height(), target: skipTarget.Key(), } - ci.indexCache[tsk] = lbe return lbe, nil } diff --git a/chain/store/store.go b/chain/store/store.go index fd12b88a5..88103ac48 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -1161,6 +1161,10 @@ func (cs *ChainStore) TryFillTipSet(ctx context.Context, ts *types.TipSet) (*Ful // selects the tipset before the null round if true, and the tipset following // the null round if false. func (cs *ChainStore) GetTipsetByHeight(ctx context.Context, h abi.ChainEpoch, ts *types.TipSet, prev bool) (*types.TipSet, error) { + if h < 0 { + return nil, xerrors.Errorf("height %d is negative", h) + } + if ts == nil { ts = cs.GetHeaviestTipSet() } diff --git a/chain/sub/incoming.go b/chain/sub/incoming.go index 533314a4f..64185c4cc 100644 --- a/chain/sub/incoming.go +++ b/chain/sub/incoming.go @@ -12,7 +12,7 @@ import ( bserv "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" - "github.com/ipni/storetheindex/announce/message" + "github.com/ipni/go-libipni/announce/message" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/connmgr" "github.com/libp2p/go-libp2p/core/peer" @@ -358,6 +358,8 @@ func (mv *MessageValidator) Validate(ctx context.Context, pid peer.ID, msg *pubs fallthrough case xerrors.Is(err, messagepool.ErrNonceGap): fallthrough + case xerrors.Is(err, messagepool.ErrGasFeeCapTooLow): + fallthrough case xerrors.Is(err, messagepool.ErrNonceTooLow): return pubsub.ValidationIgnore default: diff --git a/chain/sub/incoming_test.go b/chain/sub/incoming_test.go index 0a9504a88..f54e09049 100644 --- a/chain/sub/incoming_test.go +++ b/chain/sub/incoming_test.go @@ -9,7 +9,7 @@ import ( "github.com/golang/mock/gomock" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - "github.com/ipni/storetheindex/announce/message" + "github.com/ipni/go-libipni/announce/message" pubsub "github.com/libp2p/go-libp2p-pubsub" pb "github.com/libp2p/go-libp2p-pubsub/pb" "github.com/libp2p/go-libp2p/core/peer" diff --git a/chain/types/blockmsg.go b/chain/types/blockmsg.go index f3114499d..f8f0a08db 100644 --- a/chain/types/blockmsg.go +++ b/chain/types/blockmsg.go @@ -2,6 +2,7 @@ package types import ( "bytes" + "fmt" "github.com/ipfs/go-cid" ) @@ -14,10 +15,13 @@ type BlockMsg struct { func DecodeBlockMsg(b []byte) (*BlockMsg, error) { var bm BlockMsg - if err := bm.UnmarshalCBOR(bytes.NewReader(b)); err != nil { + data := bytes.NewReader(b) + if err := bm.UnmarshalCBOR(data); err != nil { return nil, err } - + if l := data.Len(); l != 0 { + return nil, fmt.Errorf("extraneous data in BlockMsg CBOR encoding: got %d unexpected bytes", l) + } return &bm, nil } diff --git a/chain/types/blockmsg_test.go b/chain/types/blockmsg_test.go new file mode 100644 index 000000000..02a622768 --- /dev/null +++ b/chain/types/blockmsg_test.go @@ -0,0 +1,40 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecodeBlockMsg(t *testing.T) { + type args struct { + b []byte + } + tests := []struct { + name string + data []byte + want *BlockMsg + wantErr bool + }{ + {"decode empty BlockMsg with extra data at the end", []byte{0x83, 0xf6, 0x80, 0x80, 0x20}, nil, true}, + {"decode valid empty BlockMsg", []byte{0x83, 0xf6, 0x80, 0x80}, new(BlockMsg), false}, + {"decode invalid cbor", []byte{0x83, 0xf6, 0x80}, nil, true}, + } + for _, tt := range tests { + data := tt.data + want := tt.want + wantErr := tt.wantErr + t.Run(tt.name, func(t *testing.T) { + got, err := DecodeBlockMsg(data) + if wantErr { + assert.Errorf(t, err, "DecodeBlockMsg(%x)", data) + return + } + assert.NoErrorf(t, err, "DecodeBlockMsg(%x)", data) + assert.Equalf(t, want, got, "DecodeBlockMsg(%x)", data) + serialized, err := got.Serialize() + assert.NoErrorf(t, err, "DecodeBlockMsg(%x)", data) + assert.Equalf(t, serialized, data, "DecodeBlockMsg(%x)", data) + }) + } +} diff --git a/cli/info.go b/cli/info.go index 007e3655f..8b36be488 100644 --- a/cli/info.go +++ b/cli/info.go @@ -20,6 +20,7 @@ import ( "github.com/filecoin-project/lotus/api/v1api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/journal/alerting" ) var infoCmd = &cli.Command{ @@ -62,6 +63,21 @@ func infoCmdAct(cctx *cli.Context) error { fmt.Printf(" [epoch %s]\n", color.MagentaString(("%d"), status.SyncStatus.Epoch)) fmt.Printf("Peers to: [publish messages %d] [publish blocks %d]\n", status.PeerStatus.PeersToPublishMsgs, status.PeerStatus.PeersToPublishBlocks) + alerts, err := fullapi.LogAlerts(ctx) + if err != nil { + fmt.Printf("ERROR: getting alerts: %s\n", err) + } + + activeAlerts := make([]alerting.Alert, 0) + for _, alert := range alerts { + if alert.Active { + activeAlerts = append(activeAlerts, alert) + } + } + if len(activeAlerts) > 0 { + fmt.Printf("%s (check %s)\n", color.RedString("⚠ %d Active alerts", len(activeAlerts)), color.YellowString("lotus log alerts")) + } + //Chain health calculated as percentage: amount of blocks in last finality / very healthy amount of blocks in a finality (900 epochs * 5 blocks per tipset) health := (100 * (900 * status.ChainStatus.BlocksPerTipsetLastFinality) / (900 * 5)) switch { diff --git a/cli/state.go b/cli/state.go index 9031ba870..2750ca680 100644 --- a/cli/state.go +++ b/cli/state.go @@ -1528,6 +1528,9 @@ func printMsg(ctx context.Context, api v0api.FullNode, msg cid.Cid, mw *lapi.Msg if err := printReceiptReturn(ctx, api, m, mw.Receipt); err != nil { return err } + if mw.Receipt.EventsRoot != nil { + fmt.Printf("Events Root: %s\n", mw.Receipt.EventsRoot) + } return nil } diff --git a/cmd/lotus-fountain/main.go b/cmd/lotus-fountain/main.go index 780aef916..f6d503c2f 100644 --- a/cmd/lotus-fountain/main.go +++ b/cmd/lotus-fountain/main.go @@ -7,6 +7,7 @@ import ( "net" "net/http" "os" + "strings" "time" rice "github.com/GeertJohan/go.rice" @@ -15,10 +16,14 @@ import ( "golang.org/x/xerrors" "github.com/filecoin-project/go-address" + verifregtypes9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" "github.com/filecoin-project/lotus/api/v0api" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" lcli "github.com/filecoin-project/lotus/cli" ) @@ -70,6 +75,11 @@ var runCmd = &cli.Command{ EnvVars: []string{"LOTUS_FOUNTAIN_AMOUNT"}, Value: "50", }, + &cli.Uint64Flag{ + Name: "data-cap", + EnvVars: []string{"LOTUS_DATACAP_AMOUNT"}, + Value: verifregtypes9.MinVerifiedDealSize.Uint64(), + }, &cli.Float64Flag{ Name: "captcha-threshold", Value: 0.5, @@ -108,6 +118,7 @@ var runCmd = &cli.Command{ ctx: ctx, api: nodeApi, from: from, + allowance: types.NewInt(cctx.Uint64("data-cap")), sendPerRequest: sendPerRequest, limiter: NewLimiter(LimiterConfig{ TotalRate: 500 * time.Millisecond, @@ -124,6 +135,8 @@ var runCmd = &cli.Command{ http.Handle("/", http.FileServer(box.HTTPBox())) http.HandleFunc("/funds.html", prepFundsHtml(box)) http.Handle("/send", h) + http.HandleFunc("/datacap.html", prepDataCapHtml(box)) + http.Handle("/datacap", h) fmt.Printf("Open http://%s\n", cctx.String("front")) go func() { @@ -156,12 +169,24 @@ func prepFundsHtml(box *rice.Box) http.HandlerFunc { } } +func prepDataCapHtml(box *rice.Box) http.HandlerFunc { + tmpl := template.Must(template.New("datacaps").Parse(box.MustString("datacap.html"))) + return func(w http.ResponseWriter, r *http.Request) { + err := tmpl.Execute(w, os.Getenv("RECAPTCHA_SITE_KEY")) + if err != nil { + http.Error(w, err.Error(), http.StatusBadGateway) + return + } + } +} + type handler struct { ctx context.Context api v0api.FullNode from address.Address sendPerRequest types.FIL + allowance types.BigInt limiter *Limiter recapThreshold float64 @@ -187,24 +212,41 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusBadGateway) return } + if !capResp.Success || capResp.Score < h.recapThreshold { log.Infow("spam", "capResp", capResp) http.Error(w, "spam protection", http.StatusUnprocessableEntity) return } - to, err := address.NewFromString(r.FormValue("address")) - if err != nil { + addressInput := r.FormValue("address") + + var filecoinAddress address.Address + var decodeError error + + if strings.HasPrefix(addressInput, "0x") { + ethAddress, err := ethtypes.ParseEthAddress(addressInput) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + filecoinAddress, decodeError = ethAddress.ToFilecoinAddress() + } else { + filecoinAddress, decodeError = address.NewFromString(addressInput) + } + + if decodeError != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } - if to == address.Undef { + if filecoinAddress == address.Undef { http.Error(w, "empty address", http.StatusBadRequest) return } // Limit based on wallet address - limiter := h.limiter.GetWalletLimiter(to.String()) + limiter := h.limiter.GetWalletLimiter(filecoinAddress.String()) if !limiter.Allow() { http.Error(w, http.StatusText(http.StatusTooManyRequests)+": wallet limit", http.StatusTooManyRequests) return @@ -227,11 +269,37 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - smsg, err := h.api.MpoolPushMessage(h.ctx, &types.Message{ - Value: types.BigInt(h.sendPerRequest), - From: h.from, - To: to, - }, nil) + var smsg *types.SignedMessage + if r.RequestURI == "/send" { + smsg, err = h.api.MpoolPushMessage( + h.ctx, &types.Message{ + Value: types.BigInt(h.sendPerRequest), + From: h.from, + To: filecoinAddress, + }, nil) + } else if r.RequestURI == "/datacap" { + var params []byte + params, err = actors.SerializeParams( + &verifregtypes9.AddVerifiedClientParams{ + Address: filecoinAddress, + Allowance: h.allowance, + }) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + smsg, err = h.api.MpoolPushMessage( + h.ctx, &types.Message{ + Params: params, + From: h.from, + To: verifreg.Address, + Method: verifreg.Methods.AddVerifiedClient, + }, nil) + } else { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return diff --git a/cmd/lotus-fountain/site/datacap.html b/cmd/lotus-fountain/site/datacap.html new file mode 100644 index 000000000..7434b8e48 --- /dev/null +++ b/cmd/lotus-fountain/site/datacap.html @@ -0,0 +1,41 @@ + + + + Grant DataCap - Lotus Fountain + + + + + + +
+
+
+

Grant datacap

+

Please input your address to receive a data cap on the Calibration Testnet.

+
+
+
+ Enter destination address: + + +
+
+
+ + +
+ + diff --git a/cmd/lotus-fountain/site/funds.html b/cmd/lotus-fountain/site/funds.html index ae7a4041b..a09d73964 100644 --- a/cmd/lotus-fountain/site/funds.html +++ b/cmd/lotus-fountain/site/funds.html @@ -15,12 +15,13 @@
- SENDING FUNDS +

Send funds

+

Please input your address to receive test FIL (tFIL) on the Calibration Testnet. This faucet dispenses 100 tFIL.

Enter destination address: - +
+
+ LOTUS DEVNET GRANT DATACAP +
+