-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat(consensus): tipset gas reservations and reservation‑aware mpool pre‑pack #13427
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
snissn
wants to merge
10
commits into
master
Choose a base branch
from
multistage-execution
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,755
−3
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
9cf6fb1
feat(consensus): add tipset gas reservations orchestration
snissn ed000a4
feat(mempool): add reservation-aware pre-pack simulation
snissn ae14e03
go fmt
snissn 3751590
Update filecoin-ffi submodule for reservation FFI
snissn beab3fb
feat: implement strict sender partitioning for tipset reservations
snissn 679330f
chore: update filecoin-ffi submodule with fvm patches
snissn dff1be9
Merge branch 'master' into multistage-execution
snissn f801053
Merge remote-tracking branch 'origin/master' into multistage-execution
b51d5f2
chore: gofmt reservations and compute_state
5cd2c8b
chore: add ref-fvm submodule for local FVM patches
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,3 +7,7 @@ | |
| [submodule "extern/test-vectors"] | ||
| path = extern/test-vectors | ||
| url = https://github.com/filecoin-project/test-vectors.git | ||
| [submodule "extern/ref-fvm"] | ||
| path = extern/ref-fvm | ||
| url = [email protected]:filecoin-project/ref-fvm.git | ||
| branch = multistage-execution | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package consensus | ||
|
|
||
| import "os" | ||
|
|
||
| // ReservationFeatureFlags holds feature toggles for tipset gas reservations. | ||
| // | ||
| // These flags are evaluated by consensus and the message pool when deciding | ||
| // whether to attempt tipset‑scope reservations pre‑activation, and how to | ||
| // interpret Begin/End reservation errors. | ||
| type ReservationFeatureFlags struct { | ||
| // MultiStageReservations enables tipset‑scope gas reservations | ||
| // pre‑activation. When false, ReservationsEnabled returns false for | ||
| // network versions before ReservationsActivationNetworkVersion and Lotus | ||
| // operates in legacy mode (no Begin/End calls). | ||
| // | ||
| // At or after activation, reservations are always enabled regardless of | ||
| // this flag. | ||
| MultiStageReservations bool | ||
|
|
||
| // MultiStageReservationsStrict controls how pre‑activation reservation | ||
| // failures are handled when MultiStageReservations is true: | ||
| // | ||
| // - When false (non‑strict), non‑NotImplemented Begin/End errors such | ||
| // as ErrReservationsInsufficientFunds and ErrReservationsPlanTooLarge | ||
| // are treated as best‑effort: Lotus logs and falls back to legacy | ||
| // mode for that tipset. | ||
| // - When true (strict), those reservation failures invalidate the | ||
| // tipset pre‑activation. Node‑error classes (e.g. overflow or | ||
| // invariant violations) always surface as errors regardless of this | ||
| // flag. | ||
| MultiStageReservationsStrict bool | ||
| } | ||
|
|
||
| // Feature exposes the current reservation feature flags. | ||
| // | ||
| // Defaults: | ||
| // - MultiStageReservations: enabled when LOTUS_ENABLE_TIPSET_RESERVATIONS=1. | ||
| // - MultiStageReservationsStrict: enabled when | ||
| // LOTUS_ENABLE_TIPSET_RESERVATIONS_STRICT=1. | ||
| // | ||
| // These defaults preserve the existing environment‑based gating while making | ||
| // the flags explicit and testable. | ||
| var Feature = ReservationFeatureFlags{ | ||
| MultiStageReservations: os.Getenv("LOTUS_ENABLE_TIPSET_RESERVATIONS") == "1", | ||
| MultiStageReservationsStrict: os.Getenv("LOTUS_ENABLE_TIPSET_RESERVATIONS_STRICT") == "1", | ||
| } | ||
|
|
||
| // SetFeatures overrides the global reservation feature flags. This is intended | ||
| // for wiring from higher‑level configuration and for tests; callers should | ||
| // treat it as process‑wide and set it once during initialization. | ||
| func SetFeatures(flags ReservationFeatureFlags) { | ||
| Feature = flags | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,180 @@ | ||
| package consensus | ||
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
|
|
||
| "go.opencensus.io/stats" | ||
|
|
||
| "github.com/filecoin-project/go-address" | ||
| "github.com/filecoin-project/go-state-types/abi" | ||
| "github.com/filecoin-project/go-state-types/big" | ||
| "github.com/filecoin-project/go-state-types/network" | ||
| cid "github.com/ipfs/go-cid" | ||
| logging "github.com/ipfs/go-log/v2" | ||
|
|
||
| "github.com/filecoin-project/lotus/chain/types" | ||
| "github.com/filecoin-project/lotus/chain/vm" | ||
| "github.com/filecoin-project/lotus/metrics" | ||
| ) | ||
|
|
||
| var rlog = logging.Logger("reservations") | ||
|
|
||
| // ReservationsEnabled returns true when tipset reservations should be attempted. | ||
| // Before the reservations activation network version, this helper consults the | ||
| // MultiStageReservations feature flag. At or after the activation network | ||
| // version, reservations are always enabled and become consensus-critical. | ||
| func ReservationsEnabled(nv network.Version) bool { | ||
| // After activation, reservations are required regardless of feature flags. | ||
| if nv >= vm.ReservationsActivationNetworkVersion() { | ||
| return true | ||
| } | ||
|
|
||
| // Pre-activation: best-effort mode controlled by the feature flag. | ||
| return Feature.MultiStageReservations | ||
| } | ||
|
|
||
| // buildReservationPlan aggregates per-sender gas reservations across the full | ||
| // tipset. The amount reserved for each message is gas_limit * gas_fee_cap. | ||
| // | ||
| // Strict Sender Partitioning: | ||
| // If a sender has already been seen in a previous block (higher precedence), | ||
| // their messages in subsequent blocks are strictly ignored for reservation | ||
| // purposes. This aligns with the execution logic in ApplyBlocks and prevents | ||
| // valid individual blocks from forming an invalid tipset due to aggregate | ||
| // over-reservation. | ||
| func buildReservationPlan(bms []FilecoinBlockMessages) map[address.Address]abi.TokenAmount { | ||
| plan := make(map[address.Address]abi.TokenAmount) | ||
| seenCIDs := make(map[cid.Cid]struct{}) | ||
| seenSenders := make(map[address.Address]struct{}) | ||
|
|
||
| for _, b := range bms { | ||
| currentBlockSenders := make(map[address.Address]struct{}) | ||
|
|
||
| // canonical order is preserved in the combined slices append below | ||
| for _, cm := range append(b.BlsMessages, b.SecpkMessages...) { | ||
| m := cm.VMMessage() | ||
| mcid := m.Cid() | ||
|
|
||
| // 1. Deduplicate by CID (legacy rule) | ||
| if _, ok := seenCIDs[mcid]; ok { | ||
| continue | ||
| } | ||
|
|
||
| // 2. Strict Sender Partitioning | ||
| // If this sender was seen in a previous block, ignore this message. | ||
| // We do NOT check currentBlockSenders here because multiple messages | ||
| // from the same sender in the *same* block are allowed. | ||
| if _, ok := seenSenders[m.From]; ok { | ||
| continue | ||
| } | ||
|
|
||
| seenCIDs[mcid] = struct{}{} | ||
| currentBlockSenders[m.From] = struct{}{} | ||
|
|
||
| // Only explicit messages are included in blocks; implicit messages are applied separately. | ||
| cost := types.BigMul(types.NewInt(uint64(m.GasLimit)), m.GasFeeCap) | ||
| if prev, ok := plan[m.From]; ok { | ||
| plan[m.From] = types.BigAdd(prev, cost) | ||
| } else { | ||
| plan[m.From] = cost | ||
| } | ||
| } | ||
|
|
||
| // After finishing the block, mark all its senders as seen for future blocks. | ||
| for s := range currentBlockSenders { | ||
| seenSenders[s] = struct{}{} | ||
| } | ||
| } | ||
| return plan | ||
| } | ||
|
|
||
| // startReservations is a helper that starts a reservation session on the VM if enabled. | ||
| // If the computed plan is empty (no explicit messages), Begin is skipped entirely. | ||
| func startReservations(ctx context.Context, vmi vm.Interface, bms []FilecoinBlockMessages, nv network.Version) error { | ||
| if !ReservationsEnabled(nv) { | ||
| return nil | ||
| } | ||
|
|
||
| plan := buildReservationPlan(bms) | ||
| if len(plan) == 0 { | ||
| rlog.Debugw("skipping tipset reservations for empty plan") | ||
| return nil | ||
| } | ||
|
|
||
| total := abi.NewTokenAmount(0) | ||
| for _, amt := range plan { | ||
| total = big.Add(total, amt) | ||
| } | ||
|
|
||
| stats.Record(ctx, | ||
| metrics.ReservationPlanSenders.M(int64(len(plan))), | ||
| metrics.ReservationPlanTotal.M(total.Int64()), | ||
| ) | ||
|
|
||
| rlog.Infow("starting tipset reservations", "senders", len(plan), "total", total) | ||
| if err := vmi.StartTipsetReservations(ctx, plan); err != nil { | ||
| return handleReservationError("begin", err, nv) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // endReservations ends the active reservation session if enabled. | ||
| func endReservations(ctx context.Context, vmi vm.Interface, nv network.Version) error { | ||
| if !ReservationsEnabled(nv) { | ||
| return nil | ||
| } | ||
| if err := vmi.EndTipsetReservations(ctx); err != nil { | ||
| return handleReservationError("end", err, nv) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // handleReservationError interprets Begin/End reservation errors according to | ||
| // network version and feature flags, deciding whether to fall back to legacy | ||
| // mode (pre-activation, non-strict) or surface the error. | ||
| func handleReservationError(stage string, err error, nv network.Version) error { | ||
| if err == nil { | ||
| return nil | ||
| } | ||
|
|
||
| // Post-activation: reservations are consensus-critical; all Begin/End | ||
| // errors surface to the caller. ErrReservationsNotImplemented becomes a | ||
| // node error (engine too old) under active rules. | ||
| if nv >= vm.ReservationsActivationNetworkVersion() { | ||
| return err | ||
| } | ||
|
|
||
| // Pre-activation: ErrNotImplemented is always treated as a benign signal | ||
| // that the engine does not support reservations yet; fall back to legacy | ||
| // mode regardless of strictness. | ||
| if errors.Is(err, vm.ErrReservationsNotImplemented) { | ||
| rlog.Debugw("tipset reservations not implemented; continuing in legacy mode", | ||
| "stage", stage, "error", err) | ||
| return nil | ||
| } | ||
|
|
||
| // Node-error classes: always surface as errors, even pre-activation. | ||
| if errors.Is(err, vm.ErrReservationsSessionOpen) || | ||
| errors.Is(err, vm.ErrReservationsSessionClosed) || | ||
| errors.Is(err, vm.ErrReservationsNonZeroRemainder) || | ||
| errors.Is(err, vm.ErrReservationsOverflow) || | ||
| errors.Is(err, vm.ErrReservationsInvariantViolation) { | ||
| return err | ||
| } | ||
|
|
||
| // Reservation failures toggled by strict mode. When strict is disabled, | ||
| // treat these as best-effort pre-activation and fall back to legacy mode. | ||
| switch { | ||
| case errors.Is(err, vm.ErrReservationsInsufficientFunds), errors.Is(err, vm.ErrReservationsPlanTooLarge): | ||
| if Feature.MultiStageReservationsStrict { | ||
| return err | ||
| } | ||
| rlog.Debugw("tipset reservations failed pre-activation; continuing in legacy mode (non-strict)", | ||
| "stage", stage, "error", err) | ||
| return nil | ||
| default: | ||
| // Unknown errors pre-activation are treated as node errors. | ||
| return err | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The struct initialization formatting is inconsistent. The opening brace should be on the same line as the field list, or all fields should be on separate lines with proper indentation.