feat(config): move Fevm.Events->Events, implement soft deprecation

Closes: https://github.com/filecoin-project/lotus/issues/11679

* Introduce a `moved:"To.New.Config"` tag which prints a stderr warning when
  you use one of these, but will move any set value to the new location if the
	new location isn't already set itself.
* Look for `X is DEPRECATED` to hold certain fields back from documentation.
* Use `toml:"omitempty"` to prevent the default config output from having these
  deprecated values.
This commit is contained in:
Rod Vagg 2024-03-08 18:43:39 +11:00
parent b4e7374cd5
commit e5ccf1915d
11 changed files with 343 additions and 118 deletions

View File

@ -136,7 +136,7 @@ Additionally, Filecoin is not Ethereum no matter how much we try to provide API/
[FIP-0049](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0049.md) introduced _Actor Events_ that can be emitted by user programmed actors. [FIP-0083](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0083.md) introduces new events emitted by the builtin Verified Registry, Miner and Market Actors. These new events for builtin actors are being activated with network version 22 to coincide with _Direct Data Onboarding_ as defined in [FIP-0076](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0076.md) which introduces additional flexibility for data onboarding. Sector, Deal and DataCap lifecycles can be tracked with these events, providing visibility and options for programmatic responses to changes in state. [FIP-0049](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0049.md) introduced _Actor Events_ that can be emitted by user programmed actors. [FIP-0083](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0083.md) introduces new events emitted by the builtin Verified Registry, Miner and Market Actors. These new events for builtin actors are being activated with network version 22 to coincide with _Direct Data Onboarding_ as defined in [FIP-0076](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0076.md) which introduces additional flexibility for data onboarding. Sector, Deal and DataCap lifecycles can be tracked with these events, providing visibility and options for programmatic responses to changes in state.
Actor events are available on message receipts, but can now be retrieved from a node using the new `GetActorEvents` and `SubscribeActorEvents` methods. These methods allow for querying and subscribing to actor events, respectively. They depend on the Lotus node both collecting events (with `Fevm.Events.RealTimeFilterAPI` and `Fevm.Events.HistoricFilterAPI`) and being enabled with the new configuration option `Events.EnableActorEventsAPI`. Note that a Lotus node can only respond to requests for historic events that it retains in its event store. Actor events are available on message receipts, but can now be retrieved from a node using the new `GetActorEvents` and `SubscribeActorEvents` methods. These methods allow for querying and subscribing to actor events, respectively. They depend on the Lotus node both collecting events (with `Events.RealTimeFilterAPI` and `Events.HistoricFilterAPI`) and being enabled with the new configuration option `Events.EnableActorEventsAPI`. Note that a Lotus node can only respond to requests for historic events that it retains in its event store.
Both `GetActorEvents` and `SubscribeActorEvents` take a filter parameter which can optionally filter events on: Both `GetActorEvents` and `SubscribeActorEvents` take a filter parameter which can optionally filter events on:
@ -147,7 +147,14 @@ Both `GetActorEvents` and `SubscribeActorEvents` take a filter parameter which c
`GetActorEvents` provides a one-time query for actor events, while `SubscribeActorEvents` provides a long-lived connection (via websockets) to the Lotus node, allowing for real-time updates on actor events. The subscription can be cancelled by the client at any time. `GetActorEvents` provides a one-time query for actor events, while `SubscribeActorEvents` provides a long-lived connection (via websockets) to the Lotus node, allowing for real-time updates on actor events. The subscription can be cancelled by the client at any time.
### Events Configuration Changes
All configuration options previously under `Fevm.Events` are now in the top-level `Events` section along with the new `Events.EnableActorEventsAPI` option mentioned above. If you have non-default options in `[Events]` under `[Fevm]` in your configuration file, please move them to the top-level `[Events]`.
While `Fevm.Events.*` options are deprecated and replaced by `Events.*`, any existing custom values will be respected if their new form isn't set, but a warning will be printed to standard error upon startup. Support for these deprecated options will be removed in a future Lotus release, so please migrate your configuration promptly.
### GetAllClaims and GetAllAlocations ### GetAllClaims and GetAllAlocations
Additionally the methods `GetAllAllocations` and `GetAllClaims` has been added to the Lotus API. These methods lists all the available allocations and claims available in the actor state. Additionally the methods `GetAllAllocations` and `GetAllClaims` has been added to the Lotus API. These methods lists all the available allocations and claims available in the actor state.
### Lotus CLI ### Lotus CLI

View File

@ -337,68 +337,67 @@
# env var: LOTUS_FEVM_ETHTXHASHMAPPINGLIFETIMEDAYS # env var: LOTUS_FEVM_ETHTXHASHMAPPINGLIFETIMEDAYS
#EthTxHashMappingLifetimeDays = 0 #EthTxHashMappingLifetimeDays = 0
[Fevm.Events]
# DisableRealTimeFilterAPI will disable the RealTimeFilterAPI that can create and query filters for actor events as they are emitted.
# The API is enabled when EnableEthRPC or Events.EnableActorEventsAPI is true, but can be disabled selectively with this flag.
#
# type: bool
# env var: LOTUS_FEVM_EVENTS_DISABLEREALTIMEFILTERAPI
#DisableRealTimeFilterAPI = false
# DisableHistoricFilterAPI will disable the HistoricFilterAPI that can create and query filters for actor events
# that occurred in the past. HistoricFilterAPI maintains a queryable index of events.
# The API is enabled when EnableEthRPC or Events.EnableActorEventsAPI is true, but can be disabled selectively with this flag.
#
# type: bool
# env var: LOTUS_FEVM_EVENTS_DISABLEHISTORICFILTERAPI
#DisableHistoricFilterAPI = false
# FilterTTL specifies the time to live for actor event filters. Filters that haven't been accessed longer than
# this time become eligible for automatic deletion.
#
# type: Duration
# env var: LOTUS_FEVM_EVENTS_FILTERTTL
#FilterTTL = "24h0m0s"
# MaxFilters specifies the maximum number of filters that may exist at any one time.
#
# type: int
# env var: LOTUS_FEVM_EVENTS_MAXFILTERS
#MaxFilters = 100
# MaxFilterResults specifies the maximum number of results that can be accumulated by an actor event filter.
#
# type: int
# env var: LOTUS_FEVM_EVENTS_MAXFILTERRESULTS
#MaxFilterResults = 10000
# MaxFilterHeightRange specifies the maximum range of heights that can be used in a filter (to avoid querying
# the entire chain)
#
# type: uint64
# env var: LOTUS_FEVM_EVENTS_MAXFILTERHEIGHTRANGE
#MaxFilterHeightRange = 2880
# DatabasePath is the full path to a sqlite database that will be used to index actor events to
# support the historic filter APIs. If the database does not exist it will be created. The directory containing
# the database must already exist and be writeable. If a relative path is provided here, sqlite treats it as
# relative to the CWD (current working directory).
#
# type: string
# env var: LOTUS_FEVM_EVENTS_DATABASEPATH
#DatabasePath = ""
[Events] [Events]
# DisableRealTimeFilterAPI will disable the RealTimeFilterAPI that can create and query filters for actor events as they are emitted.
# The API is enabled when Fevm.EnableEthRPC or EnableActorEventsAPI is true, but can be disabled selectively with this flag.
#
# type: bool
# env var: LOTUS_EVENTS_DISABLEREALTIMEFILTERAPI
#DisableRealTimeFilterAPI = false
# DisableHistoricFilterAPI will disable the HistoricFilterAPI that can create and query filters for actor events
# that occurred in the past. HistoricFilterAPI maintains a queryable index of events.
# The API is enabled when Fevm.EnableEthRPC or EnableActorEventsAPI is true, but can be disabled selectively with this flag.
#
# type: bool
# env var: LOTUS_EVENTS_DISABLEHISTORICFILTERAPI
#DisableHistoricFilterAPI = false
# EnableActorEventsAPI enables the Actor events API that enables clients to consume events # EnableActorEventsAPI enables the Actor events API that enables clients to consume events
# emitted by (smart contracts + built-in Actors). # emitted by (smart contracts + built-in Actors).
# This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be # This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be
# disabled by setting their respective Disable* options in Fevm.Events. # disabled by setting their respective Disable* options.
# #
# type: bool # type: bool
# env var: LOTUS_EVENTS_ENABLEACTOREVENTSAPI # env var: LOTUS_EVENTS_ENABLEACTOREVENTSAPI
#EnableActorEventsAPI = false #EnableActorEventsAPI = false
# FilterTTL specifies the time to live for actor event filters. Filters that haven't been accessed longer than
# this time become eligible for automatic deletion.
#
# type: Duration
# env var: LOTUS_EVENTS_FILTERTTL
#FilterTTL = "24h0m0s"
# MaxFilters specifies the maximum number of filters that may exist at any one time.
#
# type: int
# env var: LOTUS_EVENTS_MAXFILTERS
#MaxFilters = 100
# MaxFilterResults specifies the maximum number of results that can be accumulated by an actor event filter.
#
# type: int
# env var: LOTUS_EVENTS_MAXFILTERRESULTS
#MaxFilterResults = 10000
# MaxFilterHeightRange specifies the maximum range of heights that can be used in a filter (to avoid querying
# the entire chain)
#
# type: uint64
# env var: LOTUS_EVENTS_MAXFILTERHEIGHTRANGE
#MaxFilterHeightRange = 2880
# DatabasePath is the full path to a sqlite database that will be used to index actor events to
# support the historic filter APIs. If the database does not exist it will be created. The directory containing
# the database must already exist and be writeable. If a relative path is provided here, sqlite treats it as
# relative to the CWD (current working directory).
#
# type: string
# env var: LOTUS_EVENTS_DATABASEPATH
#DatabasePath = ""
[Index] [Index]
# EXPERIMENTAL FEATURE. USE WITH CAUTION # EXPERIMENTAL FEATURE. USE WITH CAUTION

View File

@ -65,7 +65,7 @@ var DefaultNodeOpts = nodeOpts{
// test defaults // test defaults
cfg.Fevm.EnableEthRPC = true cfg.Fevm.EnableEthRPC = true
cfg.Fevm.Events.MaxFilterHeightRange = math.MaxInt64 cfg.Events.MaxFilterHeightRange = math.MaxInt64
cfg.Events.EnableActorEventsAPI = true cfg.Events.EnableActorEventsAPI = true
return nil return nil
}, },

View File

@ -266,13 +266,13 @@ func ConfigFullNode(c interface{}) Option {
// Actor event filtering support // Actor event filtering support
Override(new(events.EventHelperAPI), From(new(modules.EventHelperAPI))), Override(new(events.EventHelperAPI), From(new(modules.EventHelperAPI))),
Override(new(*filter.EventFilterManager), modules.EventFilterManager(cfg.Fevm)), Override(new(*filter.EventFilterManager), modules.EventFilterManager(cfg.Events)),
// in lite-mode Eth api is provided by gateway // in lite-mode Eth api is provided by gateway
ApplyIf(isFullNode, ApplyIf(isFullNode,
If(cfg.Fevm.EnableEthRPC, If(cfg.Fevm.EnableEthRPC,
Override(new(full.EthModuleAPI), modules.EthModuleAPI(cfg.Fevm)), Override(new(full.EthModuleAPI), modules.EthModuleAPI(cfg.Fevm)),
Override(new(full.EthEventAPI), modules.EthEventHandler(cfg.Fevm)), Override(new(full.EthEventAPI), modules.EthEventHandler(cfg.Events, cfg.Fevm.EnableEthRPC)),
), ),
If(!cfg.Fevm.EnableEthRPC, If(!cfg.Fevm.EnableEthRPC,
Override(new(full.EthModuleAPI), &full.EthModuleDummy{}), Override(new(full.EthModuleAPI), &full.EthModuleDummy{}),
@ -282,7 +282,7 @@ func ConfigFullNode(c interface{}) Option {
ApplyIf(isFullNode, ApplyIf(isFullNode,
If(cfg.Events.EnableActorEventsAPI, If(cfg.Events.EnableActorEventsAPI,
Override(new(full.ActorEventAPI), modules.ActorEventHandler(cfg.Events.EnableActorEventsAPI, cfg.Fevm)), Override(new(full.ActorEventAPI), modules.ActorEventHandler(cfg.Events)),
), ),
If(!cfg.Events.EnableActorEventsAPI, If(!cfg.Events.EnableActorEventsAPI,
Override(new(full.ActorEventAPI), &full.ActorEventDummy{}), Override(new(full.ActorEventAPI), &full.ActorEventDummy{}),

View File

@ -74,6 +74,11 @@ func run() error {
name := f[0] name := f[0]
typ := f[1] typ := f[1]
if len(comment) > 0 && strings.HasPrefix(comment[0], fmt.Sprintf("%s is DEPRECATED", name)) {
// don't document deprecated fields
continue
}
out[currentType] = append(out[currentType], field{ out[currentType] = append(out[currentType], field{
Name: name, Name: name,
Type: typ, Type: typ,

View File

@ -110,17 +110,15 @@ func DefaultFullNode() *FullNode {
Fevm: FevmConfig{ Fevm: FevmConfig{
EnableEthRPC: false, EnableEthRPC: false,
EthTxHashMappingLifetimeDays: 0, EthTxHashMappingLifetimeDays: 0,
Events: Events{
DisableRealTimeFilterAPI: false,
DisableHistoricFilterAPI: false,
FilterTTL: Duration(time.Hour * 24),
MaxFilters: 100,
MaxFilterResults: 10000,
MaxFilterHeightRange: 2880, // conservative limit of one day
},
}, },
Events: EventsConfig{ Events: EventsConfig{
EnableActorEventsAPI: false, DisableRealTimeFilterAPI: false,
DisableHistoricFilterAPI: false,
EnableActorEventsAPI: false,
FilterTTL: Duration(time.Hour * 24),
MaxFilters: 100,
MaxFilterResults: 10000,
MaxFilterHeightRange: 2880, // conservative limit of one day
}, },
} }
} }

View File

@ -357,13 +357,13 @@ see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#
Comment: ``, Comment: ``,
}, },
}, },
"Events": { "EventsConfig": {
{ {
Name: "DisableRealTimeFilterAPI", Name: "DisableRealTimeFilterAPI",
Type: "bool", Type: "bool",
Comment: `DisableRealTimeFilterAPI will disable the RealTimeFilterAPI that can create and query filters for actor events as they are emitted. Comment: `DisableRealTimeFilterAPI will disable the RealTimeFilterAPI that can create and query filters for actor events as they are emitted.
The API is enabled when EnableEthRPC or Events.EnableActorEventsAPI is true, but can be disabled selectively with this flag.`, The API is enabled when Fevm.EnableEthRPC or EnableActorEventsAPI is true, but can be disabled selectively with this flag.`,
}, },
{ {
Name: "DisableHistoricFilterAPI", Name: "DisableHistoricFilterAPI",
@ -371,7 +371,16 @@ The API is enabled when EnableEthRPC or Events.EnableActorEventsAPI is true, but
Comment: `DisableHistoricFilterAPI will disable the HistoricFilterAPI that can create and query filters for actor events Comment: `DisableHistoricFilterAPI will disable the HistoricFilterAPI that can create and query filters for actor events
that occurred in the past. HistoricFilterAPI maintains a queryable index of events. that occurred in the past. HistoricFilterAPI maintains a queryable index of events.
The API is enabled when EnableEthRPC or Events.EnableActorEventsAPI is true, but can be disabled selectively with this flag.`, The API is enabled when Fevm.EnableEthRPC or EnableActorEventsAPI is true, but can be disabled selectively with this flag.`,
},
{
Name: "EnableActorEventsAPI",
Type: "bool",
Comment: `EnableActorEventsAPI enables the Actor events API that enables clients to consume events
emitted by (smart contracts + built-in Actors).
This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be
disabled by setting their respective Disable* options.`,
}, },
{ {
Name: "FilterTTL", Name: "FilterTTL",
@ -409,17 +418,6 @@ the database must already exist and be writeable. If a relative path is provided
relative to the CWD (current working directory).`, relative to the CWD (current working directory).`,
}, },
}, },
"EventsConfig": {
{
Name: "EnableActorEventsAPI",
Type: "bool",
Comment: `EnableActorEventsAPI enables the Actor events API that enables clients to consume events
emitted by (smart contracts + built-in Actors).
This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be
disabled by setting their respective Disable* options in Fevm.Events.`,
},
},
"FaultReporterConfig": { "FaultReporterConfig": {
{ {
Name: "EnableConsensusFaultReporter", Name: "EnableConsensusFaultReporter",
@ -474,7 +472,7 @@ Set to 0 to keep all mappings`,
}, },
{ {
Name: "Events", Name: "Events",
Type: "Events", Type: "DeprecatedEvents",
Comment: ``, Comment: ``,
}, },

View File

@ -18,12 +18,9 @@ import (
// FromFile loads config from a specified file overriding defaults specified in // FromFile loads config from a specified file overriding defaults specified in
// the def parameter. If file does not exist or is empty defaults are assumed. // the def parameter. If file does not exist or is empty defaults are assumed.
func FromFile(path string, opts ...LoadCfgOpt) (interface{}, error) { func FromFile(path string, opts ...LoadCfgOpt) (interface{}, error) {
var loadOpts cfgLoadOpts loadOpts, err := applyOpts(opts...)
var err error if err != nil {
for _, opt := range opts { return nil, err
if err = opt(&loadOpts); err != nil {
return nil, xerrors.Errorf("failed to apply load cfg option: %w", err)
}
} }
var def interface{} var def interface{}
if loadOpts.defaultCfg != nil { if loadOpts.defaultCfg != nil {
@ -56,16 +53,43 @@ func FromFile(path string, opts ...LoadCfgOpt) (interface{}, error) {
return nil, xerrors.Errorf("config failed validation: %w", err) return nil, xerrors.Errorf("config failed validation: %w", err)
} }
} }
return FromReader(buf, def) return FromReader(buf, def, opts...)
} }
// FromReader loads config from a reader instance. // FromReader loads config from a reader instance.
func FromReader(reader io.Reader, def interface{}) (interface{}, error) { func FromReader(reader io.Reader, def interface{}, opts ...LoadCfgOpt) (interface{}, error) {
cfg := def loadOpts, err := applyOpts(opts...)
_, err := toml.NewDecoder(reader).Decode(cfg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg := def
md, err := toml.NewDecoder(reader).Decode(cfg)
if err != nil {
return nil, err
}
// find any fields with a tag: `moved:"New.Config.Location"` and move any set values there over to
// the new location if they are not already set there.
movedFields := findMovedFields(nil, cfg)
var warningOut io.Writer = os.Stderr
if loadOpts.warningWriter != nil {
warningOut = loadOpts.warningWriter
}
for _, d := range movedFields {
if md.IsDefined(d.Field...) {
fmt.Fprintf(
warningOut,
"WARNING: Use of deprecated configuration option '%s' will be removed in a future release, use '%s' instead\n",
strings.Join(d.Field, "."),
strings.Join(d.NewField, "."))
if !md.IsDefined(d.NewField...) {
// new value isn't set but old is, we should move what the user set there
if err := moveFieldValue(cfg, d.Field, d.NewField); err != nil {
return nil, fmt.Errorf("failed to move field value: %w", err)
}
}
}
}
err = envconfig.Process("LOTUS", cfg) err = envconfig.Process("LOTUS", cfg)
if err != nil { if err != nil {
@ -75,14 +99,105 @@ func FromReader(reader io.Reader, def interface{}) (interface{}, error) {
return cfg, nil return cfg, nil
} }
// move a value from the location in the valPtr struct specified by oldPath, to the location
// specified by newPath; where the path is an array of nested field names.
func moveFieldValue(valPtr interface{}, oldPath []string, newPath []string) error {
oldValue, err := getFieldValue(valPtr, oldPath)
if err != nil {
return err
}
val := reflect.ValueOf(valPtr).Elem()
for {
field := val.FieldByName(newPath[0])
if !field.IsValid() {
return fmt.Errorf("unexpected error fetching field value")
}
if len(newPath) == 1 {
if field.Kind() != oldValue.Kind() {
return fmt.Errorf("unexpected error, old kind != new kind")
}
// set field on val to be the new one, and we're done
field.Set(oldValue)
return nil
}
if field.Kind() != reflect.Struct {
return fmt.Errorf("unexpected error fetching field value, is not a struct")
}
newPath = newPath[1:]
val = field
}
}
// recursively iterate into `path` to find the terminal value
func getFieldValue(val interface{}, path []string) (reflect.Value, error) {
if reflect.ValueOf(val).Kind() == reflect.Ptr {
val = reflect.ValueOf(val).Elem().Interface()
}
field := reflect.ValueOf(val).FieldByName(path[0])
if !field.IsValid() {
return reflect.Value{}, fmt.Errorf("unexpected error fetching field value")
}
if len(path) > 1 {
if field.Kind() != reflect.Struct {
return reflect.Value{}, fmt.Errorf("unexpected error fetching field value, is not a struct")
}
return getFieldValue(field.Interface(), path[1:])
}
return field, nil
}
type movedField struct {
Field []string
NewField []string
}
// inspect the fields recursively within a struct and find any with "moved" tags
func findMovedFields(path []string, val interface{}) []movedField {
dep := make([]movedField, 0)
if reflect.ValueOf(val).Kind() == reflect.Ptr {
val = reflect.ValueOf(val).Elem().Interface()
}
t := reflect.TypeOf(val)
if t.Kind() != reflect.Struct {
return nil
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
// could also do a "deprecated" in here
if idx := field.Tag.Get("moved"); idx != "" && idx != "-" {
dep = append(dep, movedField{
Field: append(path, field.Name),
NewField: strings.Split(idx, "."),
})
}
if field.Type.Kind() == reflect.Struct && reflect.ValueOf(val).FieldByName(field.Name).IsValid() {
deps := findMovedFields(append(path, field.Name), reflect.ValueOf(val).FieldByName(field.Name).Interface())
dep = append(dep, deps...)
}
}
return dep
}
type cfgLoadOpts struct { type cfgLoadOpts struct {
defaultCfg func() (interface{}, error) defaultCfg func() (interface{}, error)
canFallbackOnDefault func() error canFallbackOnDefault func() error
validate func(string) error validate func(string) error
warningWriter io.Writer
} }
type LoadCfgOpt func(opts *cfgLoadOpts) error type LoadCfgOpt func(opts *cfgLoadOpts) error
func applyOpts(opts ...LoadCfgOpt) (cfgLoadOpts, error) {
var loadOpts cfgLoadOpts
var err error
for _, opt := range opts {
if err = opt(&loadOpts); err != nil {
return loadOpts, fmt.Errorf("failed to apply load cfg option: %w", err)
}
}
return loadOpts, nil
}
func SetDefault(f func() (interface{}, error)) LoadCfgOpt { func SetDefault(f func() (interface{}, error)) LoadCfgOpt {
return func(opts *cfgLoadOpts) error { return func(opts *cfgLoadOpts) error {
opts.defaultCfg = f opts.defaultCfg = f
@ -104,6 +219,13 @@ func SetValidate(f func(string) error) LoadCfgOpt {
} }
} }
func SetWarningWriter(w io.Writer) LoadCfgOpt {
return func(opts *cfgLoadOpts) error {
opts.warningWriter = w
return nil
}
}
func NoDefaultForSplitstoreTransition() error { func NoDefaultForSplitstoreTransition() error {
return xerrors.Errorf("FullNode config not found and fallback to default disallowed while we transition to splitstore discard default. Use `lotus config default` to set this repo up with a default config. Be sure to set `EnableSplitstore` to `false` if you are running a full archive node") return xerrors.Errorf("FullNode config not found and fallback to default disallowed while we transition to splitstore discard default. Use `lotus config default` to set this repo up with a default config. Be sure to set `EnableSplitstore` to `false` if you are running a full archive node")
} }

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func fullNodeDefault() (interface{}, error) { return DefaultFullNode(), nil } func fullNodeDefault() (interface{}, error) { return DefaultFullNode(), nil }
@ -138,3 +139,77 @@ func TestFailToFallbackToDefault(t *testing.T) {
_, err = FromFile(nonExistantFileName, SetDefault(fullNodeDefault), SetCanFallbackOnDefault(NoDefaultForSplitstoreTransition)) _, err = FromFile(nonExistantFileName, SetDefault(fullNodeDefault), SetCanFallbackOnDefault(NoDefaultForSplitstoreTransition))
assert.Error(t, err) assert.Error(t, err)
} }
func TestPrintDeprecated(t *testing.T) {
type ChildCfg struct {
Field string `moved:"Bang"`
NewField string
}
type Old struct {
Thing1 int `moved:"New.Thing1"`
Thing2 int `moved:"New.Thing2"`
}
type New struct {
Thing1 int
Thing2 int
}
type ParentCfg struct {
Child ChildCfg
Old Old
New New
Foo int
Baz string `moved:"Child.NewField"`
Boom int `moved:"Foo"`
Bang string
}
t.Run("warning output", func(t *testing.T) {
cfg := `
Baz = "baz"
Foo = 100
[Child]
Field = "bip"
NewField = "bop"
`
warningWriter := bytes.NewBuffer(nil)
v, err := FromReader(bytes.NewReader([]byte(cfg)), &ParentCfg{Boom: 200, Bang: "300"}, SetWarningWriter(warningWriter))
require.NoError(t, err)
require.Equal(t, &ParentCfg{
Child: ChildCfg{
Field: "bip",
NewField: "bop",
},
Baz: "baz",
Foo: 100,
Boom: 200,
Bang: "bip",
}, v)
require.Regexp(t, `\WChild\.Field\W.+use 'Bang' instead`, warningWriter.String())
require.Regexp(t, `\WBaz\W.+use 'Child\.NewField' instead`, warningWriter.String())
require.NotContains(t, warningWriter.String(), "don't use this at all")
require.NotContains(t, warningWriter.String(), "Boom")
})
defaultNew := New{Thing1: 42, Thing2: 800}
testCases := []struct {
name string
cfg string
expected New
}{
{"simple", ``, defaultNew},
{"set new", "[New]\nThing1 = 101\nThing2 = 102\n", New{Thing1: 101, Thing2: 102}},
// should move old to new fields if new isn't set
{"set old", "[Old]\nThing1 = 101\nThing2 = 102\n", New{Thing1: 101, Thing2: 102}},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
v, err := FromReader(bytes.NewReader([]byte(tc.cfg)), &ParentCfg{New: defaultNew})
require.NoError(t, err)
require.Equal(t, tc.expected, v.(*ParentCfg).New)
})
}
}

View File

@ -791,19 +791,48 @@ type FevmConfig struct {
// Set to 0 to keep all mappings // Set to 0 to keep all mappings
EthTxHashMappingLifetimeDays int EthTxHashMappingLifetimeDays int
Events Events Events DeprecatedEvents `toml:"Events,omitempty"`
} }
type Events struct { type DeprecatedEvents struct {
// DisableRealTimeFilterAPI is DEPRECATED and will be removed in a future release. Use Events.DisableRealTimeFilterAPI instead.
DisableRealTimeFilterAPI bool `moved:"Events.DisableRealTimeFilterAPI" toml:"DisableRealTimeFilterAPI,omitempty"`
// DisableHistoricFilterAPI is DEPRECATED and will be removed in a future release. Use Events.DisableHistoricFilterAPI instead.
DisableHistoricFilterAPI bool `moved:"Events.DisableHistoricFilterAPI" toml:"DisableHistoricFilterAPI,omitempty"`
// FilterTTL is DEPRECATED and will be removed in a future release. Use Events.FilterTTL instead.
FilterTTL Duration `moved:"Events.FilterTTL" toml:"FilterTTL,omitzero"`
// MaxFilters is DEPRECATED and will be removed in a future release. Use Events.MaxFilters instead.
MaxFilters int `moved:"Events.MaxFilters" toml:"MaxFilters,omitzero"`
// MaxFilterResults is DEPRECATED and will be removed in a future release. Use Events.MaxFilterResults instead.
MaxFilterResults int `moved:"Events.MaxFilterResults" toml:"MaxFilterResults,omitzero"`
// MaxFilterHeightRange is DEPRECATED and will be removed in a future release. Use Events.MaxFilterHeightRange instead.
MaxFilterHeightRange uint64 `moved:"Events.MaxFilterHeightRange" toml:"MaxFilterHeightRange,omitzero"`
// DatabasePath is DEPRECATED and will be removed in a future release. Use Events.DatabasePath instead.
DatabasePath string `moved:"Events.DatabasePath" toml:"DatabasePath,omitempty"`
}
type EventsConfig struct {
// DisableRealTimeFilterAPI will disable the RealTimeFilterAPI that can create and query filters for actor events as they are emitted. // DisableRealTimeFilterAPI will disable the RealTimeFilterAPI that can create and query filters for actor events as they are emitted.
// The API is enabled when EnableEthRPC or Events.EnableActorEventsAPI is true, but can be disabled selectively with this flag. // The API is enabled when Fevm.EnableEthRPC or EnableActorEventsAPI is true, but can be disabled selectively with this flag.
DisableRealTimeFilterAPI bool DisableRealTimeFilterAPI bool
// DisableHistoricFilterAPI will disable the HistoricFilterAPI that can create and query filters for actor events // DisableHistoricFilterAPI will disable the HistoricFilterAPI that can create and query filters for actor events
// that occurred in the past. HistoricFilterAPI maintains a queryable index of events. // that occurred in the past. HistoricFilterAPI maintains a queryable index of events.
// The API is enabled when EnableEthRPC or Events.EnableActorEventsAPI is true, but can be disabled selectively with this flag. // The API is enabled when Fevm.EnableEthRPC or EnableActorEventsAPI is true, but can be disabled selectively with this flag.
DisableHistoricFilterAPI bool DisableHistoricFilterAPI bool
// EnableActorEventsAPI enables the Actor events API that enables clients to consume events
// emitted by (smart contracts + built-in Actors).
// This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be
// disabled by setting their respective Disable* options.
EnableActorEventsAPI bool
// FilterTTL specifies the time to live for actor event filters. Filters that haven't been accessed longer than // FilterTTL specifies the time to live for actor event filters. Filters that haven't been accessed longer than
// this time become eligible for automatic deletion. // this time become eligible for automatic deletion.
FilterTTL Duration FilterTTL Duration
@ -830,14 +859,6 @@ type Events struct {
// Set upper bound on index size // Set upper bound on index size
} }
type EventsConfig struct {
// EnableActorEventsAPI enables the Actor events API that enables clients to consume events
// emitted by (smart contracts + built-in Actors).
// This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be
// disabled by setting their respective Disable* options in Fevm.Events.
EnableActorEventsAPI bool
}
type IndexConfig struct { type IndexConfig struct {
// EXPERIMENTAL FEATURE. USE WITH CAUTION // EXPERIMENTAL FEATURE. USE WITH CAUTION
// EnableMsgIndex enables indexing of messages on chain. // EnableMsgIndex enables indexing of messages on chain.

View File

@ -32,17 +32,17 @@ type EventHelperAPI struct {
var _ events.EventHelperAPI = &EventHelperAPI{} var _ events.EventHelperAPI = &EventHelperAPI{}
func EthEventHandler(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRepo, fx.Lifecycle, *filter.EventFilterManager, *store.ChainStore, *stmgr.StateManager, EventHelperAPI, *messagepool.MessagePool, full.StateAPI, full.ChainAPI) (*full.EthEventHandler, error) { func EthEventHandler(cfg config.EventsConfig, enableEthRPC bool) func(helpers.MetricsCtx, repo.LockedRepo, fx.Lifecycle, *filter.EventFilterManager, *store.ChainStore, *stmgr.StateManager, EventHelperAPI, *messagepool.MessagePool, full.StateAPI, full.ChainAPI) (*full.EthEventHandler, error) {
return func(mctx helpers.MetricsCtx, r repo.LockedRepo, lc fx.Lifecycle, fm *filter.EventFilterManager, cs *store.ChainStore, sm *stmgr.StateManager, evapi EventHelperAPI, mp *messagepool.MessagePool, stateapi full.StateAPI, chainapi full.ChainAPI) (*full.EthEventHandler, error) { return func(mctx helpers.MetricsCtx, r repo.LockedRepo, lc fx.Lifecycle, fm *filter.EventFilterManager, cs *store.ChainStore, sm *stmgr.StateManager, evapi EventHelperAPI, mp *messagepool.MessagePool, stateapi full.StateAPI, chainapi full.ChainAPI) (*full.EthEventHandler, error) {
ctx := helpers.LifecycleCtx(mctx, lc) ctx := helpers.LifecycleCtx(mctx, lc)
ee := &full.EthEventHandler{ ee := &full.EthEventHandler{
Chain: cs, Chain: cs,
MaxFilterHeightRange: abi.ChainEpoch(cfg.Events.MaxFilterHeightRange), MaxFilterHeightRange: abi.ChainEpoch(cfg.MaxFilterHeightRange),
SubscribtionCtx: ctx, SubscribtionCtx: ctx,
} }
if !cfg.EnableEthRPC || cfg.Events.DisableRealTimeFilterAPI { if !enableEthRPC || cfg.DisableRealTimeFilterAPI {
// all event functionality is disabled // all event functionality is disabled
// the historic filter API relies on the real time one // the historic filter API relies on the real time one
return ee, nil return ee, nil
@ -53,21 +53,21 @@ func EthEventHandler(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.Locked
StateAPI: stateapi, StateAPI: stateapi,
ChainAPI: chainapi, ChainAPI: chainapi,
} }
ee.FilterStore = filter.NewMemFilterStore(cfg.Events.MaxFilters) ee.FilterStore = filter.NewMemFilterStore(cfg.MaxFilters)
// Start garbage collection for filters // Start garbage collection for filters
lc.Append(fx.Hook{ lc.Append(fx.Hook{
OnStart: func(context.Context) error { OnStart: func(context.Context) error {
go ee.GC(ctx, time.Duration(cfg.Events.FilterTTL)) go ee.GC(ctx, time.Duration(cfg.FilterTTL))
return nil return nil
}, },
}) })
ee.TipSetFilterManager = &filter.TipSetFilterManager{ ee.TipSetFilterManager = &filter.TipSetFilterManager{
MaxFilterResults: cfg.Events.MaxFilterResults, MaxFilterResults: cfg.MaxFilterResults,
} }
ee.MemPoolFilterManager = &filter.MemPoolFilterManager{ ee.MemPoolFilterManager = &filter.MemPoolFilterManager{
MaxFilterResults: cfg.Events.MaxFilterResults, MaxFilterResults: cfg.MaxFilterResults,
} }
ee.EventFilterManager = fm ee.EventFilterManager = fm
@ -94,22 +94,22 @@ func EthEventHandler(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.Locked
} }
} }
func EventFilterManager(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRepo, fx.Lifecycle, *store.ChainStore, *stmgr.StateManager, EventHelperAPI, full.ChainAPI) (*filter.EventFilterManager, error) { func EventFilterManager(cfg config.EventsConfig) func(helpers.MetricsCtx, repo.LockedRepo, fx.Lifecycle, *store.ChainStore, *stmgr.StateManager, EventHelperAPI, full.ChainAPI) (*filter.EventFilterManager, error) {
return func(mctx helpers.MetricsCtx, r repo.LockedRepo, lc fx.Lifecycle, cs *store.ChainStore, sm *stmgr.StateManager, evapi EventHelperAPI, chainapi full.ChainAPI) (*filter.EventFilterManager, error) { return func(mctx helpers.MetricsCtx, r repo.LockedRepo, lc fx.Lifecycle, cs *store.ChainStore, sm *stmgr.StateManager, evapi EventHelperAPI, chainapi full.ChainAPI) (*filter.EventFilterManager, error) {
ctx := helpers.LifecycleCtx(mctx, lc) ctx := helpers.LifecycleCtx(mctx, lc)
// Enable indexing of actor events // Enable indexing of actor events
var eventIndex *filter.EventIndex var eventIndex *filter.EventIndex
if !cfg.Events.DisableHistoricFilterAPI { if !cfg.DisableHistoricFilterAPI {
var dbPath string var dbPath string
if cfg.Events.DatabasePath == "" { if cfg.DatabasePath == "" {
sqlitePath, err := r.SqlitePath() sqlitePath, err := r.SqlitePath()
if err != nil { if err != nil {
return nil, err return nil, err
} }
dbPath = filepath.Join(sqlitePath, "events.db") dbPath = filepath.Join(sqlitePath, "events.db")
} else { } else {
dbPath = cfg.Events.DatabasePath dbPath = cfg.DatabasePath
} }
var err error var err error
@ -144,7 +144,7 @@ func EventFilterManager(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.Loc
return *actor.Address, true return *actor.Address, true
}, },
MaxFilterResults: cfg.Events.MaxFilterResults, MaxFilterResults: cfg.MaxFilterResults,
} }
lc.Append(fx.Hook{ lc.Append(fx.Hook{
@ -162,10 +162,10 @@ func EventFilterManager(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.Loc
} }
} }
func ActorEventHandler(enable bool, fevmCfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRepo, fx.Lifecycle, *filter.EventFilterManager, *store.ChainStore, *stmgr.StateManager, EventHelperAPI, *messagepool.MessagePool, full.StateAPI, full.ChainAPI) (*full.ActorEventHandler, error) { func ActorEventHandler(cfg config.EventsConfig) func(helpers.MetricsCtx, repo.LockedRepo, fx.Lifecycle, *filter.EventFilterManager, *store.ChainStore, *stmgr.StateManager, EventHelperAPI, *messagepool.MessagePool, full.StateAPI, full.ChainAPI) (*full.ActorEventHandler, error) {
return func(mctx helpers.MetricsCtx, r repo.LockedRepo, lc fx.Lifecycle, fm *filter.EventFilterManager, cs *store.ChainStore, sm *stmgr.StateManager, evapi EventHelperAPI, mp *messagepool.MessagePool, stateapi full.StateAPI, chainapi full.ChainAPI) (*full.ActorEventHandler, error) { return func(mctx helpers.MetricsCtx, r repo.LockedRepo, lc fx.Lifecycle, fm *filter.EventFilterManager, cs *store.ChainStore, sm *stmgr.StateManager, evapi EventHelperAPI, mp *messagepool.MessagePool, stateapi full.StateAPI, chainapi full.ChainAPI) (*full.ActorEventHandler, error) {
if !enable || fevmCfg.Events.DisableRealTimeFilterAPI { if !cfg.EnableActorEventsAPI || cfg.DisableRealTimeFilterAPI {
fm = nil fm = nil
} }
@ -173,7 +173,7 @@ func ActorEventHandler(enable bool, fevmCfg config.FevmConfig) func(helpers.Metr
cs, cs,
fm, fm,
time.Duration(build.BlockDelaySecs)*time.Second, time.Duration(build.BlockDelaySecs)*time.Second,
abi.ChainEpoch(fevmCfg.Events.MaxFilterHeightRange), abi.ChainEpoch(cfg.MaxFilterHeightRange),
), nil ), nil
} }
} }