diff --git a/pkg/firedancer/assets.go b/pkg/firedancer/assets.go new file mode 100644 index 0000000..20f7e8a --- /dev/null +++ b/pkg/firedancer/assets.go @@ -0,0 +1,14 @@ +package firedancer + +import ( + "embed" +) + +//go:embed assets +var assets embed.FS + +const ( + assetsInstall = "assets/install" + assetsFDService = "assets/svmkit-fd.service" + assetsFDSetupService = "assets/svmkit-fd-setup.service" +) diff --git a/pkg/firedancer/assets/install b/pkg/firedancer/assets/install new file mode 100644 index 0000000..9be44cf --- /dev/null +++ b/pkg/firedancer/assets/install @@ -0,0 +1,59 @@ +# -*- mode: shell-script -*- +# shellcheck shell=bash + +USER=sol +GROUP=sol +HOME=/home/$USER + +VALIDATOR_PACKAGE=svmkit-frankendancer +VALIDATOR_SERVICE=${VALIDATOR_PACKAGE}.service + +step::00::wait-for-a-stable-environment() { + cloud-init::wait-for-stable-environment +} + +step::05::setup-abklabs-apt() { + apt::setup-abk-apt-source +} + +step::20::create-sol-user() { + create-sol-user +} + +step::30::copy-assets() { + $SUDO cp validator-keypair.json vote-account-keypair.json config.toml "$HOME" + $SUDO chown "$USER:$GROUP" "$HOME"/{validator-keypair,vote-account-keypair}.json "$HOME"/config.toml + + $SUDO cp *.service /etc/systemd/system/. + $SUDO systemctl daemon-reload +} + +step::70::install-validator() { + if [[ -v VALIDATOR_VERSION ]]; then + # XXX - This needs to be fixed up to be able to select the right solana CLI major version. + $APT --allow-downgrades install "${VALIDATOR_PACKAGE}=$VALIDATOR_VERSION" "svmkit-solana-cli" + else + $APT --allow-downgrades install "${VALIDATOR_PACKAGE}" "svmkit-solana-cli" + fi +} + +step::75::setup-solana-cli() { + [[ -v SOLANA_CLI_CONFIG_FLAGS ]] || return 0 + + # First setup the login user. + solana config set $SOLANA_CLI_CONFIG_FLAGS + + # Setup the sol user. + $SUDO -u "$USER" -i solana config set $SOLANA_CLI_CONFIG_FLAGS +} + +step::80::setup-validator() { + if systemctl list-unit-files svmkit-fd.service >/dev/null; then + $SUDO systemctl stop svmkit-fd.service || true + fi + + $SUDO systemctl enable svmkit-fd-setup.service + $SUDO systemctl start svmkit-fd-setup.service + $SUDO systemctl enable svmkit-fd-validator.service + $SUDO systemctl start svmkit-fd-validator.service +} diff --git a/pkg/firedancer/assets/svmkit-fd-setup.service b/pkg/firedancer/assets/svmkit-fd-setup.service new file mode 100644 index 0000000..df7808e --- /dev/null +++ b/pkg/firedancer/assets/svmkit-fd-setup.service @@ -0,0 +1,15 @@ +[Unit] +Description=SVMkit FD Machine Setup +After=local-fs.target +After=network.target + +[Service] +Type=exec +User=root +Group=root +ExecStart=/opt/frankendancer/bin/fdctl --config /home/sol/config.toml configure init all +RemainAfterExit=true +Type=oneshot + +[Install] +WantedBy=default.target diff --git a/pkg/firedancer/assets/svmkit-fd.service b/pkg/firedancer/assets/svmkit-fd.service new file mode 100644 index 0000000..9a16907 --- /dev/null +++ b/pkg/firedancer/assets/svmkit-fd.service @@ -0,0 +1,13 @@ +[Unit] +Description=SVMkit FD Validator +After=svmkit-fd-setup.service +Requires=svmkit-fd-setup.service + +[Service] +Type=exec +User=root +Group=root +ExecStart=/opt/frankendancer/bin/fdctl --config /home/sol/config.toml run + +[Install] +WantedBy=default.target diff --git a/pkg/firedancer/config.go b/pkg/firedancer/config.go new file mode 100644 index 0000000..e85a412 --- /dev/null +++ b/pkg/firedancer/config.go @@ -0,0 +1,126 @@ +package firedancer + +import ( + "github.com/BurntSushi/toml" + "io" +) + +type ConfigLog struct { + Path *string `toml:"path,omitempty" pulumi:"path,optional"` + Colorize *string `toml:"colorize,omitempty" pulumi:"colorize,optional"` + LevelLogfile *string `toml:"level_logfile,omitempty" pulumi:"levelLogfile,optional"` + LevelStderr *string `toml:"level_stderr,omitempty" pulumi:"levelStderr,optional"` + LevelFlush *string `toml:"level_flush,omitempty" pulumi:"levelFlush,optional"` +} + +type ConfigReporting struct { + SolanaMetricsConfig *string `toml:"solana_metrics_config,omitempty" pulumi:"solanaMetricsConfig,optional"` +} + +type ConfigLedger struct { + Path *string `toml:"path,omitempty" pulumi:"path,optional"` + AccountsPath *string `toml:"accounts_path,omitempty" pulumi:"accountsPath,optional"` + LimitSize *int `toml:"limit_size,omitempty" pulumi:"limitSize,optional"` + AccountIndexes *[]string `toml:"account_indexes,omitempty" pulumi:"accountIndexes,optional"` + AccountIndexExcludeKeys *[]string `toml:"account_index_exclude_keys,omitempty" pulumi:"accountIndexExcludeKeys,optional"` + AccountIndexIncludeKeys *[]string `toml:"account_index_include_keys,omitempty" pulumi:"accountIndexIncludeKeys,optional"` + SnapshotArchiveFormat *string `toml:"snapshot_archive_format,omitempty" pulumi:"snapshotArchiveFormat,optional"` + RequireTower *bool `toml:"require_tower,omitempty" pulumi:"requireTower,optional"` +} + +type ConfigGossip struct { + Entrypoints *[]string `toml:"entrypoints,omitempty" pulumi:"entrypoints,optional"` + PortCheck *bool `toml:"port_check,omitempty" pulumi:"portCheck,optional"` + Port *int `toml:"port,omitempty" pulumi:"port,optional"` + Host *string `toml:"host,omitempty" pulumi:"host,optional"` +} + +type ConfigRPC struct { + Port *int `toml:"port,omitempty" pulumi:"port,optional"` + FullAPI *bool `toml:"full_api,omitempty" pulumi:"fullApi,optional"` + Private *bool `toml:"private,omitempty" pulumi:"private,optional"` + TransactionHistory *bool `toml:"transaction_history,omitempty" pulumi:"transactionHistory,optional"` + ExtendedTxMetadataStorage *bool `toml:"extended_tx_metadata_storage,omitempty" pulumi:"extendedTxMetadataStorage,optional"` + OnlyKnown *bool `toml:"only_known,omitempty" pulumi:"onlyKnown,optional"` + PubsubEnableBlockSubscription *bool `toml:"pubsub_enable_block_subscription,omitempty" pulumi:"pubsubEnableBlockSubscription,optional"` + PubsubEnableVoteSubscription *bool `toml:"pubsub_enable_vote_subscription,omitempty" pulumi:"pubsubEnableVoteSubscription,optional"` + BigtableLedgerStorage *bool `toml:"bigtable_ledger_storage,omitempty" pulumi:"bigtableLedgerStorage,optional"` +} + +type ConfigSnapshots struct { + IncrementalSnapshots *bool `toml:"incremental_snapshots,omitempty" pulumi:"incrementalSnapshots,optional"` + FullSnapshotIntervalSlots *int `toml:"full_snapshot_interval_slots,omitempty" pulumi:"fullSnapshotIntervalSlots,optional"` + IncrementalSnapshotIntervalSlots *int `toml:"incremental_snapshot_interval_slots,omitempty" pulumi:"incrementalSnapshotIntervalSlots,optional"` + MaximumFullSnapshotsToRetain *int `toml:"maximum_full_snapshots_to_retain,omitempty" pulumi:"maximumFullSnapshotsToRetain,optional"` + MaximumIncrementalSnapshotsToRetain *int `toml:"maximum_incremental_snapshots_to_retain,omitempty" pulumi:"maximumIncrementalSnapshotsToRetain,optional"` + MinimumSnapshotDownloadSpeed *int `toml:"minimum_snapshot_download_speed,omitempty" pulumi:"minimumSnapshotDownloadSpeed,optional"` + Path *string `toml:"path,omitempty" pulumi:"path,optional"` + IncrementalPath *string `toml:"incremental_path,omitempty" pulumi:"incrementalPath,optional"` +} + +type ConfigConsensus struct { + IdentityPath *string `toml:"identity_path,omitempty" pulumi:"identityPath,optional"` + VoteAccountPath *string `toml:"vote_account_path,omitempty" pulumi:"voteAccountPath,optional"` + AuthorizedVoterPaths *[]string `toml:"authorized_voter_paths,omitempty" pulumi:"authorizedVoterPaths,optional"` + SnapshotFetch *bool `toml:"snapshot_fetch,omitempty" pulumi:"snapshotFetch,optional"` + GenesisFetch *bool `toml:"genesis_fetch,omitempty" pulumi:"genesisFetch,optional"` + PohSpeedTest *bool `toml:"poh_speed_test,omitempty" pulumi:"pohSpeedTest,optional"` + ExpectedGenesisHash *string `toml:"expected_genesis_hash,omitempty" pulumi:"expectedGenesisHash,optional"` + WaitForSupermajorityAtSlot *int `toml:"wait_for_supermajority_at_slot,omitempty" pulumi:"waitForSupermajorityAtSlot,optional"` + ExpectedBankHash *string `toml:"expected_bank_hash,omitempty" pulumi:"expectedBankHash,optional"` + ExpectedShredVersion *int `toml:"expected_shred_version,omitempty" pulumi:"expectedShredVersion,optional"` + WaitForVoteToStartLeader *bool `toml:"wait_for_vote_to_start_leader,omitempty" pulumi:"waitForVoteToStartLeader,optional"` + OsNetworkLimitsTest *bool `toml:"os_network_limits_test,omitempty" pulumi:"osNetworkLimitsTest,optional"` + HardForkAtSlots *[]string `toml:"hard_fork_at_slots,omitempty" pulumi:"hardForkAtSlots,optional"` + KnownValidators *[]string `toml:"known_validators,omitempty" pulumi:"knownValidators,optional"` +} + +type ConfigLayout struct { + Affinity *string `toml:"affinity,omitempty" pulumi:"affinity,optional"` + AgaveAffinity *string `toml:"agave_affinity,omitempty" pulumi:"agaveAffinity,optional"` + NetTileCount *int `toml:"net_tile_count,omitempty" pulumi:"netTileCount,optional"` + QuicTileCount *int `toml:"quic_tile_count,omitempty" pulumi:"quicTileCount,optional"` + ResolvTileCount *int `toml:"resolv_tile_count,omitempty" pulumi:"resolvTileCount,optional"` + VerifyTileCount *int `toml:"verify_tile_count,omitempty" pulumi:"verifyTileCount,optional"` + BankTileCount *int `toml:"bank_tile_count,omitempty" pulumi:"bankTileCount,optional"` + ShredTileCount *int `toml:"shred_tile_count,omitempty" pulumi:"shredTileCount,optional"` +} + +type ConfigHugeTLBFS struct { + MountPath *string `toml:"mount_path,omitempty" pulumi:"mountPath,optional"` +} + +type Config struct { + Name *string `toml:"name,omitempty" pulumi:"name,optional"` + User *string `toml:"user,omitempty" pulumi:"user,optional"` + ScratchDirectory *string `toml:"scratch_directory,omitempty" pulumi:"scratchDirectory,optional"` + DynamicPortRange *string `toml:"dynamic_port_range,omitempty" pulumi:"dynamicPortRange,optional"` + + Log *ConfigLog `toml:"log,omitempty" pulumi:"log,optional"` + Reporting *ConfigReporting `toml:"reporting,omitempty" pulumi:"reporting,optional"` + Ledger *ConfigLedger `toml:"ledger,omitempty" pulumi:"ledger,optional"` + Gossip *ConfigGossip `toml:"gossip,omitempty" pulumi:"gossip,optional"` + RPC *ConfigRPC `toml:"rpc,omitempty" pulumi:"rpc,optional"` + Snapshots *ConfigSnapshots `toml:"snapshots,omitempty" pulumi:"snapshots,optional"` + Consensus *ConfigConsensus `toml:"consensus,omitempty" pulumi:"consensus,optional"` + Layout *ConfigLayout `toml:"layout,omitempty" pulumi:"layout,optional"` + HugeTLBFS *ConfigHugeTLBFS `toml:"hugetlbfs,omitempty" pulumi:"hugetlbfs,optional"` + + ExtraConfig *[]string `pulumi:"extraConfig,optional"` +} + +func (c *Config) Encode(w io.Writer) error { + if err := toml.NewEncoder(w).Encode(c); err != nil { + return err + } + + if c.ExtraConfig != nil { + for _, v := range *c.ExtraConfig { + if _, err := w.Write([]byte(v)); err != nil { + return err + } + } + } + + return nil +} diff --git a/pkg/firedancer/setup.go b/pkg/firedancer/setup.go new file mode 100644 index 0000000..852742d --- /dev/null +++ b/pkg/firedancer/setup.go @@ -0,0 +1,79 @@ +package firedancer + +import ( + "github.com/abklabs/svmkit/pkg/runner" +) + +type KeyPairs struct { + Identity string `pulumi:"identity" provider:"secret"` + VoteAccount string `pulumi:"voteAccount" provider:"secret"` +} + +type Firedancer struct { + KeyPairs KeyPairs `pulumi:"keyPairs"` + Config Config `pulumi:"config"` +} + +func (fd *Firedancer) Install() runner.Command { + return &InstallCommand{ + Firedancer: *fd, + } +} + +type InstallCommand struct { + Firedancer +} + +func (c *InstallCommand) Check() error { + return nil +} + +func (c *InstallCommand) Env() *runner.EnvBuilder { + e := runner.NewEnvBuilder() + return e +} + +func (c *InstallCommand) AddToPayload(p *runner.Payload) error { + { + w := p.NewWriter(runner.PayloadFile{Path: "config.toml"}) + + if err := c.Config.Encode(w); err != nil { + return err + } + } + + { + r, err := assets.Open(assetsInstall) + + if err != nil { + return err + } + + p.AddReader("steps.sh", r) + } + + { + r, err := assets.Open(assetsFDService) + + if err != nil { + return err + } + + p.AddReader("svmkit-fd-validator.service", r) + } + + { + r, err := assets.Open(assetsFDSetupService) + + if err != nil { + return err + } + + p.AddReader("svmkit-fd-setup.service", r) + } + + p.AddString("validator-keypair.json", c.KeyPairs.Identity) + p.AddString("vote-account-keypair.json", c.KeyPairs.VoteAccount) + + return nil +} diff --git a/pkg/firedancer/variant.go b/pkg/firedancer/variant.go new file mode 100644 index 0000000..21a2db0 --- /dev/null +++ b/pkg/firedancer/variant.go @@ -0,0 +1,27 @@ +package firedancer + +import ( + "github.com/pulumi/pulumi-go-provider/infer" +) + +type Variant string + +const ( + VariantFrankendancer Variant = "frankendancer" + VariantFiredancer Variant = "firedancer" +) + +func (Variant) Values() []infer.EnumValue[Variant] { + return []infer.EnumValue[Variant]{ + { + Name: string(VariantFrankendancer), + Value: VariantFrankendancer, + Description: "The Frankendancer variant", + }, + { + Name: string(VariantFiredancer), + Value: VariantFiredancer, + Description: "The Firedancer variant", + }, + } +}