From 16204dc303d29e8ae0a1ee4dc0f6529442d55629 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 3 Oct 2024 12:57:56 -0700 Subject: [PATCH] feat(f3): prepare for f3 bootstrap This patch: 1. Bootstraps F3 when we hit the `F3BootstrapEpoch` (when non-negative). 2. Refuses any/all dynamic manifests once we get within one finality of said epoch. 3. Sets the F3 network name for mainnet to "filecoin". 4. Refuses any/all dynamic manifests that don't start with the expected network name prefix. --- chain/lf3/config.go | 94 +++++++++++++++++++++++++++---------- chain/lf3/manifest.go | 59 +++++++++++++++++++---- itests/kit/node_opts.go | 22 ++++----- node/builder_chain.go | 6 +-- node/modules/lp2p/pubsub.go | 7 ++- 5 files changed, 133 insertions(+), 55 deletions(-) diff --git a/chain/lf3/config.go b/chain/lf3/config.go index 8c669ded94..cae1711ead 100644 --- a/chain/lf3/config.go +++ b/chain/lf3/config.go @@ -8,6 +8,7 @@ import ( "github.com/filecoin-project/go-f3/gpbft" "github.com/filecoin-project/go-f3/manifest" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/build/buildconstants" "github.com/filecoin-project/lotus/chain/actors/policy" @@ -15,34 +16,77 @@ import ( ) type Config struct { - InitialManifest *manifest.Manifest + // BaseNetworkName is the base from which dynamic network names are defined and is usually + // the name of the network defined by the static manifest. This must be set correctly or, + // e.g., pubsub topic filters won't work correctly. + BaseNetworkName gpbft.NetworkName + // StaticManifest this instance's default manifest absent any dynamic manifests. Also see + // PrioritizeStaticManifest. + StaticManifest *manifest.Manifest + // DynamicManifestProvider is the peer ID of the peer authorized to send us dynamic manifest + // updates. Dynamic manifest updates can be used for testing but will not be used to affect + // finality. DynamicManifestProvider peer.ID + // PrioritizeStaticManifest means that, once we get within one finality of the static + // manifest's bootstrap epoch we'll switch to it and ignore any further dynamic manifest + // updates. This exists to enable bootstrapping F3. + PrioritizeStaticManifest bool } -func NewConfig(manifestProvider peer.ID, initialPowerTable cid.Cid) func(dtypes.NetworkName) *Config { - return func(nn dtypes.NetworkName) *Config { - m := manifest.LocalDevnetManifest() - m.NetworkName = gpbft.NetworkName(nn) - m.EC.Period = time.Duration(buildconstants.BlockDelaySecs) * time.Second - m.CatchUpAlignment = time.Duration(buildconstants.BlockDelaySecs) * time.Second / 2 - if buildconstants.F3BootstrapEpoch < 0 { - // if unset, set to a sane default so we don't get scary logs and pause. - m.BootstrapEpoch = 2 * int64(policy.ChainFinality) - m.Pause = true - } else { - m.BootstrapEpoch = int64(buildconstants.F3BootstrapEpoch) - } - m.EC.Finality = int64(policy.ChainFinality) - m.CommitteeLookback = 5 - m.InitialPowerTable = initialPowerTable - m.EC.Finalize = buildconstants.F3Consensus +// NewManifest constructs a sane F3 manifest based on the passed parameters. This function does not +// look at and/or depend on the nodes build params, etc. +func NewManifest( + nn gpbft.NetworkName, + finality, bootstrapEpoch abi.ChainEpoch, + ecPeriod time.Duration, + initialPowerTable cid.Cid, +) *manifest.Manifest { + return &manifest.Manifest{ + ProtocolVersion: manifest.VersionCapability, + BootstrapEpoch: int64(bootstrapEpoch), + NetworkName: nn, + InitialPowerTable: initialPowerTable, + CommitteeLookback: manifest.DefaultCommitteeLookback, + CatchUpAlignment: ecPeriod / 2, + Gpbft: manifest.DefaultGpbftConfig, + EC: manifest.EcConfig{ + Period: ecPeriod, + Finality: int64(finality), + DelayMultiplier: manifest.DefaultEcConfig.DelayMultiplier, + BaseDecisionBackoffTable: manifest.DefaultEcConfig.BaseDecisionBackoffTable, + HeadLookback: 0, + Finalize: true, + }, + CertificateExchange: manifest.CxConfig{ + ClientRequestTimeout: manifest.DefaultCxConfig.ClientRequestTimeout, + ServerRequestTimeout: manifest.DefaultCxConfig.ServerRequestTimeout, + MinimumPollInterval: ecPeriod, + MaximumPollInterval: 4 * ecPeriod, + }, + } +} - // TODO: We're forcing this to start paused for now. We need to remove this for the final - // mainnet launch. - m.Pause = true - return &Config{ - InitialManifest: m, - DynamicManifestProvider: manifestProvider, - } +// NewConfig creates a new F3 config based on the node's build parameters and the passed network +// name. +func NewConfig(nn dtypes.NetworkName) *Config { + // Use "filecoin" as the network name on mainnet, otherwise use the network name. Yes, + // mainnet is called testnetnet in state. + if nn == "testnetnet" { + nn = "filecoin" + } + c := &Config{ + BaseNetworkName: gpbft.NetworkName(nn), + PrioritizeStaticManifest: true, + DynamicManifestProvider: buildconstants.F3ManifestServerID, + } + if buildconstants.F3BootstrapEpoch >= 0 { + c.StaticManifest = NewManifest( + c.BaseNetworkName, + policy.ChainFinality, + buildconstants.F3BootstrapEpoch, + time.Duration(buildconstants.BlockDelaySecs)*time.Second, + buildconstants.F3InitialPowerTableCID, + ) } + return c } diff --git a/chain/lf3/manifest.go b/chain/lf3/manifest.go index e4a944f090..fba6537d08 100644 --- a/chain/lf3/manifest.go +++ b/chain/lf3/manifest.go @@ -1,45 +1,84 @@ package lf3 import ( + "context" "fmt" + "strings" "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/namespace" pubsub "github.com/libp2p/go-libp2p-pubsub" + "golang.org/x/xerrors" + "github.com/filecoin-project/go-f3/ec" "github.com/filecoin-project/go-f3/manifest" + "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/node/modules/helpers" ) +type headGetter store.ChainStore + +func (hg *headGetter) GetHead(context.Context) (ec.TipSet, error) { + head := (*store.ChainStore)(hg).GetHeaviestTipSet() + if head == nil { + return nil, xerrors.New("no heaviest tipset") + } + return &f3TipSet{TipSet: head}, nil +} + // Determines the max. number of configuration changes // that are allowed for the dynamic manifest. // If the manifest changes more than this number, the F3 // message topic will be filtered var MaxDynamicManifestChangesAllowed = 1000 -func NewManifestProvider(config *Config, ps *pubsub.PubSub, mds dtypes.MetadataDS) (manifest.ManifestProvider, error) { +func NewManifestProvider(mctx helpers.MetricsCtx, config *Config, cs *store.ChainStore, ps *pubsub.PubSub, mds dtypes.MetadataDS) (prov manifest.ManifestProvider, err error) { if config.DynamicManifestProvider == "" { - return manifest.NewStaticManifestProvider(config.InitialManifest) + if config.StaticManifest == nil { + return manifest.NoopManifestProvider{}, nil + } + return manifest.NewStaticManifestProvider(config.StaticManifest) + } + + opts := []manifest.DynamicManifestProviderOption{ + manifest.DynamicManifestProviderWithDatastore( + namespace.Wrap(mds, datastore.NewKey("/f3-dynamic-manifest")), + ), + } + + if config.StaticManifest != nil { + opts = append(opts, + manifest.DynamicManifestProviderWithInitialManifest(config.StaticManifest), + ) } - primaryNetworkName := config.InitialManifest.NetworkName + networkNameBase := config.BaseNetworkName + "/" filter := func(m *manifest.Manifest) error { if m.EC.Finalize { return fmt.Errorf("refusing dynamic manifest that finalizes tipsets") } - if m.NetworkName == primaryNetworkName { + if !strings.HasPrefix(string(m.NetworkName), string(networkNameBase)) { return fmt.Errorf( - "refusing dynamic manifest with network name %q that clashes with initial manifest", - primaryNetworkName, + "refusing dynamic manifest with network name %q, must start with %q", + m.NetworkName, + networkNameBase, ) } return nil } - ds := namespace.Wrap(mds, datastore.NewKey("/f3-dynamic-manifest")) - return manifest.NewDynamicManifestProvider(ps, config.DynamicManifestProvider, - manifest.DynamicManifestProviderWithInitialManifest(config.InitialManifest), - manifest.DynamicManifestProviderWithDatastore(ds), + opts = append(opts, manifest.DynamicManifestProviderWithFilter(filter), ) + + prov, err = manifest.NewDynamicManifestProvider(ps, config.DynamicManifestProvider, opts...) + if err != nil { + return nil, err + } + if config.PrioritizeStaticManifest && config.StaticManifest != nil { + prov, err = manifest.NewFusingManifestProvider(mctx, + (*headGetter)(cs), prov, config.StaticManifest) + } + return prov, err } diff --git a/itests/kit/node_opts.go b/itests/kit/node_opts.go index 6755988caa..70e784a745 100644 --- a/itests/kit/node_opts.go +++ b/itests/kit/node_opts.go @@ -8,6 +8,7 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/peer" + "github.com/filecoin-project/go-f3/gpbft" "github.com/filecoin-project/go-f3/manifest" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" @@ -218,23 +219,18 @@ func MutateSealingConfig(mut func(sc *config.SealingConfig)) NodeOpt { func F3Enabled(bootstrapEpoch abi.ChainEpoch, blockDelay time.Duration, finality abi.ChainEpoch, manifestProvider peer.ID) NodeOpt { return ConstructorOpts( node.Override(new(*lf3.Config), func(nn dtypes.NetworkName) *lf3.Config { - c := lf3.NewConfig(manifestProvider, cid.Undef)(nn) - c.InitialManifest.Pause = false - c.InitialManifest.EC.Period = blockDelay - c.InitialManifest.Gpbft.Delta = blockDelay / 5 - c.InitialManifest.EC.Finality = int64(finality) - c.InitialManifest.BootstrapEpoch = int64(bootstrapEpoch) - c.InitialManifest.EC.HeadLookback = 0 - c.InitialManifest.EC.Finalize = true - c.InitialManifest.CatchUpAlignment = blockDelay / 2 - c.InitialManifest.CertificateExchange.MinimumPollInterval = 2 * blockDelay - c.InitialManifest.CertificateExchange.MaximumPollInterval = 10 * blockDelay - return c + m := lf3.NewManifest(gpbft.NetworkName(nn), finality, bootstrapEpoch, blockDelay, cid.Undef) + return &lf3.Config{ + BaseNetworkName: gpbft.NetworkName(nn), + StaticManifest: m, + DynamicManifestProvider: manifestProvider, + PrioritizeStaticManifest: false, + } }), node.Override(new(manifest.ManifestProvider), func(config *lf3.Config, ps *pubsub.PubSub) (manifest.ManifestProvider, error) { return manifest.NewDynamicManifestProvider(ps, config.DynamicManifestProvider, - manifest.DynamicManifestProviderWithInitialManifest(config.InitialManifest), + manifest.DynamicManifestProviderWithInitialManifest(config.StaticManifest), ) }), node.Override(new(*lf3.F3), lf3.New), diff --git a/node/builder_chain.go b/node/builder_chain.go index 480c5cb27f..d367ab4e3e 100644 --- a/node/builder_chain.go +++ b/node/builder_chain.go @@ -13,7 +13,6 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/build/buildconstants" "github.com/filecoin-project/lotus/chain" "github.com/filecoin-project/lotus/chain/beacon" "github.com/filecoin-project/lotus/chain/consensus" @@ -165,10 +164,7 @@ var ChainNode = Options( ), If(build.IsF3Enabled(), - Override(new(*lf3.Config), lf3.NewConfig( - buildconstants.F3ManifestServerID, - buildconstants.F3InitialPowerTableCID, - )), + Override(new(*lf3.Config), lf3.NewConfig), Override(new(manifest.ManifestProvider), lf3.NewManifestProvider), Override(new(*lf3.F3), lf3.New), ), diff --git a/node/modules/lp2p/pubsub.go b/node/modules/lp2p/pubsub.go index 0c8f7b291f..20a222cd21 100644 --- a/node/modules/lp2p/pubsub.go +++ b/node/modules/lp2p/pubsub.go @@ -388,9 +388,12 @@ func GossipSub(in GossipIn) (service *pubsub.PubSub, err error) { allowTopics = append(allowTopics, drandTopics...) if in.F3Config != nil { - f3BaseTopicName := manifest.PubSubTopicFromNetworkName(in.F3Config.InitialManifest.NetworkName) - allowTopics = append(allowTopics, f3BaseTopicName) + if in.F3Config.StaticManifest != nil { + f3TopicName := manifest.PubSubTopicFromNetworkName(in.F3Config.StaticManifest.NetworkName) + allowTopics = append(allowTopics, f3TopicName) + } if in.F3Config.DynamicManifestProvider != "" { + f3BaseTopicName := manifest.PubSubTopicFromNetworkName(in.F3Config.BaseNetworkName) allowTopics = append(allowTopics, manifest.ManifestPubSubTopicName) for i := 0; i < lf3.MaxDynamicManifestChangesAllowed; i++ { allowTopics = append(allowTopics, fmt.Sprintf("%s/%d", f3BaseTopicName, i))