diff --git a/assets/patchnotes/2.0.0-b4.tmpl b/assets/patchnotes/2.0.0-b4.tmpl new file mode 100644 index 000000000..ce65c8139 --- /dev/null +++ b/assets/patchnotes/2.0.0-b4.tmpl @@ -0,0 +1,13 @@ +{{.ColorGreen}}=== Smart Node v{{.RocketPoolVersion}} ==={{.ColorReset}} + +Changes you should be aware of before starting: + +{{.ColorGreen}}=== Welcome to the v2.0 Beta! ==={{.ColorReset}} +Welcome to Smart Node v2! This is a completely redesigned Smart Node from the ground up, taking advantage of years of lessons learned and user feedback. The list of features and changes is far too long to list here, but here are some highlights: +- Support for installing and updating via Debian's `apt` package manager (other package managers coming soon!) +- Support for printing transaction data or signed transactions without submitting them to the network +- Passwordless mode: an opt-in feature that will no longer save your node wallet's password to disk +- Overhauled Smart Node service with an HTTP API, support for batching Ethereum queries and transactions together, a new logging system, and consolidation of the api / node / watchtower containers into one +- And much more! + +To learn all about what's changed in Smart Node v2 and how to use it, take a look at our guide: https://github.com/rocket-pool/smartnode/blob/v2/v2.md diff --git a/assets/version.json b/assets/version.json index f58ff60eb..fd52c2425 100644 --- a/assets/version.json +++ b/assets/version.json @@ -1,3 +1,3 @@ { - "Version": "2.0.0-b3" + "Version": "2.0.0-b4" } diff --git a/client/network.go b/client/network.go index 7f12278b6..9e2a692cc 100644 --- a/client/network.go +++ b/client/network.go @@ -90,3 +90,8 @@ func (r *NetworkRequester) Stats() (*types.ApiResponse[api.NetworkStatsData], er func (r *NetworkRequester) TimezoneMap() (*types.ApiResponse[api.NetworkTimezonesData], error) { return client.SendGetRequest[api.NetworkTimezonesData](r, "timezone-map", "TimezoneMap", nil) } + +// Get the timezone map +func (r *NetworkRequester) IsHoustonHotfixDeployed() (*types.ApiResponse[api.NetworkHotfixDeployedData], error) { + return client.SendGetRequest[api.NetworkHotfixDeployedData](r, "is-hotfix-deployed", "IsHoustonHotfixDeployed", nil) +} diff --git a/client/pdao.go b/client/pdao.go index 6e807a898..a05f2d091 100644 --- a/client/pdao.go +++ b/client/pdao.go @@ -195,12 +195,17 @@ func (r *PDaoRequester) ProposeSetting(contractName rocketpool.ContractName, set return client.SendGetRequest[api.ProtocolDaoProposeSettingData](r, "setting/propose", "ProposeSetting", args) } -// Initialize voting so the node can vote on Protocol DAO proposals -func (r *PDaoRequester) InitializeVoting(delegate common.Address) (*types.ApiResponse[api.ProtocolDaoInitializeVotingData], error) { +// Initialize voting with delegate unlocks the node's voting power and sets the delegate in one transasction +func (r *PDaoRequester) InitializeVotingWithDelegate(delegate common.Address) (*types.ApiResponse[api.ProtocolDaoInitializeVotingData], error) { args := map[string]string{ "delegate": delegate.Hex(), } - return client.SendGetRequest[api.ProtocolDaoInitializeVotingData](r, "initialize-voting", "InitializeVoting", args) + return client.SendGetRequest[api.ProtocolDaoInitializeVotingData](r, "initialize-voting-with-delegate", "InitializeVotingWithDelegate", args) +} + +// Initialize voting so the node can vote on Protocol DAO proposals +func (r *PDaoRequester) InitializeVoting() (*types.ApiResponse[api.ProtocolDaoInitializeVotingData], error) { + return client.SendGetRequest[api.ProtocolDaoInitializeVotingData](r, "initialize-voting", "InitializeVoting", nil) } // Set the delegate for voting on Protocol DAO proposals @@ -234,3 +239,8 @@ func (r *PDaoRequester) SetSignallingAddress(signallingAddress common.Address, s func (r *PDaoRequester) ClearSignallingAddress() (*types.ApiResponse[types.TxInfoData], error) { return client.SendGetRequest[types.TxInfoData](r, "clear-signalling-address", "ClearSignallingAddress", nil) } + +// IsVotingInitialized checks if a node has initialized voting power +func (r *PDaoRequester) IsVotingInitialized() (*types.ApiResponse[api.ProtocolDaoIsVotingInitializedData], error) { + return client.SendGetRequest[api.ProtocolDaoIsVotingInitializedData](r, "is-voting-initialized", "IsVotingInitialized", nil) +} diff --git a/go.mod b/go.mod index 710cfedfd..6bfcddd34 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/prysmaticlabs/prysm/v5 v5.0.3 github.com/rivo/tview v0.0.0-20230208211350-7dfff1ce7854 // DO NOT UPGRADE github.com/rocket-pool/node-manager-core v0.5.0 - github.com/rocket-pool/rocketpool-go/v2 v2.0.0-b2.0.20240903030216-a6f93f52a853 + github.com/rocket-pool/rocketpool-go/v2 v2.0.0-b2.0.20241009181157-ebb8c8b0bf54 github.com/shirou/gopsutil/v3 v3.24.3 github.com/tyler-smith/go-bip39 v1.1.0 github.com/wealdtech/go-ens/v3 v3.6.0 diff --git a/go.sum b/go.sum index d42b1ab49..631f51e65 100644 --- a/go.sum +++ b/go.sum @@ -485,8 +485,8 @@ github.com/rocket-pool/go-merkletree v1.0.1-0.20220406020931-c262d9b976dd h1:p9K github.com/rocket-pool/go-merkletree v1.0.1-0.20220406020931-c262d9b976dd/go.mod h1:UE9fof8P7iESVtLn1K9CTSkNRYVFHZHlf96RKbU33kA= github.com/rocket-pool/node-manager-core v0.5.0 h1:98PnHb67mgOKTHMQlRql5KINYM+5NGYV3n4GYChZuec= github.com/rocket-pool/node-manager-core v0.5.0/go.mod h1:Clii5aca9PvR4HoAlUs8dh2OsJbDDnJ4yL5EaQE1gSo= -github.com/rocket-pool/rocketpool-go/v2 v2.0.0-b2.0.20240903030216-a6f93f52a853 h1:Zeb//JGHTR/FbHAN7eD73nqzy1R/2VGYHq6I0ofXzQM= -github.com/rocket-pool/rocketpool-go/v2 v2.0.0-b2.0.20240903030216-a6f93f52a853/go.mod h1:pcY43H/m5pjr7zacrsKVaXnXfKKi1UV08VDPUwxbJkc= +github.com/rocket-pool/rocketpool-go/v2 v2.0.0-b2.0.20241009181157-ebb8c8b0bf54 h1:869F7HFTkCHUNxAj6WwtapWEL8DiVfy918DviPz57dY= +github.com/rocket-pool/rocketpool-go/v2 v2.0.0-b2.0.20241009181157-ebb8c8b0bf54/go.mod h1:pcY43H/m5pjr7zacrsKVaXnXfKKi1UV08VDPUwxbJkc= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= diff --git a/install/deploy/scripts/start-mev-boost.sh b/install/deploy/scripts/start-mev-boost.sh index bfbc64706..3a222ce41 100755 --- a/install/deploy/scripts/start-mev-boost.sh +++ b/install/deploy/scripts/start-mev-boost.sh @@ -1,6 +1,26 @@ #!/bin/sh -# Set up the network-based flag +parse_additional_flags() { + # Initialize an empty string for additional arguments + ADDITIONAL_ARGS="" + + # Check if the environment variable MEV_BOOST_ADDITIONAL_FLAGS is not empty + if [ -n "$MEV_BOOST_ADDITIONAL_FLAGS" ]; then + # Split the input string into an array of key-value pairs using comma as the delimiter + IFS=',' read -r -a pairs <<< "$MEV_BOOST_ADDITIONAL_FLAGS" + + # Iterate over each key-value pair + for pair in "${pairs[@]}"; do + # Extract the key and value from the current pair + key=$(echo "$pair" | cut -d'=' -f1) + value=$(echo "$pair" | cut -d'=' -f2) + + # Append the key-value pair to the ADDITIONAL_ARGS string + ADDITIONAL_ARGS="$ADDITIONAL_ARGS -${key} ${value}" + done + fi +} + if [ "$NETWORK" = "mainnet" ]; then MEV_NETWORK="mainnet" elif [ "$NETWORK" = "holesky" ]; then @@ -12,5 +32,6 @@ else exit 1 fi -# Run MEV-boost -exec /app/mev-boost -${MEV_NETWORK} -addr 0.0.0.0:${MEV_BOOST_PORT} -relay-check -relays ${MEV_BOOST_RELAYS} \ No newline at end of file +parse_additional_flags + +exec /app/mev-boost -${MEV_NETWORK} -addr 0.0.0.0:${MEV_BOOST_PORT} -relay-check -relays ${MEV_BOOST_RELAYS} ${ADDITIONAL_ARGS} diff --git a/install/deploy/templates/eth1.tmpl b/install/deploy/templates/eth1.tmpl index 6451c9447..1657b186d 100644 --- a/install/deploy/templates/eth1.tmpl +++ b/install/deploy/templates/eth1.tmpl @@ -16,7 +16,7 @@ services: ports: [ "{{$p2p}}:{{$p2p}}/udp", "{{$p2p}}:{{$p2p}}/tcp"{{.GetEcOpenApiPorts}} ] volumes: - {{.ExecutionClientDataVolume}}:/ethclient - - /usr/share/rocketpool/scripts:/usr/share/rocketpool/scripts:ro + - /opt/rocketpool/scripts:/opt/rocketpool/scripts:ro - /var/lib/rocketpool/data/{{.ProjectName}}:/secrets networks: - net @@ -49,7 +49,7 @@ services: - RP_GETH_ARCHIVE_MODE={{.LocalExecutionClient.Geth.ArchiveMode}} {{- end}} entrypoint: sh - command: "/usr/share/rocketpool/scripts/{{.GetEcStartScript}}" + command: "/opt/rocketpool/scripts/{{.GetEcStartScript}}" cap_drop: - all cap_add: diff --git a/install/deploy/templates/eth2.tmpl b/install/deploy/templates/eth2.tmpl index d755a2f34..d11e8182f 100644 --- a/install/deploy/templates/eth2.tmpl +++ b/install/deploy/templates/eth2.tmpl @@ -24,7 +24,7 @@ services: {{- end}} volumes: - {{.BeaconNodeDataVolume}}:/ethclient - - /usr/share/rocketpool/scripts:/usr/share/rocketpool/scripts:ro + - /opt/rocketpool/scripts:/opt/rocketpool/scripts:ro - /var/lib/rocketpool/data/{{.ProjectName}}:/secrets:ro networks: - net @@ -61,7 +61,7 @@ services: - BN_P2P_QUIC_PORT={{.LocalBeaconClient.Lighthouse.P2pQuicPort}} {{- end}} entrypoint: sh - command: "/usr/share/rocketpool/scripts/{{.GetBnStartScript}}" + command: "/opt/rocketpool/scripts/{{.GetBnStartScript}}" cap_drop: - all cap_add: diff --git a/install/deploy/templates/exporter.tmpl b/install/deploy/templates/exporter.tmpl index 2f8553032..a8175cb22 100644 --- a/install/deploy/templates/exporter.tmpl +++ b/install/deploy/templates/exporter.tmpl @@ -29,7 +29,7 @@ services: - "/sys:/host/sys:ro,rslave" - "/var/lib/node_exporter/textfile_collector:/host/textfile_collector:ro" {{- if .Metrics.Exporter.RootFs.Value }} - - "/:/rootfs:ro" + - "/:/rootfs:ro,rslave" {{- end}} network_mode: host networks: diff --git a/install/deploy/templates/mev-boost.tmpl b/install/deploy/templates/mev-boost.tmpl index 113566a3c..084a45dc8 100644 --- a/install/deploy/templates/mev-boost.tmpl +++ b/install/deploy/templates/mev-boost.tmpl @@ -12,15 +12,16 @@ services: restart: unless-stopped ports: [{{.GetMevBoostOpenPorts}}] volumes: - - /usr/share/rocketpool/scripts:/usr/share/rocketpool/scripts:ro + - /opt/rocketpool/scripts:/opt/rocketpool/scripts:ro networks: - net environment: - NETWORK={{.Network}} - MEV_BOOST_PORT={{.MevBoost.Port}} - MEV_BOOST_RELAYS={{.MevBoost.GetRelayString}} + - MEV_BOOST_ADDITIONAL_FLAGS={{.MevBoost.AdditionalFlags}} entrypoint: sh - command: "/usr/share/rocketpool/scripts/{{.GetMevBoostStartScript}}" + command: "/opt/rocketpool/scripts/{{.GetMevBoostStartScript}}" cap_drop: - all cap_add: diff --git a/install/deploy/templates/validator.tmpl b/install/deploy/templates/validator.tmpl index d4fed0a8e..20913db98 100644 --- a/install/deploy/templates/validator.tmpl +++ b/install/deploy/templates/validator.tmpl @@ -13,7 +13,7 @@ services: restart: unless-stopped stop_grace_period: 3m volumes: - - /usr/share/rocketpool/scripts:/usr/share/rocketpool/scripts:ro + - /opt/rocketpool/scripts:/opt/rocketpool/scripts:ro - {{.GetValidatorsFolderPath}}:{{.GetValidatorsFolderPath}} - {{.GetAddonsFolderPath}}:{{.GetAddonsFolderPath}} networks: @@ -45,7 +45,7 @@ services: - TEKU_SHUT_DOWN_WHEN_SLASHED={{.ValidatorClient.Teku.UseSlashingProtection}} {{- end}} entrypoint: sh - command: "/usr/share/rocketpool/scripts/{{.GetVcStartScript}}" + command: "/opt/rocketpool/scripts/{{.GetVcStartScript}}" cap_drop: - all cap_add: diff --git a/install/install.sh b/install/install.sh index e97106912..244d948ba 100755 --- a/install/install.sh +++ b/install/install.sh @@ -336,12 +336,11 @@ install() { fi # Create rocket pool dir & files - RP_BIN_PATH=/usr/bin/rocketpool - RP_SHARE_PATH=/usr/share/rocketpool + RP_SYS_PATH=/opt/rocketpool RP_VAR_PATH=/var/lib/rocketpool progress 4 "Creating Rocket Pool directory structure..." - { mkdir -p "$RP_SHARE_PATH" || fail "Could not create the Rocket Pool resources directory."; } >&2 + { mkdir -p "$RP_SYS_PATH" || fail "Could not create the Rocket Pool resources directory."; } >&2 { mkdir -p "$RP_VAR_PATH/data" || fail "Could not create the Rocket Pool system data directory."; } >&2 { chmod 0700 "$RP_VAR_PATH/data" || fail "Could not set the Rocket Pool data directory permissions."; } >&2 @@ -359,11 +358,11 @@ install() { # Copy package files progress 6 "Copying package files to Rocket Pool system directory..." - { cp -r "$PACKAGE_FILES_PATH/addons" "$RP_SHARE_PATH" || fail "Could not copy addons folder to the Rocket Pool system directory."; } >&2 - { cp -r "$PACKAGE_FILES_PATH/override" "$RP_SHARE_PATH" || fail "Could not copy override folder to the Rocket Pool system directory."; } >&2 - { cp -r "$PACKAGE_FILES_PATH/scripts" "$RP_SHARE_PATH" || fail "Could not copy scripts folder to the Rocket Pool system directory."; } >&2 - { cp -r "$PACKAGE_FILES_PATH/templates" "$RP_SHARE_PATH" || fail "Could not copy templates folder to the Rocket Pool system directory."; } >&2 - { find "$RP_SHARE_PATH/scripts" -name "*.sh" -exec chmod +x {} \; 2>/dev/null || fail "Could not set executable permissions on package files."; } >&2 + { cp -r "$PACKAGE_FILES_PATH/addons" "$RP_SYS_PATH" || fail "Could not copy addons folder to the Rocket Pool system directory."; } >&2 + { cp -r "$PACKAGE_FILES_PATH/override" "$RP_SYS_PATH" || fail "Could not copy override folder to the Rocket Pool system directory."; } >&2 + { cp -r "$PACKAGE_FILES_PATH/scripts" "$RP_SYS_PATH" || fail "Could not copy scripts folder to the Rocket Pool system directory."; } >&2 + { cp -r "$PACKAGE_FILES_PATH/templates" "$RP_SYS_PATH" || fail "Could not copy templates folder to the Rocket Pool system directory."; } >&2 + { find "$RP_SYS_PATH/scripts" -name "*.sh" -exec chmod +x {} \; 2>/dev/null || fail "Could not set executable permissions on package files."; } >&2 # Clean up unnecessary files from old installations diff --git a/install/packages/debian/debian/changelog b/install/packages/debian/debian/changelog index b05936161..058158c02 100644 --- a/install/packages/debian/debian/changelog +++ b/install/packages/debian/debian/changelog @@ -1,4 +1,17 @@ -rocketpool (2.0.0~b3) UNRELEASED; urgency=medium +rocketpool (2.0.0~b4) unstable; urgency=medium + + * Fixed a bug while rescuing dissolved minipools + * Fixed a bug while setting the RPL withdrawal address + * Updated the initialize voting command to mirror V1's behaviour + * Added a check to handle a case where bond reductions are disabled + * Updated the initialize voting command text + * Updated the node status text to mirror V1 + * Fixed a bug while submitting odao proposals + * Fixed a bug with the odao and security list/detail commands (eg. rocketpool security proposal details or rocketpool odao proposals list) + + -- Rocket Pool Mon, 23 Sep 2024 01:23:54 +0000 + +rocketpool (2.0.0~b3) unstable; urgency=medium * Fixed the `minipool close` issue. * Fixed the client mode logic. diff --git a/install/packages/debian/debian/rules b/install/packages/debian/debian/rules index 4377cd077..1c421ea7c 100755 --- a/install/packages/debian/debian/rules +++ b/install/packages/debian/debian/rules @@ -38,8 +38,8 @@ override_dh_auto_install: # Create the folder structure and copy the deploy files over install -dm 0700 debian/rocketpool/var/lib/rocketpool/data install -dm 0700 debian/rocketpool/var/lib/rocketpool/global - mkdir -p debian/rocketpool/usr/share/rocketpool/ - cp -r deploy/* debian/rocketpool/usr/share/rocketpool/ - chmod -R +x debian/rocketpool/usr/share/rocketpool/scripts + mkdir -p debian/rocketpool/opt/rocketpool/ + cp -r deploy/* debian/rocketpool/opt/rocketpool/ + chmod -R +x debian/rocketpool/opt/rocketpool/scripts override_dh_auto_test: diff --git a/rocketpool-cli/client/compose.go b/rocketpool-cli/client/compose.go index 1c1d65671..7b30b7d31 100644 --- a/rocketpool-cli/client/compose.go +++ b/rocketpool-cli/client/compose.go @@ -18,10 +18,10 @@ import ( ) const ( - templatesDir string = "/usr/share/rocketpool/templates" - addonsSourceDir string = "/usr/share/rocketpool/addons" - overrideSourceDir string = "/usr/share/rocketpool/override" - nativeScriptsSourceDir string = "/usr/share/rocketpool/scripts/native" + templatesDir string = "/opt/rocketpool/templates" + addonsSourceDir string = "/opt/rocketpool/addons" + overrideSourceDir string = "/opt/rocketpool/override" + nativeScriptsSourceDir string = "/opt/rocketpool/scripts/native" overrideDir string = "override" runtimeDir string = "runtime" extraScrapeJobsDir string = "extra-scrape-jobs" diff --git a/rocketpool-cli/client/service.go b/rocketpool-cli/client/service.go index 4346ad080..3e616a004 100644 --- a/rocketpool-cli/client/service.go +++ b/rocketpool-cli/client/service.go @@ -33,55 +33,44 @@ func (c *Client) downloadAndRun( url string, verbose bool, version string, - useLocalInstaller bool, extraFlags []string, ) error { var script []byte - flags := []string{ - "-v", shellescape.Quote(version), + // Download the installation script + resp, err := http.Get(fmt.Sprintf(url, version)) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected http status downloading %s script: %d", name, resp.StatusCode) } - flags = append(flags, extraFlags...) - if useLocalInstaller { - // Make sure it exists - _, err := os.Stat(name) - if errors.Is(err, os.ErrNotExist) { - return fmt.Errorf("local script [%s] does not exist", name) - } - if err != nil { - return fmt.Errorf("error checking script [%s]: %w", name, err) - } + // Sanity check that the script octet length matches content-length + script, err = io.ReadAll(resp.Body) + if err != nil { + return err + } - // Read it - script, err = os.ReadFile(name) - if err != nil { - return fmt.Errorf("error reading local script [%s]: %w", name, err) - } + if fmt.Sprint(len(script)) != resp.Header.Get("content-length") { + return fmt.Errorf("downloaded script length %d did not match content-length header %s", len(script), resp.Header.Get("content-length")) + } - // Set the "local mode" flag - flags = append(flags, "-l") - } else { - // Download the installation script - resp, err := http.Get(fmt.Sprintf(url, version)) - if err != nil { - return err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("unexpected http status downloading %s script: %d", name, resp.StatusCode) - } + return c.runScript(script, version, verbose, extraFlags) +} - // Sanity check that the script octet length matches content-length - script, err = io.ReadAll(resp.Body) - if err != nil { - return err - } +func (c *Client) runScript( + script []byte, + version string, + verbose bool, + extraFlags []string, +) error { - if fmt.Sprint(len(script)) != resp.Header.Get("content-length") { - return fmt.Errorf("downloaded script length %d did not match content-length header %s", len(script), resp.Header.Get("content-length")) - } + flags := []string{ + "-v", shellescape.Quote(version), } + flags = append(flags, extraFlags...) // Get the escalation command escalationCmd, err := c.getEscalationCommand() @@ -134,8 +123,29 @@ func (c *Client) downloadAndRun( return nil } +func readLocalScript(path string) ([]byte, error) { + // Make sure it exists + _, err := os.Stat(path) + if errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("local script [%s] does not exist", path) + } + if err != nil { + return nil, fmt.Errorf("error checking script [%s]: %w", path, err) + } + + // Read it + script, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("error reading local script [%s]: %w", path, err) + } + + return script, nil +} + // Install the Rocket Pool service -func (c *Client) InstallService(verbose bool, noDeps bool, version string, path string, useLocalInstaller bool) error { +// installScriptPath is optional. If unset, the install script is downloaded from github. +func (c *Client) InstallService(verbose bool, noDeps bool, version string, path string, installScriptPath string) error { + // Get installation script flags flags := []string{} if path != "" { @@ -145,12 +155,29 @@ func (c *Client) InstallService(verbose bool, noDeps bool, version string, path flags = append(flags, "-d") } - return c.downloadAndRun(installerName, installerURL, verbose, version, useLocalInstaller, flags) + if installScriptPath != "" { + script, err := readLocalScript(installScriptPath) + if err != nil { + return err + } + // Set the "local mode" flag + flags = append(flags, "-l") + return c.runScript(script, version, verbose, flags) + } + + return c.downloadAndRun(installerName, installerURL, verbose, version, flags) } // Install the update tracker -func (c *Client) InstallUpdateTracker(verbose bool, version string, useLocalInstaller bool) error { - return c.downloadAndRun(updateTrackerInstallerName, updateTrackerURL, verbose, version, useLocalInstaller, nil) +func (c *Client) InstallUpdateTracker(verbose bool, version string, installScriptPath string) error { + if installScriptPath != "" { + script, err := readLocalScript(installScriptPath) + if err != nil { + return err + } + return c.runScript(script, version, verbose, nil) + } + return c.downloadAndRun(updateTrackerInstallerName, updateTrackerURL, verbose, version, nil) } // Start the Rocket Pool service @@ -243,23 +270,6 @@ func (c *Client) PrintNodeLogs(composeFiles []string, tail string, logPaths ...s return c.printOutput(cmd) } -// Print the Rocket Pool service stats -func (c *Client) PrintServiceStats(composeFiles []string) error { - // Get service container IDs - cmd, err := c.compose(composeFiles, "ps -q") - if err != nil { - return err - } - containers, err := c.readOutput(cmd) - if err != nil { - return err - } - containerIds := strings.Split(strings.TrimSpace(string(containers)), "\n") - - // Print stats - return c.printOutput(fmt.Sprintf("docker stats %s", strings.Join(containerIds, " "))) -} - // Print the Rocket Pool service compose config func (c *Client) PrintServiceCompose(composeFiles []string) error { cmd, err := c.compose(composeFiles, "config") diff --git a/rocketpool-cli/commands/minipool/reduce-bond.go b/rocketpool-cli/commands/minipool/reduce-bond.go index e29948325..2e3022330 100644 --- a/rocketpool-cli/commands/minipool/reduce-bond.go +++ b/rocketpool-cli/commands/minipool/reduce-bond.go @@ -26,6 +26,12 @@ func reduceBondAmount(c *cli.Context) error { return err } + // Print message and exit if bond reductions are disabled + if details.Data.BondReductionDisabled { + fmt.Println("Cannot perform a bond reduction: bond reductions are currently disabled") + return nil + } + // Check the fee distributor if !details.Data.IsFeeDistributorInitialized { fmt.Println("Minipools cannot have their bonds reduced until your fee distributor has been initialized.") diff --git a/rocketpool-cli/commands/node/deposit.go b/rocketpool-cli/commands/node/deposit.go index dddf496e4..3b363f110 100644 --- a/rocketpool-cli/commands/node/deposit.go +++ b/rocketpool-cli/commands/node/deposit.go @@ -23,6 +23,7 @@ const ( maxSlippageFlag string = "max-slippage" saltFlag string = "salt" defaultMaxNodeFeeSlippage float64 = 0.01 // 1% below current network fee + depositWarningMessage string = "NOTE: by creating a new minipool, your node will automatically initialize voting power to itself. If you would like to delegate your on-chain voting power, you should run the command `rocketpool pdao initialize-voting` before creating a new minipool." ) type deposit struct { @@ -44,6 +45,12 @@ func newDepositPrompts(c *cli.Context, rp *client.Client, soloConversionPubkey * return nil, nil } + // If hotfix is live and voting isn't initialized, display a warning + err = warnIfVotingUninitialized(rp, c, depositWarningMessage) + if err != nil { + return nil, err + } + // Check if the fee distributor has been initialized feeDistributorResponse, err := rp.Api.Node.InitializeFeeDistributor() if err != nil { @@ -136,7 +143,7 @@ func newDepositPrompts(c *cli.Context, rp *client.Client, soloConversionPubkey * } } else { // Prompt for min node fee - if nodeFeeResponse.Data.MinNodeFee == nodeFeeResponse.Data.MaxNodeFee { + if nodeFeeResponse.Data.MinNodeFee.Cmp(nodeFeeResponse.Data.MaxNodeFee) == 0 { fmt.Printf("Your minipool will use the current fixed commission rate of %.2f%%.\n", eth.WeiToEth(nodeFeeResponse.Data.MinNodeFee)*100) minNodeFee = eth.WeiToEth(nodeFeeResponse.Data.MinNodeFee) } else { diff --git a/rocketpool-cli/commands/node/stake-rpl.go b/rocketpool-cli/commands/node/stake-rpl.go index 0452d5c89..b5f78d4c0 100644 --- a/rocketpool-cli/commands/node/stake-rpl.go +++ b/rocketpool-cli/commands/node/stake-rpl.go @@ -17,7 +17,8 @@ import ( ) const ( - swapFlag string = "swap" + swapFlag string = "swap" + stakeRPLWarningMessage string = "NOTE: by staking RPL, your node will automatically initialize voting power to itself. If you would like to delegate your on-chain voting power, you should run the command `rocketpool pdao initialize-voting` before staking RPL." ) func nodeStakeRpl(c *cli.Context) error { @@ -33,6 +34,12 @@ func nodeStakeRpl(c *cli.Context) error { return err } + // If hotfix is live and voting isn't initialized, display a warning + err = warnIfVotingUninitialized(rp, c, stakeRPLWarningMessage) + if err != nil { + return err + } + // If a custom nonce is set, print the multi-transaction warning if rp.Context.Nonce.Cmp(common.Big0) > 0 { utils.PrintMultiTransactionNonceWarning() @@ -128,7 +135,10 @@ func nodeStakeRpl(c *cli.Context) error { // Run the stake TX validated, err := tx.HandleTx(c, rp, stakeResponse.Data.StakeTxInfo, - fmt.Sprintf("Are you sure you want to stake %.6f RPL? You will not be able to unstake this RPL until you exit your validators and close your minipools, or reach over 100%% collateral!", math.RoundDown(eth.WeiToEth(amountWei), 6)), + fmt.Sprintf("Are you sure you want to stake %.6f RPL? You will not be able to unstake this RPL until you exit your validators and close your minipools, or reach %.6f staked RPL (%.0f%% of bonded eth)!", + math.RoundDown(eth.WeiToEth(amountWei), 6), + math.RoundDown(eth.WeiToEth(status.Data.MaximumRplStake), 6), + eth.WeiToEth(status.Data.MaximumStakeFraction)*100), "staking RPL", "Staking RPL...", ) diff --git a/rocketpool-cli/commands/node/utils.go b/rocketpool-cli/commands/node/utils.go index 51bbecb36..0e3ec2b4c 100644 --- a/rocketpool-cli/commands/node/utils.go +++ b/rocketpool-cli/commands/node/utils.go @@ -21,6 +21,7 @@ import ( "github.com/rocket-pool/node-manager-core/utils/math" "github.com/rocket-pool/smartnode/v2/rocketpool-cli/client" "github.com/rocket-pool/smartnode/v2/rocketpool-cli/utils" + "github.com/rocket-pool/smartnode/v2/rocketpool-cli/utils/terminal" "github.com/rocket-pool/smartnode/v2/rocketpool-cli/utils/tx" ) @@ -380,3 +381,29 @@ func SwapRpl(c *cli.Context, rp *client.Client, amountWei *big.Int) error { fmt.Printf("Successfully swapped %.6f legacy RPL for new RPL.\n", math.RoundDown(eth.WeiToEth(amountWei), 6)) return nil } + +// Display a warning if hotfix is live and voting is uninitialized +func warnIfVotingUninitialized(rp *client.Client, c *cli.Context, warningMessage string) error { + hotfix, err := rp.Api.Network.IsHoustonHotfixDeployed() + if err != nil { + return err + } + + if hotfix.Data.IsHoustonHotfixDeployed { + // Check if voting is initialized + votingInitializedResponse, err := rp.Api.PDao.IsVotingInitialized() + if err != nil { + return fmt.Errorf("error checking if voting is initialized: %w", err) + } + if !votingInitializedResponse.Data.VotingInitialized { + fmt.Println("Your voting power hasn't been initialized yet. Please visit https://docs.rocketpool.net/guides/houston/participate#initializing-voting to learn more.") + // Post a warning about initializing voting + if !(c.Bool("yes") || utils.Confirm(fmt.Sprintf("%s%s%s\nWould you like to continue?", terminal.ColorYellow, warningMessage, terminal.ColorReset))) { + fmt.Println("Cancelled.") + return fmt.Errorf("operation cancelled by user") + } + } + } + + return nil +} diff --git a/rocketpool-cli/commands/pdao/commands.go b/rocketpool-cli/commands/pdao/commands.go index 0d66caa55..bd240503a 100644 --- a/rocketpool-cli/commands/pdao/commands.go +++ b/rocketpool-cli/commands/pdao/commands.go @@ -160,7 +160,7 @@ func RegisterCommands(app *cli.App, name string, aliases []string) { Usage: "Unlocks a node operator's voting power (only required for node operators who registered before governance structure was in place)", Action: func(c *cli.Context) error { // Run - return initializeVoting(c) + return initializeVotingPrompt(c) }, }, diff --git a/rocketpool-cli/commands/pdao/initialize-voting.go b/rocketpool-cli/commands/pdao/initialize-voting.go index 32ec1b3fe..aea6e59bc 100644 --- a/rocketpool-cli/commands/pdao/initialize-voting.go +++ b/rocketpool-cli/commands/pdao/initialize-voting.go @@ -2,14 +2,46 @@ package pdao import ( "fmt" + "strings" "github.com/rocket-pool/node-manager-core/utils/input" "github.com/rocket-pool/smartnode/v2/rocketpool-cli/client" "github.com/rocket-pool/smartnode/v2/rocketpool-cli/utils" + cliutils "github.com/rocket-pool/smartnode/v2/rocketpool-cli/utils" "github.com/rocket-pool/smartnode/v2/rocketpool-cli/utils/tx" "github.com/urfave/cli/v2" ) +func initializeVotingPrompt(c *cli.Context) error { + fmt.Println("Thanks for initializing your voting power!") + fmt.Println() + fmt.Println("You have two options:") + fmt.Println() + fmt.Println("1. Vote directly (delegate vote power to yourself)") + fmt.Println(" This will allow you to vote on proposals directly,") + fmt.Println(" allowing you to personally shape the direction of the protocol.") + fmt.Println() + fmt.Println("2. Delegate your vote") + fmt.Println(" This will delegate your vote power to someone you trust,") + fmt.Println(" giving them the power to vote on your behalf. You will have the option to override.") + fmt.Println() + fmt.Printf("You can see a list of existing public delegates at %s,\n", "https://delegates.rocketpool.net") + fmt.Println("however, you can delegate to any node address.") + fmt.Println() + fmt.Printf("Learn more about how this all works via: %s\n", "https://docs.rocketpool.net/guides/houston/participate#participating-in-on-chain-pdao-proposals") + fmt.Println() + + inputString := cliutils.Prompt("Please type `direct` or `delegate` to continue:", "^(?i)(direct|delegate)$", "Please type `direct` or `delegate` to continue:") + switch strings.ToLower(inputString) { + case "direct": + return initializeVoting(c) + case "delegate": + return initializeVotingWithDelegate(c) + } + return nil + +} + func initializeVoting(c *cli.Context) error { // Get RP client rp, err := client.NewClientFromCtx(c) @@ -17,6 +49,44 @@ func initializeVoting(c *cli.Context) error { return err } + // Get the TX + response, err := rp.Api.PDao.InitializeVoting() + if err != nil { + return err + } + + // Verify + if response.Data.VotingInitialized { + fmt.Println("Voting has already been initialized for your node.") + return nil + } + + // Run the TX + validated, err := tx.HandleTx(c, rp, response.Data.TxInfo, + "Are you sure you want to initialize voting so you can vote on Protocol DAO proposals?", + "initialize voting", + "Initializing voting...", + ) + if err != nil { + return err + } + if !validated { + return nil + } + + // Log & return + fmt.Printf("Successfully initialized voting. Your node can now vote on Protocol DAO proposals.") + return nil + +} + +func initializeVotingWithDelegate(c *cli.Context) error { + // Get RP client + rp, err := client.NewClientFromCtx(c) + if err != nil { + return err + } + // Get the address delegateAddressString := c.String("address") if delegateAddressString == "" { @@ -28,7 +98,7 @@ func initializeVoting(c *cli.Context) error { } // Get the TX - response, err := rp.Api.PDao.InitializeVoting(delegateAddress) + response, err := rp.Api.PDao.InitializeVotingWithDelegate(delegateAddress) if err != nil { return err } @@ -41,7 +111,7 @@ func initializeVoting(c *cli.Context) error { // Run the TX validated, err := tx.HandleTx(c, rp, response.Data.TxInfo, - "Are you sure you want to initialize voting so you can vote on Protocol DAO proposals?", + "Are you sure you want to initialize voting?", "initialize voting", "Initializing voting...", ) @@ -53,6 +123,6 @@ func initializeVoting(c *cli.Context) error { } // Log & return - fmt.Printf("Successfully initialized voting. Your node can now vote on Protocol DAO proposals.") + fmt.Printf("Successfully initialized voting.") return nil } diff --git a/rocketpool-cli/commands/service/commands.go b/rocketpool-cli/commands/service/commands.go index 6bc112ca5..f4616261f 100644 --- a/rocketpool-cli/commands/service/commands.go +++ b/rocketpool-cli/commands/service/commands.go @@ -286,13 +286,11 @@ func RegisterCommands(app *cli.App, name string, aliases []string) { { Name: "stats", Aliases: []string{"a"}, - Usage: "View the Rocket Pool service stats", + Usage: "DEPRECATED - No longer supported", Action: func(c *cli.Context) error { - // Validate args - utils.ValidateArgCount(c, 0) // Run command - return serviceStats(c) + return serviceStats() }, }, @@ -342,6 +340,7 @@ func RegisterCommands(app *cli.App, name string, aliases []string) { cliutils.YesFlag, installUpdateTrackerVerboseFlag, installUpdateTrackerVersionFlag, + installLocalFlag, }, Action: func(c *cli.Context) error { // Validate args diff --git a/rocketpool-cli/commands/service/install-update-tracker.go b/rocketpool-cli/commands/service/install-update-tracker.go index 73550c911..7affa87d9 100644 --- a/rocketpool-cli/commands/service/install-update-tracker.go +++ b/rocketpool-cli/commands/service/install-update-tracker.go @@ -41,7 +41,7 @@ func installUpdateTracker(c *cli.Context) error { } // Install service - err = rp.InstallUpdateTracker(c.Bool(installUpdateTrackerVerboseFlag.Name), c.String(installUpdateTrackerVersionFlag.Name), c.Bool(installLocalFlag.Name)) + err = rp.InstallUpdateTracker(c.Bool(installUpdateTrackerVerboseFlag.Name), c.String(installUpdateTrackerVersionFlag.Name), c.String(installLocalFlag.Name)) if err != nil { return err } diff --git a/rocketpool-cli/commands/service/install.go b/rocketpool-cli/commands/service/install.go index 694a4be47..680e23e62 100644 --- a/rocketpool-cli/commands/service/install.go +++ b/rocketpool-cli/commands/service/install.go @@ -37,10 +37,10 @@ var ( Aliases: []string{"u"}, Usage: "Certain configuration values are reset when the Smart Node is updated, such as Docker container tags; use this flag to force that reset, even if the Smart Node hasn't been updated", } - installLocalFlag *cli.BoolFlag = &cli.BoolFlag{ + installLocalFlag *cli.StringFlag = &cli.StringFlag{ Name: "local-script", Aliases: []string{"l"}, - Usage: fmt.Sprintf("Use a local installer script instead of pulling it down from the source repository. The script and the installer package must be in your current working directory.%sMake sure you absolutely trust the script before using this flag.%s", terminal.ColorRed, terminal.ColorReset), + Usage: fmt.Sprintf("Use a local installer script instead of pulling it down from the source repository. The script and the installer package must be in your current working directory.\n%sMake sure you absolutely trust the script before using this flag.%s", terminal.ColorRed, terminal.ColorReset), } ) @@ -67,7 +67,7 @@ func installService(c *cli.Context) error { c.Bool(installNoDepsFlag.Name), c.String(installVersionFlag.Name), c.String(installPathFlag.Name), - c.Bool(installLocalFlag.Name), + c.String(installLocalFlag.Name), ) if err != nil { return err diff --git a/rocketpool-cli/commands/service/stats.go b/rocketpool-cli/commands/service/stats.go index c510f9284..a6fba56d3 100644 --- a/rocketpool-cli/commands/service/stats.go +++ b/rocketpool-cli/commands/service/stats.go @@ -1,18 +1,11 @@ package service import ( - "github.com/rocket-pool/smartnode/v2/rocketpool-cli/client" - "github.com/urfave/cli/v2" + "fmt" ) // View the Rocket Pool service stats -func serviceStats(c *cli.Context) error { - // Get RP client - rp, err := client.NewClientFromCtx(c) - if err != nil { - return err - } - - // Print service stats - return rp.PrintServiceStats(getComposeFiles(c)) +func serviceStats() error { + fmt.Println("No longer supported - please run `docker stats -a` instead.") + return nil } diff --git a/rocketpool-daemon/api/minipool/rescue-dissolved-details.go b/rocketpool-daemon/api/minipool/rescue-dissolved-details.go index 3083d2fb0..ef12c71db 100644 --- a/rocketpool-daemon/api/minipool/rescue-dissolved-details.go +++ b/rocketpool-daemon/api/minipool/rescue-dissolved-details.go @@ -89,6 +89,7 @@ func (c *minipoolRescueDissolvedDetailsContext) PrepareData(addresses []common.A MinipoolState: mpCommon.Status.Formatted(), IsFinalized: mpCommon.IsFinalised.Get(), MinipoolVersion: mpCommon.Version, + BeaconBalance: big.NewInt(0), } if mpDetails.MinipoolState != rptypes.MinipoolStatus_Dissolved || mpDetails.IsFinalized { diff --git a/rocketpool-daemon/api/network/handler.go b/rocketpool-daemon/api/network/handler.go index 2c1a41716..2417944d6 100644 --- a/rocketpool-daemon/api/network/handler.go +++ b/rocketpool-daemon/api/network/handler.go @@ -34,6 +34,7 @@ func NewNetworkHandler(logger *log.Logger, ctx context.Context, serviceProvider &networkPriceContextFactory{h}, &networkStatsContextFactory{h}, &networkTimezoneContextFactory{h}, + &networkHotfixDeployedContextFactory{h}, } return h } diff --git a/rocketpool-daemon/api/network/is-hotfix-deployed.go b/rocketpool-daemon/api/network/is-hotfix-deployed.go new file mode 100644 index 000000000..701f19866 --- /dev/null +++ b/rocketpool-daemon/api/network/is-hotfix-deployed.go @@ -0,0 +1,62 @@ +package network + +import ( + "fmt" + "net/url" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/gorilla/mux" + "github.com/rocket-pool/node-manager-core/api/server" + "github.com/rocket-pool/node-manager-core/api/types" + "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/state" + "github.com/rocket-pool/smartnode/v2/shared/types/api" +) + +// =============== +// === Factory === +// =============== + +type networkHotfixDeployedContextFactory struct { + handler *NetworkHandler +} + +func (f *networkHotfixDeployedContextFactory) Create(args url.Values) (*networkHotfixDeployedContext, error) { + c := &networkHotfixDeployedContext{ + handler: f.handler, + } + return c, nil +} + +func (f *networkHotfixDeployedContextFactory) RegisterRoute(router *mux.Router) { + server.RegisterQuerylessGet[*networkHotfixDeployedContext, api.NetworkHotfixDeployedData]( + router, "is-hotfix-deployed", f, f.handler.logger.Logger, f.handler.serviceProvider.ServiceProvider, + ) +} + +// =============== +// === Context === +// =============== + +type networkHotfixDeployedContext struct { + handler *NetworkHandler +} + +func (c *networkHotfixDeployedContext) PrepareData(data *api.NetworkHotfixDeployedData, opts *bind.TransactOpts) (types.ResponseStatus, error) { + sp := c.handler.serviceProvider + rp := sp.GetRocketPool() + + // Requirements + status, err := sp.RequireRocketPoolContracts(c.handler.ctx) + if err != nil { + return status, err + } + + // Bindings + houstonHotfixDeployed, err := state.IsHoustonHotfixDeployed(rp, nil) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("error getting the protocol version: %w", err) + } + data.IsHoustonHotfixDeployed = houstonHotfixDeployed + + return types.ResponseStatus_Success, nil +} diff --git a/rocketpool-daemon/api/node/set-rpl-withdrawal-address.go b/rocketpool-daemon/api/node/set-rpl-withdrawal-address.go index 2cbd3b9c1..ab98a9547 100644 --- a/rocketpool-daemon/api/node/set-rpl-withdrawal-address.go +++ b/rocketpool-daemon/api/node/set-rpl-withdrawal-address.go @@ -82,6 +82,7 @@ func (c *nodeSetRplWithdrawalAddressContext) GetState(mc *batch.MultiCaller) { c.node.IsRplWithdrawalAddressSet, c.node.RplWithdrawalAddress, c.node.PrimaryWithdrawalAddress, + c.node.RplStake, ) } @@ -90,6 +91,7 @@ func (c *nodeSetRplWithdrawalAddressContext) PrepareData(data *api.NodeSetRplWit data.PrimaryAddressDiffers = (c.node.PrimaryWithdrawalAddress.Get() != c.nodeAddress || isRplWithdrawalAddressSet) data.RplAddressDiffers = (isRplWithdrawalAddressSet && c.node.RplWithdrawalAddress.Get() != c.nodeAddress) data.CanSet = !(data.PrimaryAddressDiffers || data.RplAddressDiffers) + data.RplStake = c.node.RplStake.Get() if data.CanSet { txInfo, err := c.node.SetRplWithdrawalAddress(c.address, c.confirm, opts) diff --git a/rocketpool-daemon/api/node/stake-rpl.go b/rocketpool-daemon/api/node/stake-rpl.go index 912044255..0ca8de00c 100644 --- a/rocketpool-daemon/api/node/stake-rpl.go +++ b/rocketpool-daemon/api/node/stake-rpl.go @@ -10,12 +10,14 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/gorilla/mux" batch "github.com/rocket-pool/batch-query" + "github.com/rocket-pool/rocketpool-go/v2/dao/protocol" "github.com/rocket-pool/rocketpool-go/v2/node" "github.com/rocket-pool/rocketpool-go/v2/rocketpool" "github.com/rocket-pool/rocketpool-go/v2/tokens" "github.com/rocket-pool/node-manager-core/api/server" "github.com/rocket-pool/node-manager-core/api/types" + "github.com/rocket-pool/node-manager-core/eth" "github.com/rocket-pool/node-manager-core/utils/input" "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/utils" "github.com/rocket-pool/smartnode/v2/shared/types/api" @@ -60,6 +62,7 @@ type nodeStakeRplContext struct { node *node.Node balance *big.Int allowance *big.Int + pSettings *protocol.ProtocolDaoSettings } func (c *nodeStakeRplContext) Initialize() (types.ResponseStatus, error) { @@ -86,6 +89,11 @@ func (c *nodeStakeRplContext) Initialize() (types.ResponseStatus, error) { if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error creating RocketNodeStaking binding: %w", err) } + pMgr, err := protocol.NewProtocolDaoManager(c.rp) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("error creating pDAO manager binding: %w", err) + } + c.pSettings = pMgr.Settings c.nsAddress = rns.Address return types.ResponseStatus_Success, nil } @@ -93,12 +101,17 @@ func (c *nodeStakeRplContext) Initialize() (types.ResponseStatus, error) { func (c *nodeStakeRplContext) GetState(mc *batch.MultiCaller) { c.rpl.BalanceOf(mc, &c.balance, c.nodeAddress) c.rpl.GetAllowance(mc, &c.allowance, c.nodeAddress, c.nsAddress) + eth.AddQueryablesToMulticall(mc, + c.node.MaximumRplStake, + ) } func (c *nodeStakeRplContext) PrepareData(data *api.NodeStakeRplData, opts *bind.TransactOpts) (types.ResponseStatus, error) { data.InsufficientBalance = (c.amount.Cmp(c.balance) > 0) data.Allowance = c.allowance data.CanStake = !(data.InsufficientBalance) + data.MaximumStakeFraction = c.pSettings.Node.MaximumPerMinipoolStake.Raw() + data.MaximumRplStake = c.node.MaximumRplStake.Get() if data.CanStake { if c.allowance.Cmp(c.amount) < 0 { diff --git a/rocketpool-daemon/api/node/withdraw-eth.go b/rocketpool-daemon/api/node/withdraw-eth.go index b811586b0..fb5b1c863 100644 --- a/rocketpool-daemon/api/node/withdraw-eth.go +++ b/rocketpool-daemon/api/node/withdraw-eth.go @@ -89,7 +89,7 @@ func (c *nodeWithdrawEthContext) PrepareData(data *api.NodeWithdrawEthData, opts ethBalance := c.node.DonatedEthBalance.Get() hasDifferentPrimaryWithdrawalAddress := c.nodeAddress != c.node.PrimaryWithdrawalAddress.Get() - data.InsufficientBalance = (c.amount.Cmp(ethBalance) >= 0) + data.InsufficientBalance = (c.amount.Cmp(ethBalance) > 0) data.HasDifferentPrimaryWithdrawalAddress = hasDifferentPrimaryWithdrawalAddress // Update & return response diff --git a/rocketpool-daemon/api/odao/cancel-proposal.go b/rocketpool-daemon/api/odao/cancel-proposal.go index b0b9aa62a..0206557f8 100644 --- a/rocketpool-daemon/api/odao/cancel-proposal.go +++ b/rocketpool-daemon/api/odao/cancel-proposal.go @@ -40,7 +40,7 @@ func (f *oracleDaoCancelProposalContextFactory) Create(args url.Values) (*oracle func (f *oracleDaoCancelProposalContextFactory) RegisterRoute(router *mux.Router) { server.RegisterSingleStageRoute[*oracleDaoCancelProposalContext, api.OracleDaoCancelProposalData]( - router, "proposal/execute", f, f.handler.logger.Logger, f.handler.serviceProvider.ServiceProvider, + router, "proposal/cancel", f, f.handler.logger.Logger, f.handler.serviceProvider.ServiceProvider, ) } diff --git a/rocketpool-daemon/api/odao/execute-proposals.go b/rocketpool-daemon/api/odao/execute-proposals.go index 515f79815..e806a315f 100644 --- a/rocketpool-daemon/api/odao/execute-proposals.go +++ b/rocketpool-daemon/api/odao/execute-proposals.go @@ -110,7 +110,7 @@ func (c *oracleDaoExecuteProposalsContext) PrepareData(dataBatch *types.DataBatc data := &dataBatch.Batch[i] state := prop.State.Formatted() data.DoesNotExist = (c.ids[i] > c.dpm.ProposalCount.Formatted()) - data.InvalidState = !(state == rptypes.ProposalState_Pending || state == rptypes.ProposalState_Active) + data.InvalidState = state != rptypes.ProposalState_Succeeded data.CanExecute = !(data.DoesNotExist || data.InvalidState) // Get the tx diff --git a/rocketpool-daemon/api/odao/proposals.go b/rocketpool-daemon/api/odao/proposals.go index 9ffbe1c4b..0ec0a5058 100644 --- a/rocketpool-daemon/api/odao/proposals.go +++ b/rocketpool-daemon/api/odao/proposals.go @@ -93,6 +93,7 @@ func (c *oracleDaoProposalsContext) PrepareData(data *api.OracleDaoProposalsData for _, odaoProp := range odaoProps { prop := api.OracleDaoProposalDetails{ ID: odaoProp.ID, + DAO: "rocketDAONodeTrustedProposals", ProposerAddress: odaoProp.ProposerAddress.Get(), Message: odaoProp.Message.Get(), CreatedTime: odaoProp.CreatedTime.Formatted(), @@ -105,6 +106,7 @@ func (c *oracleDaoProposalsContext) PrepareData(data *api.OracleDaoProposalsData IsCancelled: odaoProp.IsCancelled.Get(), IsExecuted: odaoProp.IsExecuted.Get(), Payload: odaoProp.Payload.Get(), + State: odaoProp.State.Formatted(), } prop.PayloadStr, err = odaoProp.GetPayloadAsString() if err != nil { diff --git a/rocketpool-daemon/api/odao/propose-settings.go b/rocketpool-daemon/api/odao/propose-settings.go index 24d357ae5..bb8bac478 100644 --- a/rocketpool-daemon/api/odao/propose-settings.go +++ b/rocketpool-daemon/api/odao/propose-settings.go @@ -132,7 +132,7 @@ func (c *oracleDaoProposeSettingContext) PrepareData(data *api.OracleDaoProposeS data.TxInfo = txInfo } } - return types.ResponseStatus_Error, nil + return types.ResponseStatus_Success, nil } func (c *oracleDaoProposeSettingContext) createProposalTx(category oracle.SettingsCategory, opts *bind.TransactOpts) (bool, *eth.TransactionInfo, error, error) { diff --git a/rocketpool-daemon/api/pdao/handler.go b/rocketpool-daemon/api/pdao/handler.go index c43536c38..0f720a162 100644 --- a/rocketpool-daemon/api/pdao/handler.go +++ b/rocketpool-daemon/api/pdao/handler.go @@ -44,11 +44,13 @@ func NewProtocolDaoHandler(logger *log.Logger, ctx context.Context, serviceProvi &protocolDaoSettingsContextFactory{h}, &protocolDaoProposeSettingContextFactory{h}, &protocolDaoInitializeVotingContextFactory{h}, + &protocolDaoInitializeVotingWithDelegateContextFactory{h}, &protocolDaoSetVotingDelegateContextFactory{h}, &protocolDaoCurrentVotingDelegateContextFactory{h}, &protocolDaoGetStatusContextFactory{h}, &protocolDaoClearSignallingAddressFactory{h}, &protocolDaoSetSignallingAddressFactory{h}, + &protocolDaoIsVotingInitializedContextFactory{h}, } return h } diff --git a/rocketpool-daemon/api/pdao/initialize-voting-with-delegate.go b/rocketpool-daemon/api/pdao/initialize-voting-with-delegate.go new file mode 100644 index 000000000..2922763dd --- /dev/null +++ b/rocketpool-daemon/api/pdao/initialize-voting-with-delegate.go @@ -0,0 +1,97 @@ +package pdao + +import ( + "errors" + "fmt" + "net/url" + _ "time/tzdata" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/gorilla/mux" + batch "github.com/rocket-pool/batch-query" + "github.com/rocket-pool/rocketpool-go/v2/node" + "github.com/rocket-pool/rocketpool-go/v2/rocketpool" + + "github.com/rocket-pool/node-manager-core/api/server" + "github.com/rocket-pool/node-manager-core/api/types" + "github.com/rocket-pool/node-manager-core/eth" + "github.com/rocket-pool/node-manager-core/utils/input" + "github.com/rocket-pool/smartnode/v2/shared/types/api" +) + +// =============== +// === Factory === +// =============== + +type protocolDaoInitializeVotingWithDelegateContextFactory struct { + handler *ProtocolDaoHandler +} + +func (f *protocolDaoInitializeVotingWithDelegateContextFactory) Create(args url.Values) (*protocolDaoInitializeVotingWithDelegateContext, error) { + c := &protocolDaoInitializeVotingWithDelegateContext{ + handler: f.handler, + } + inputErrs := []error{ + server.ValidateArg("delegate", args, input.ValidateAddress, &c.delegate), + } + return c, errors.Join(inputErrs...) +} + +func (f *protocolDaoInitializeVotingWithDelegateContextFactory) RegisterRoute(router *mux.Router) { + server.RegisterSingleStageRoute[*protocolDaoInitializeVotingWithDelegateContext, api.ProtocolDaoInitializeVotingData]( + router, "initialize-voting-with-delegate", f, f.handler.logger.Logger, f.handler.serviceProvider.ServiceProvider, + ) +} + +// =============== +// === Context === +// =============== + +type protocolDaoInitializeVotingWithDelegateContext struct { + handler *ProtocolDaoHandler + rp *rocketpool.RocketPool + + delegate common.Address + node *node.Node +} + +func (c *protocolDaoInitializeVotingWithDelegateContext) Initialize() (types.ResponseStatus, error) { + sp := c.handler.serviceProvider + c.rp = sp.GetRocketPool() + nodeAddress, _ := sp.GetWallet().GetAddress() + + // Requirements + status, err := sp.RequireNodeRegistered(c.handler.ctx) + if err != nil { + return status, err + } + + // Bindings + c.node, err = node.NewNode(c.rp, nodeAddress) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("error creating node %s binding: %w", nodeAddress.Hex(), err) + } + return types.ResponseStatus_Success, nil +} + +func (c *protocolDaoInitializeVotingWithDelegateContext) GetState(mc *batch.MultiCaller) { + eth.AddQueryablesToMulticall(mc, + c.node.IsVotingInitialized, + ) +} + +func (c *protocolDaoInitializeVotingWithDelegateContext) PrepareData(data *api.ProtocolDaoInitializeVotingData, opts *bind.TransactOpts) (types.ResponseStatus, error) { + data.VotingInitialized = c.node.IsVotingInitialized.Get() + data.CanInitialize = !(data.VotingInitialized) + + // Get TX info + if data.CanInitialize { + txInfo, err := c.node.InitializeVotingWithDelegate(c.delegate, opts) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("error getting TX info for InitializeVotingWithDelegate: %w", err) + } + data.TxInfo = txInfo + } + return types.ResponseStatus_Success, nil +} diff --git a/rocketpool-daemon/api/pdao/initialize-voting.go b/rocketpool-daemon/api/pdao/initialize-voting.go index a6a8df5f8..e20b1226a 100644 --- a/rocketpool-daemon/api/pdao/initialize-voting.go +++ b/rocketpool-daemon/api/pdao/initialize-voting.go @@ -1,13 +1,11 @@ package pdao import ( - "errors" "fmt" "net/url" _ "time/tzdata" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" "github.com/gorilla/mux" batch "github.com/rocket-pool/batch-query" "github.com/rocket-pool/rocketpool-go/v2/node" @@ -16,7 +14,6 @@ import ( "github.com/rocket-pool/node-manager-core/api/server" "github.com/rocket-pool/node-manager-core/api/types" "github.com/rocket-pool/node-manager-core/eth" - "github.com/rocket-pool/node-manager-core/utils/input" "github.com/rocket-pool/smartnode/v2/shared/types/api" ) @@ -32,10 +29,7 @@ func (f *protocolDaoInitializeVotingContextFactory) Create(args url.Values) (*pr c := &protocolDaoInitializeVotingContext{ handler: f.handler, } - inputErrs := []error{ - server.ValidateArg("delegate", args, input.ValidateAddress, &c.delegate), - } - return c, errors.Join(inputErrs...) + return c, nil } func (f *protocolDaoInitializeVotingContextFactory) RegisterRoute(router *mux.Router) { @@ -52,8 +46,7 @@ type protocolDaoInitializeVotingContext struct { handler *ProtocolDaoHandler rp *rocketpool.RocketPool - delegate common.Address - node *node.Node + node *node.Node } func (c *protocolDaoInitializeVotingContext) Initialize() (types.ResponseStatus, error) { @@ -87,7 +80,7 @@ func (c *protocolDaoInitializeVotingContext) PrepareData(data *api.ProtocolDaoIn // Get TX info if data.CanInitialize { - txInfo, err := c.node.InitializeVotingWithDelegate(c.delegate, opts) + txInfo, err := c.node.InitializeVoting(opts) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error getting TX info for InitializeVoting: %w", err) } diff --git a/rocketpool-daemon/api/pdao/is-voting-initialized.go b/rocketpool-daemon/api/pdao/is-voting-initialized.go new file mode 100644 index 000000000..04b9d49b8 --- /dev/null +++ b/rocketpool-daemon/api/pdao/is-voting-initialized.go @@ -0,0 +1,80 @@ +package pdao + +import ( + "fmt" + "net/url" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/gorilla/mux" + batch "github.com/rocket-pool/batch-query" + "github.com/rocket-pool/node-manager-core/eth" + "github.com/rocket-pool/rocketpool-go/v2/node" + "github.com/rocket-pool/rocketpool-go/v2/rocketpool" + + "github.com/rocket-pool/node-manager-core/api/server" + "github.com/rocket-pool/node-manager-core/api/types" + "github.com/rocket-pool/smartnode/v2/shared/types/api" +) + +// =============== +// === Factory === +// =============== + +type protocolDaoIsVotingInitializedContextFactory struct { + handler *ProtocolDaoHandler +} + +func (f *protocolDaoIsVotingInitializedContextFactory) Create(args url.Values) (*protocolDaoIsVotingInitializedContext, error) { + c := &protocolDaoIsVotingInitializedContext{ + handler: f.handler, + } + return c, nil +} + +func (f *protocolDaoIsVotingInitializedContextFactory) RegisterRoute(router *mux.Router) { + server.RegisterSingleStageRoute[*protocolDaoIsVotingInitializedContext, api.ProtocolDaoIsVotingInitializedData]( + router, "is-voting-initialized", f, f.handler.logger.Logger, f.handler.serviceProvider.ServiceProvider, + ) +} + +// =============== +// === Context === +// =============== + +type protocolDaoIsVotingInitializedContext struct { + handler *ProtocolDaoHandler + rp *rocketpool.RocketPool + + node *node.Node +} + +func (c *protocolDaoIsVotingInitializedContext) Initialize() (types.ResponseStatus, error) { + sp := c.handler.serviceProvider + c.rp = sp.GetRocketPool() + nodeAddress, _ := sp.GetWallet().GetAddress() + + // Requirements + status, err := sp.RequireNodeRegistered(c.handler.ctx) + if err != nil { + return status, err + } + + // Bindings + c.node, err = node.NewNode(c.rp, nodeAddress) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("error creating node %s binding: %w", nodeAddress.Hex(), err) + } + return types.ResponseStatus_Success, nil +} + +func (c *protocolDaoIsVotingInitializedContext) GetState(mc *batch.MultiCaller) { + eth.AddQueryablesToMulticall(mc, + c.node.IsVotingInitialized, + ) +} + +func (c *protocolDaoIsVotingInitializedContext) PrepareData(data *api.ProtocolDaoIsVotingInitializedData, opts *bind.TransactOpts) (types.ResponseStatus, error) { + data.VotingInitialized = c.node.IsVotingInitialized.Get() + + return types.ResponseStatus_Success, nil +} diff --git a/rocketpool-daemon/api/security/proposals.go b/rocketpool-daemon/api/security/proposals.go index d0c172da9..d6f50094d 100644 --- a/rocketpool-daemon/api/security/proposals.go +++ b/rocketpool-daemon/api/security/proposals.go @@ -98,6 +98,7 @@ func (c *securityProposalsContext) PrepareData(data *api.SecurityProposalsData, for _, scProp := range scProps { prop := api.SecurityProposalDetails{ ID: scProp.ID, + DAO: "rocketDAOSecurityProposals", ProposerAddress: scProp.ProposerAddress.Get(), Message: scProp.Message.Get(), CreatedTime: scProp.CreatedTime.Formatted(), diff --git a/rocketpool-daemon/common/rewards/utils.go b/rocketpool-daemon/common/rewards/utils.go index 291e9d266..b9b1f43e6 100644 --- a/rocketpool-daemon/common/rewards/utils.go +++ b/rocketpool-daemon/common/rewards/utils.go @@ -2,6 +2,7 @@ package rewards import ( "context" + "errors" "fmt" "io" "math" @@ -374,7 +375,7 @@ func DownloadRewardsFile(cfg *config.SmartNodeConfig, i *sharedtypes.IntervalInf errBuilder.WriteString(fmt.Sprintf("Downloading files with timeout %v failed.\n", timeout)) } - return fmt.Errorf("%s", errBuilder.String()) + return errors.New(errBuilder.String()) } // Gets the start slot for the given interval diff --git a/rocketpool-daemon/common/state/update-checks.go b/rocketpool-daemon/common/state/update-checks.go index a0f869345..681144dfa 100644 --- a/rocketpool-daemon/common/state/update-checks.go +++ b/rocketpool-daemon/common/state/update-checks.go @@ -38,3 +38,13 @@ func IsHoustonDeployed(rp *rocketpool.RocketPool, opts *bind.CallOpts) (bool, er constraint, _ := version.NewConstraint(">= 1.3.0") return constraint.Check(currentVersion), nil } + +// Check if Houston Hotfix has been deployed +func IsHoustonHotfixDeployed(rp *rocketpool.RocketPool, opts *bind.CallOpts) (bool, error) { + currentVersion, err := rp.GetProtocolVersion(opts) + if err != nil { + return false, err + } + constraint, _ := version.NewConstraint(">= 1.3.1") + return constraint.Check(currentVersion), nil +} diff --git a/rocketpool-daemon/node/applyto_test.go b/rocketpool-daemon/node/applyto_test.go new file mode 100644 index 000000000..2d6c5c373 --- /dev/null +++ b/rocketpool-daemon/node/applyto_test.go @@ -0,0 +1,81 @@ +package node + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" +) + +func TestApplyTo(t *testing.T) { + tests := []struct { + name string + maxPriorityFee *big.Int + maxFee *big.Int + expectedGasFeeCap *big.Int + expectedGasTipCap *big.Int + }{ + { + name: "maxPriorityFee less than maxFee", + maxPriorityFee: big.NewInt(50), + maxFee: big.NewInt(100), + expectedGasFeeCap: big.NewInt(100), + expectedGasTipCap: big.NewInt(50), + }, + { + name: "maxPriorityFee equal to maxFee", + maxPriorityFee: big.NewInt(100), + maxFee: big.NewInt(100), + expectedGasFeeCap: big.NewInt(100), + expectedGasTipCap: big.NewInt(25), + }, + { + name: "maxPriorityFee greater than maxFee", + maxPriorityFee: big.NewInt(150), + maxFee: big.NewInt(100), + expectedGasFeeCap: big.NewInt(100), + expectedGasTipCap: big.NewInt(25), + }, + { + name: "maxPriorityFee less than 25%% of maxFee", + maxPriorityFee: big.NewInt(20), + maxFee: big.NewInt(100), + expectedGasFeeCap: big.NewInt(100), + expectedGasTipCap: big.NewInt(20), + }, + { + name: "maxPriorityFee equal to 25%% of maxFee", + maxPriorityFee: big.NewInt(25), + maxFee: big.NewInt(100), + expectedGasFeeCap: big.NewInt(100), + expectedGasTipCap: big.NewInt(25), + }, + { + name: "maxPriorityFee less than maxFee but greater than 25%% of maxFee", + maxPriorityFee: big.NewInt(30), + maxFee: big.NewInt(100), + expectedGasFeeCap: big.NewInt(100), + expectedGasTipCap: big.NewInt(30), + }, + } + + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + g := &GasSettings{ + maxFee: testCase.maxFee, + maxPriorityFee: testCase.maxPriorityFee, + } + + opts := &bind.TransactOpts{} + + g.ApplyTo(opts) + + if opts.GasFeeCap.Cmp(testCase.expectedGasFeeCap) != 0 { + t.Errorf("expected GasFeeCap %s, got %s", testCase.expectedGasFeeCap.String(), opts.GasFeeCap.String()) + } + if opts.GasTipCap.Cmp(testCase.expectedGasTipCap) != 0 { + t.Errorf("expected GasTipCap %s, got %s", testCase.expectedGasTipCap.String(), opts.GasTipCap.String()) + } + }) + } +} diff --git a/rocketpool-daemon/node/defend-pdao-props.go b/rocketpool-daemon/node/defend-pdao-props.go index 242098d0b..9235e59d3 100644 --- a/rocketpool-daemon/node/defend-pdao-props.go +++ b/rocketpool-daemon/node/defend-pdao-props.go @@ -39,9 +39,7 @@ type DefendPdaoProps struct { rp *rocketpool.RocketPool bc beacon.IBeaconClient rs *config.RocketPoolResources - gasThreshold float64 - maxFee *big.Int - maxPriorityFee *big.Int + gasSettings *GasSettings gasLimit uint64 nodeAddress common.Address propMgr *proposals.ProposalManager @@ -56,6 +54,12 @@ func NewDefendPdaoProps(ctx context.Context, sp *services.ServiceProvider, logge cfg := sp.GetConfig() log := logger.With(slog.String(keys.TaskKey, "Defend PDAO Proposals")) maxFee, maxPriorityFee := getAutoTxInfo(cfg, log) + gasSettings := &GasSettings{ + maxFee: maxFee, + maxPriorityFee: maxPriorityFee, + gasThreshold: cfg.AutoTxGasThreshold.Value, + } + return &DefendPdaoProps{ ctx: ctx, sp: sp, @@ -65,9 +69,7 @@ func NewDefendPdaoProps(ctx context.Context, sp *services.ServiceProvider, logge rp: sp.GetRocketPool(), bc: sp.GetBeaconClient(), rs: cfg.GetRocketPoolResources(), - gasThreshold: cfg.AutoTxGasThreshold.Value, - maxFee: maxFee, - maxPriorityFee: maxPriorityFee, + gasSettings: gasSettings, lastScannedBlock: nil, intervalSize: big.NewInt(int64(config.EventLogInterval)), } @@ -226,21 +228,20 @@ func (t *DefendPdaoProps) defendProposal(prop defendableProposal) error { } // Get the max fee - maxFee := t.maxFee - if maxFee == nil || maxFee.Uint64() == 0 { - maxFee, err = gas.GetMaxFeeWeiForDaemon(t.logger) + if t.gasSettings.maxFee == nil || t.gasSettings.maxFee.Uint64() == 0 { + t.gasSettings.maxFee, err = gas.GetMaxFeeWeiForDaemon(t.logger) if err != nil { return err } } // Print the gas info - if !gas.PrintAndCheckGasInfo(txInfo.SimulationResult, true, t.gasThreshold, t.logger, maxFee, t.gasLimit) { + if !gas.PrintAndCheckGasInfo(txInfo.SimulationResult, true, t.gasSettings.gasThreshold, t.logger, t.gasSettings.maxFee, t.gasLimit) { t.logger.Warn("NOTICE: Challenge responses bypass the automatic TX gas threshold, responding for safety.") } - opts.GasFeeCap = maxFee - opts.GasTipCap = t.maxPriorityFee + // Set GasFeeCap and GasTipCap + t.gasSettings.ApplyTo(opts) opts.GasLimit = txInfo.SimulationResult.SafeGasLimit // Print TX info and wait for it to be included in a block diff --git a/rocketpool-daemon/node/distribute-minipools.go b/rocketpool-daemon/node/distribute-minipools.go index 382bc75f4..d565547f1 100644 --- a/rocketpool-daemon/node/distribute-minipools.go +++ b/rocketpool-daemon/node/distribute-minipools.go @@ -36,11 +36,9 @@ type DistributeMinipools struct { bc beacon.IBeaconClient d *client.Client mpMgr *minipool.MinipoolManager - gasThreshold float64 distributeThreshold *big.Int eight *big.Int - maxFee *big.Int - maxPriorityFee *big.Int + gasSettings *GasSettings } // Create distribute minipools task @@ -48,9 +46,14 @@ func NewDistributeMinipools(sp *services.ServiceProvider, logger *log.Logger) *D cfg := sp.GetConfig() log := logger.With(slog.String(keys.TaskKey, "Distribute Minipools")) maxFee, maxPriorityFee := getAutoTxInfo(cfg, log) - gasThreshold := cfg.AutoTxGasThreshold.Value - if gasThreshold == 0 { + gasSettings := &GasSettings{ + maxFee: maxFee, + maxPriorityFee: maxPriorityFee, + gasThreshold: cfg.AutoTxGasThreshold.Value, + } + + if gasSettings.gasThreshold == 0 { logger.Info("Automatic tx gas threshold is 0, disabling auto-distribute.") } @@ -74,10 +77,8 @@ func NewDistributeMinipools(sp *services.ServiceProvider, logger *log.Logger) *D rp: sp.GetRocketPool(), bc: sp.GetBeaconClient(), d: sp.GetDocker().(*client.Client), - gasThreshold: gasThreshold, distributeThreshold: distributeThreshold, - maxFee: maxFee, - maxPriorityFee: maxPriorityFee, + gasSettings: gasSettings, eight: eth.EthToWei(8), } } @@ -85,7 +86,7 @@ func NewDistributeMinipools(sp *services.ServiceProvider, logger *log.Logger) *D // Distribute minipools func (t *DistributeMinipools) Run(state *state.NetworkState) error { // Check if auto-distributing is disabled - if t.gasThreshold == 0 { + if t.gasSettings.gasThreshold == 0 { return nil } @@ -201,18 +202,18 @@ func (t *DistributeMinipools) distributeMinipools(submissions []*eth.Transaction } // Get the max fee - maxFee := t.maxFee - if maxFee == nil || maxFee.Uint64() == 0 { - maxFee, err = gas.GetMaxFeeWeiForDaemon(t.logger) + if t.gasSettings.maxFee == nil || t.gasSettings.maxFee.Uint64() == 0 { + t.gasSettings.maxFee, err = gas.GetMaxFeeWeiForDaemon(t.logger) if err != nil { return err } } - opts.GasFeeCap = maxFee - opts.GasTipCap = t.maxPriorityFee + + // Set GasFeeCap and GasTipCap + t.gasSettings.ApplyTo(opts) // Print the gas info - if !gas.PrintAndCheckGasInfoForBatch(submissions, true, t.gasThreshold, t.logger, maxFee) { + if !gas.PrintAndCheckGasInfoForBatch(submissions, true, t.gasSettings.gasThreshold, t.logger, t.gasSettings.maxFee) { return nil } diff --git a/rocketpool-daemon/node/node.go b/rocketpool-daemon/node/node.go index fc69cdf2e..19c9d03c4 100644 --- a/rocketpool-daemon/node/node.go +++ b/rocketpool-daemon/node/node.go @@ -89,6 +89,12 @@ type TaskLoop struct { secondsDelta float64 } +type GasSettings struct { + maxFee *big.Int + maxPriorityFee *big.Int + gasThreshold float64 +} + func NewTaskLoop(sp *services.ServiceProvider, wg *sync.WaitGroup) *TaskLoop { logger := sp.GetTasksLogger() ctx := logger.CreateContextWithLogger(sp.GetBaseContext()) @@ -480,3 +486,24 @@ func calculateTotalEffectiveStakeForNetwork(state *state.NetworkState) *big.Int } return total } + +// Applies GasFeeCap and GasTipCap to opts and handles a case where the user-inputted maxPriorityFee is greater than the oracle based maxFee +// If so, maxPriorityFee is appplied to opts as the min(maxPriorityFee, 25% of the oracle based maxFee) +func (g *GasSettings) ApplyTo(opts *bind.TransactOpts) *bind.TransactOpts { + opts.GasFeeCap = g.maxFee + // If maxPriorityFee < maxFee, apply maxPriorityFee to opts + if g.maxPriorityFee.Cmp(g.maxFee) < 0 { + opts.GasTipCap = g.maxPriorityFee + return opts + } + quarterMaxFee := new(big.Int).Div(g.maxFee, big.NewInt(4)) + + // Otherwise apply maxPriorityFee to opts as min(maxPriorityFee, 25% of the oracle based maxFee) + if g.maxPriorityFee.Cmp(quarterMaxFee) < 0 { + opts.GasTipCap = g.maxPriorityFee + } else { + opts.GasTipCap = quarterMaxFee + } + return opts + +} diff --git a/rocketpool-daemon/node/promote-minipools.go b/rocketpool-daemon/node/promote-minipools.go index 4d88192b3..d0f8580bd 100644 --- a/rocketpool-daemon/node/promote-minipools.go +++ b/rocketpool-daemon/node/promote-minipools.go @@ -27,16 +27,14 @@ import ( // Promote minipools task type PromoteMinipools struct { - sp *services.ServiceProvider - logger *slog.Logger - alerter *alerting.Alerter - cfg *config.SmartNodeConfig - w *wallet.Wallet - rp *rocketpool.RocketPool - mpMgr *minipool.MinipoolManager - gasThreshold float64 - maxFee *big.Int - maxPriorityFee *big.Int + sp *services.ServiceProvider + logger *slog.Logger + alerter *alerting.Alerter + cfg *config.SmartNodeConfig + w *wallet.Wallet + rp *rocketpool.RocketPool + mpMgr *minipool.MinipoolManager + gasSettings *GasSettings } // Create promote minipools task @@ -44,16 +42,20 @@ func NewPromoteMinipools(sp *services.ServiceProvider, logger *log.Logger) *Prom cfg := sp.GetConfig() log := logger.With(slog.String(keys.TaskKey, "Promote Minipools")) maxFee, maxPriorityFee := getAutoTxInfo(cfg, log) - return &PromoteMinipools{ - sp: sp, - logger: log, - alerter: alerting.NewAlerter(cfg, logger), - cfg: cfg, - w: sp.GetWallet(), - rp: sp.GetRocketPool(), - gasThreshold: cfg.AutoTxGasThreshold.Value, + gasSettings := &GasSettings{ maxFee: maxFee, maxPriorityFee: maxPriorityFee, + gasThreshold: cfg.AutoTxGasThreshold.Value, + } + + return &PromoteMinipools{ + sp: sp, + logger: log, + alerter: alerting.NewAlerter(cfg, logger), + cfg: cfg, + w: sp.GetWallet(), + rp: sp.GetRocketPool(), + gasSettings: gasSettings, } } @@ -179,19 +181,19 @@ func (t *PromoteMinipools) promoteMinipools(submissions []*eth.TransactionSubmis } // Get the max fee - maxFee := t.maxFee - if maxFee == nil || maxFee.Uint64() == 0 { - maxFee, err = gas.GetMaxFeeWeiForDaemon(t.logger) + if t.gasSettings.maxFee == nil || t.gasSettings.maxFee.Uint64() == 0 { + t.gasSettings.maxFee, err = gas.GetMaxFeeWeiForDaemon(t.logger) if err != nil { return err } } - opts.GasFeeCap = maxFee - opts.GasTipCap = t.maxPriorityFee + + // Set GasFeeCap and GasTipCap + t.gasSettings.ApplyTo(opts) // Print the gas info forceSubmissions := []*eth.TransactionSubmission{} - if !gas.PrintAndCheckGasInfoForBatch(submissions, true, t.gasThreshold, t.logger, maxFee) { + if !gas.PrintAndCheckGasInfoForBatch(submissions, true, t.gasSettings.gasThreshold, t.logger, t.gasSettings.maxFee) { // Check for the timeout buffers for i, mpd := range minipools { creationTime := time.Unix(mpd.StatusTime.Int64(), 0) diff --git a/rocketpool-daemon/node/reduce-bonds.go b/rocketpool-daemon/node/reduce-bonds.go index 319c4fde5..086630578 100644 --- a/rocketpool-daemon/node/reduce-bonds.go +++ b/rocketpool-daemon/node/reduce-bonds.go @@ -35,16 +35,14 @@ const ( // Reduce bonds task type ReduceBonds struct { - sp *services.ServiceProvider - logger *slog.Logger - alerter *alerting.Alerter - cfg *config.SmartNodeConfig - w *wallet.Wallet - rp *rocketpool.RocketPool - mpMgr *minipool.MinipoolManager - gasThreshold float64 - maxFee *big.Int - maxPriorityFee *big.Int + sp *services.ServiceProvider + logger *slog.Logger + alerter *alerting.Alerter + cfg *config.SmartNodeConfig + w *wallet.Wallet + rp *rocketpool.RocketPool + mpMgr *minipool.MinipoolManager + gasSettings *GasSettings } // Create reduce bonds task @@ -52,29 +50,32 @@ func NewReduceBonds(sp *services.ServiceProvider, logger *log.Logger) *ReduceBon cfg := sp.GetConfig() log := logger.With(slog.String(keys.TaskKey, "Reduce Bonds")) maxFee, maxPriorityFee := getAutoTxInfo(cfg, log) - gasThreshold := cfg.AutoTxGasThreshold.Value - if gasThreshold == 0 { + gasSettings := &GasSettings{ + maxFee: maxFee, + maxPriorityFee: maxPriorityFee, + gasThreshold: cfg.AutoTxGasThreshold.Value, + } + + if gasSettings.gasThreshold == 0 { log.Info("Automatic tx gas threshold is 0, disabling auto-reduce.") } return &ReduceBonds{ - sp: sp, - logger: log, - alerter: alerting.NewAlerter(cfg, logger), - cfg: cfg, - w: sp.GetWallet(), - rp: sp.GetRocketPool(), - gasThreshold: gasThreshold, - maxFee: maxFee, - maxPriorityFee: maxPriorityFee, + sp: sp, + logger: log, + alerter: alerting.NewAlerter(cfg, logger), + cfg: cfg, + w: sp.GetWallet(), + rp: sp.GetRocketPool(), + gasSettings: gasSettings, } } // Reduce bonds func (t *ReduceBonds) Run(state *state.NetworkState) error { // Check if auto-bond-reduction is disabled - if t.gasThreshold == 0 { + if t.gasSettings.gasThreshold == 0 { return nil } @@ -212,21 +213,20 @@ func (t *ReduceBonds) forceFeeDistribution(state *state.NetworkState) (bool, err } // Get the max fee - maxFee := t.maxFee - if maxFee == nil || maxFee.Uint64() == 0 { - maxFee, err = gas.GetMaxFeeWeiForDaemon(t.logger) + if t.gasSettings == nil || t.gasSettings.maxFee.Uint64() == 0 { + t.gasSettings.maxFee, err = gas.GetMaxFeeWeiForDaemon(t.logger) if err != nil { return false, err } } // Print the gas info - if !gas.PrintAndCheckGasInfo(txInfo.SimulationResult, true, t.gasThreshold, t.logger, maxFee, txInfo.SimulationResult.SafeGasLimit) { + if !gas.PrintAndCheckGasInfo(txInfo.SimulationResult, true, t.gasSettings.gasThreshold, t.logger, t.gasSettings.maxFee, txInfo.SimulationResult.SafeGasLimit) { return false, nil } - opts.GasFeeCap = maxFee - opts.GasTipCap = t.maxPriorityFee + // Set GasFeeCap and GasTipCap + t.gasSettings.ApplyTo(opts) opts.GasLimit = txInfo.SimulationResult.SafeGasLimit // Print TX info and wait for it to be included in a block @@ -342,18 +342,18 @@ func (t *ReduceBonds) reduceBonds(submissions []*eth.TransactionSubmission, mini } // Get the max fee - maxFee := t.maxFee - if maxFee == nil || maxFee.Uint64() == 0 { - maxFee, err = gas.GetMaxFeeWeiForDaemon(t.logger) + if t.gasSettings.maxFee == nil || t.gasSettings.maxFee.Uint64() == 0 { + t.gasSettings.maxFee, err = gas.GetMaxFeeWeiForDaemon(t.logger) if err != nil { return err } } - opts.GasFeeCap = maxFee - opts.GasTipCap = t.maxPriorityFee + + // Set GasFeeCap and GasTipCap + t.gasSettings.ApplyTo(opts) // Print the gas info - if !gas.PrintAndCheckGasInfoForBatch(submissions, true, t.gasThreshold, t.logger, maxFee) { + if !gas.PrintAndCheckGasInfoForBatch(submissions, true, t.gasSettings.gasThreshold, t.logger, t.gasSettings.maxFee) { for _, mp := range minipools { timeSinceReductionStart := latestBlockTime.Sub(mp.ReduceBondTime.Formatted()) remainingTime := windowDuration - timeSinceReductionStart diff --git a/rocketpool-daemon/node/stake-prelaunch-minipools.go b/rocketpool-daemon/node/stake-prelaunch-minipools.go index a6856396b..64fafa51c 100644 --- a/rocketpool-daemon/node/stake-prelaunch-minipools.go +++ b/rocketpool-daemon/node/stake-prelaunch-minipools.go @@ -31,19 +31,17 @@ import ( // Stake prelaunch minipools task type StakePrelaunchMinipools struct { - sp *services.ServiceProvider - logger *slog.Logger - alerter *alerting.Alerter - cfg *config.SmartNodeConfig - w *wallet.Wallet - vMgr *validator.ValidatorManager - rp *rocketpool.RocketPool - bc beacon.IBeaconClient - d *client.Client - mpMgr *minipool.MinipoolManager - gasThreshold float64 - maxFee *big.Int - maxPriorityFee *big.Int + sp *services.ServiceProvider + logger *slog.Logger + alerter *alerting.Alerter + cfg *config.SmartNodeConfig + w *wallet.Wallet + vMgr *validator.ValidatorManager + rp *rocketpool.RocketPool + bc beacon.IBeaconClient + d *client.Client + mpMgr *minipool.MinipoolManager + gasSettings *GasSettings } // Create stake prelaunch minipools task @@ -51,19 +49,23 @@ func NewStakePrelaunchMinipools(sp *services.ServiceProvider, logger *log.Logger cfg := sp.GetConfig() log := logger.With(slog.String(keys.TaskKey, "Prelaunch Stake")) maxFee, maxPriorityFee := getAutoTxInfo(cfg, log) - return &StakePrelaunchMinipools{ - sp: sp, - logger: log, - alerter: alerting.NewAlerter(cfg, logger), - cfg: sp.GetConfig(), - w: sp.GetWallet(), - vMgr: sp.GetValidatorManager(), - rp: sp.GetRocketPool(), - bc: sp.GetBeaconClient(), - d: sp.GetDocker().(*client.Client), - gasThreshold: cfg.AutoTxGasThreshold.Value, + gasSettings := &GasSettings{ maxFee: maxFee, maxPriorityFee: maxPriorityFee, + gasThreshold: cfg.AutoTxGasThreshold.Value, + } + + return &StakePrelaunchMinipools{ + sp: sp, + logger: log, + alerter: alerting.NewAlerter(cfg, logger), + cfg: sp.GetConfig(), + w: sp.GetWallet(), + vMgr: sp.GetValidatorManager(), + rp: sp.GetRocketPool(), + bc: sp.GetBeaconClient(), + d: sp.GetDocker().(*client.Client), + gasSettings: gasSettings, } } @@ -230,20 +232,20 @@ func (t *StakePrelaunchMinipools) stakeMinipools(submissions []*eth.TransactionS } // Get the max fee - maxFee := t.maxFee - if maxFee == nil || maxFee.Uint64() == 0 { - maxFee, err = gas.GetMaxFeeWeiForDaemon(t.logger) + if t.gasSettings.maxFee == nil || t.gasSettings.maxFee.Uint64() == 0 { + t.gasSettings.maxFee, err = gas.GetMaxFeeWeiForDaemon(t.logger) if err != nil { return false, err } } - opts.GasFeeCap = maxFee - opts.GasTipCap = t.maxPriorityFee + + // Set GasFeeCap and GasTipCap + t.gasSettings.ApplyTo(opts) // Print the gas info forceSubmissions := []*eth.TransactionSubmission{} forceMinipools := []*rpstate.NativeMinipoolDetails{} - if !gas.PrintAndCheckGasInfoForBatch(submissions, true, t.gasThreshold, t.logger, maxFee) { + if !gas.PrintAndCheckGasInfoForBatch(submissions, true, t.gasSettings.gasThreshold, t.logger, t.gasSettings.maxFee) { // Check for the timeout buffers for i, mpd := range minipools { prelaunchTime := time.Unix(mpd.StatusTime.Int64(), 0) diff --git a/rocketpool-daemon/node/verify-pdao-props.go b/rocketpool-daemon/node/verify-pdao-props.go index 776b9cba1..d0432f71a 100644 --- a/rocketpool-daemon/node/verify-pdao-props.go +++ b/rocketpool-daemon/node/verify-pdao-props.go @@ -39,16 +39,15 @@ type defeat struct { } type VerifyPdaoProps struct { - ctx context.Context - sp *services.ServiceProvider - logger *slog.Logger - cfg *config.SmartNodeConfig - w *wallet.Wallet - rp *rocketpool.RocketPool - bc beacon.IBeaconClient - gasThreshold float64 - maxFee *big.Int - maxPriorityFee *big.Int + ctx context.Context + sp *services.ServiceProvider + logger *slog.Logger + cfg *config.SmartNodeConfig + w *wallet.Wallet + rp *rocketpool.RocketPool + bc beacon.IBeaconClient + // gasThreshold float64 + gasSettings *GasSettings nodeAddress common.Address propMgr *proposals.ProposalManager pdaoMgr *protocol.ProtocolDaoManager @@ -64,6 +63,12 @@ func NewVerifyPdaoProps(ctx context.Context, sp *services.ServiceProvider, logge cfg := sp.GetConfig() log := logger.With(slog.String(keys.TaskKey, "Verify PDAO Proposals")) maxFee, maxPriorityFee := getAutoTxInfo(cfg, log) + gasSettings := &GasSettings{ + maxFee: maxFee, + maxPriorityFee: maxPriorityFee, + gasThreshold: cfg.AutoTxGasThreshold.Value, + } + return &VerifyPdaoProps{ ctx: ctx, sp: sp, @@ -72,9 +77,7 @@ func NewVerifyPdaoProps(ctx context.Context, sp *services.ServiceProvider, logge w: sp.GetWallet(), rp: sp.GetRocketPool(), bc: sp.GetBeaconClient(), - gasThreshold: cfg.AutoTxGasThreshold.Value, - maxFee: maxFee, - maxPriorityFee: maxPriorityFee, + gasSettings: gasSettings, lastScannedBlock: nil, intervalSize: big.NewInt(int64(config.EventLogInterval)), validPropCache: map[uint64]bool{}, @@ -402,18 +405,18 @@ func (t *VerifyPdaoProps) submitTxs(submissions []*eth.TransactionSubmission) er } // Get the max fee - maxFee := t.maxFee - if maxFee == nil || maxFee.Uint64() == 0 { - maxFee, err = gas.GetMaxFeeWeiForDaemon(t.logger) + if t.gasSettings.maxFee == nil || t.gasSettings.maxFee.Uint64() == 0 { + t.gasSettings.maxFee, err = gas.GetMaxFeeWeiForDaemon(t.logger) if err != nil { return err } } - opts.GasFeeCap = maxFee - opts.GasTipCap = t.maxPriorityFee + + // Set GasFeeCap and GasTipCap + t.gasSettings.ApplyTo(opts) // Print the gas info - if !gas.PrintAndCheckGasInfoForBatch(submissions, true, t.gasThreshold, t.logger, maxFee) { + if !gas.PrintAndCheckGasInfoForBatch(submissions, true, t.gasSettings.gasThreshold, t.logger, t.gasSettings.maxFee) { return nil } diff --git a/rocketpool-daemon/watchtower/submit-network-balances.go b/rocketpool-daemon/watchtower/submit-network-balances.go index 64db327be..ec3a5cf9a 100644 --- a/rocketpool-daemon/watchtower/submit-network-balances.go +++ b/rocketpool-daemon/watchtower/submit-network-balances.go @@ -100,11 +100,11 @@ func (t *SubmitNetworkBalances) Run(state *state.NetworkState) error { t.logger.Info("Starting network balance check.") // Check the last submission block - lastSubmissionBlock := state.NetworkDetails.PricesBlock + lastSubmissionBlock := state.NetworkDetails.BalancesBlock referenceTimestamp := t.cfg.PriceBalanceSubmissionReferenceTimestamp.Value // Get the duration in seconds for the interval between submissions - submissionIntervalInSeconds := int64(state.NetworkDetails.PricesSubmissionFrequency) + submissionIntervalInSeconds := int64(state.NetworkDetails.BalancesSubmissionFrequency) eth2Config := state.BeaconConfig slotNumber, nextSubmissionTime, targetBlockHeader, err := utils.FindNextSubmissionTarget(t.ctx, t.rp, eth2Config, t.bc, t.ec, lastSubmissionBlock, referenceTimestamp, submissionIntervalInSeconds) @@ -113,8 +113,8 @@ func (t *SubmitNetworkBalances) Run(state *state.NetworkState) error { } targetBlockNumber := targetBlockHeader.Number.Uint64() - if targetBlockNumber < lastSubmissionBlock { - // No submission needed: target block older or equal to the last submission + if targetBlockNumber > state.ElBlockNumber { + // No submission needed: target block in the future return nil } diff --git a/rocketpool-daemon/watchtower/submit-rpl-price.go b/rocketpool-daemon/watchtower/submit-rpl-price.go index 427557ec0..6047adeaf 100644 --- a/rocketpool-daemon/watchtower/submit-rpl-price.go +++ b/rocketpool-daemon/watchtower/submit-rpl-price.go @@ -102,8 +102,8 @@ func (t *SubmitRplPrice) Run(state *state.NetworkState) error { submissionTimestamp := uint64(nextSubmissionTime.Unix()) targetBlockNumber := targetBlockHeader.Number.Uint64() - if targetBlockNumber < lastSubmissionBlock { - // No submission needed: target block older or equal to the last submission + if targetBlockNumber > state.ElBlockNumber { + // No submission needed: target block in the future return nil } diff --git a/shared/config/resources.go b/shared/config/resources.go index 83e6d0e23..72d4ce3a1 100644 --- a/shared/config/resources.go +++ b/shared/config/resources.go @@ -196,6 +196,7 @@ func newRocketPoolResources(network config.Network) *RocketPoolResources { PreviousRewardsPoolAddresses: []common.Address{ common.HexToAddress("0x4d581a552490fb6fce5F978e66560C8b7E481818"), }, + RocketSignerRegistryAddress: hexToAddressPtr("0x15Da69Dde70998FC045a260f84ad4aB0A2204e76"), PreviousProtocolDaoVerifierAddresses: nil, OptimismPriceMessengerAddress: nil, PolygonPriceMessengerAddress: nil, diff --git a/shared/config/smartnode-config.go b/shared/config/smartnode-config.go index 0ff532d52..96e36dea7 100644 --- a/shared/config/smartnode-config.go +++ b/shared/config/smartnode-config.go @@ -6,7 +6,6 @@ import ( "path/filepath" "reflect" "strconv" - "strings" "github.com/alessio/shellescape" "github.com/rocket-pool/node-manager-core/config" @@ -782,17 +781,13 @@ func getNetworkOptions() []*config.ParameterOption[config.Network] { Description: "This is the Holešky (Holešovice) test network, which is the next generation of long-lived testnets for Ethereum. It uses free fake ETH and free fake RPL to make fake validators.\nUse this if you want to practice running the Smart Node in a free, safe environment before moving to Mainnet.", }, Value: config.Network_Holesky, - }, - } - - if strings.HasSuffix(assets.RocketPoolVersion(), "-dev") { - options = append(options, &config.ParameterOption[config.Network]{ + }, { ParameterOptionCommon: &config.ParameterOptionCommon{ Name: "Devnet", Description: "This is a development network used by Rocket Pool engineers to test new features and contract upgrades before they are promoted to a Testnet for staging. You should not use this network unless invited to do so by the developers.", }, Value: Network_Devnet, - }) + }, } return options diff --git a/shared/types/api/network.go b/shared/types/api/network.go index 44e077d30..e04702b99 100644 --- a/shared/types/api/network.go +++ b/shared/types/api/network.go @@ -102,3 +102,7 @@ func (ndcid *NetworkDepositContractInfoData) PrintMismatch() bool { fmt.Printf("\tYour Beacon client is using deposit contract %s on chain %d.%s\n", ndcid.BeaconDepositContract.Hex(), ndcid.BeaconNetwork, terminal.ColorReset) return true } + +type NetworkHotfixDeployedData struct { + IsHoustonHotfixDeployed bool `json:"isHoustonHotfixDeployed"` +} diff --git a/shared/types/api/node.go b/shared/types/api/node.go index db751f790..9d7416be9 100644 --- a/shared/types/api/node.go +++ b/shared/types/api/node.go @@ -151,11 +151,13 @@ type NodeSwapRplData struct { } type NodeStakeRplData struct { - CanStake bool `json:"canStake"` - InsufficientBalance bool `json:"insufficientBalance"` - Allowance *big.Int `json:"allowance"` - ApproveTxInfo *eth.TransactionInfo `json:"approveTxInfo"` - StakeTxInfo *eth.TransactionInfo `json:"stakeTxInfo"` + CanStake bool `json:"canStake"` + InsufficientBalance bool `json:"insufficientBalance"` + Allowance *big.Int `json:"allowance"` + ApproveTxInfo *eth.TransactionInfo `json:"approveTxInfo"` + StakeTxInfo *eth.TransactionInfo `json:"stakeTxInfo"` + MaximumStakeFraction *big.Int `json:"maximumStakeFraction"` + MaximumRplStake *big.Int `json:"maximumRplStake"` } type NodeSetStakeRplForAllowedData struct { diff --git a/shared/types/api/pdao.go b/shared/types/api/pdao.go index 7d5b28774..d8bc78dda 100644 --- a/shared/types/api/pdao.go +++ b/shared/types/api/pdao.go @@ -325,3 +325,7 @@ type SnapshotResponseData struct { Error string `json:"error"` ActiveSnapshotProposals []*sharedtypes.SnapshotProposal `json:"activeSnapshotProposals"` } + +type ProtocolDaoIsVotingInitializedData struct { + VotingInitialized bool `json:"votingInitialized"` +} diff --git a/v2.md b/v2.md index 5552a46d3..71352ce59 100644 --- a/v2.md +++ b/v2.md @@ -17,7 +17,7 @@ NOTE: for actual installation instructions, please see the [section below](#inst - The Smart Node now comes in a `.deb` package, so users running Debian or Debian-derivative distributions can now install it via `sudo apt install rocketpool` and update it via `sudo apt update && sudo apt dist-upgrade` once the Rocket Pool repository is set up. - Packages for other distributions, such as RHEL or Fedora, will be introduced at a later date. - For now, users not on Debian-based systems can install via the traditional `rocketpool service install` command. It will still pull the necessary files from GitHub as it did before. -- Installation no longer puts all core system files into your `~/.rocketpool` directory. System files (such as Docker compose templates and execution scripts) are now put into `/usr/share/rocketpool`. The CLI binary is now installed to `/usr/bin/rocketpool`. +- Installation no longer puts all core system files into your `~/.rocketpool` directory. System files (such as Docker compose templates and execution scripts) are now put into `/opt/rocketpool`. The CLI binary is now installed to `/usr/bin/rocketpool`. - Runtime files, such as your Docker compose overrides and logging, are still put into `~/.rocketpool` by default. - Installation via the CLI can optionally now be done with local install scripts and packages on your filesystem instead of needing to reach out to GitHub. This is helpful in places where GitHub can't be accessed. - (*For developers*) The old `smartnode-install` repository has been migrated into the `smartnode` repository. @@ -264,7 +264,7 @@ Running the Smart Node is, for all practical purposes, the same as it was in `v1 - Use `rocketpool service node-logs api` to view the new API logs - Use `rocketpool service node-logs tasks` to view the node task loop logs (previously `rocketpool service logs node`) - Use `rocketpool service node-logs watchtower` to view the watchtower task loop logs (`rocketpool service logs watchtower`) -- The "non-modifiable" files like Docker compose templates and scripts are now in `/usr/share/rocketpool` instead of your user home directory (though personal files like overrides are still in your home directory) +- The "non-modifiable" files like Docker compose templates and scripts are now in `/opt/rocketpool` instead of your user home directory (though personal files like overrides are still in your home directory) - The CLI (if installed via the package manager) is now at `/usr/bin/rocketpool` instead of `~/bin/rocketpool` - Some CLI commands have moved and/or have new flags (see the overview section above) - Commands that involve selecting multiple items (such as distributing minipool balances) will now let you select arbitrary options, and submit all of the transactions at once. The overall flow will feel much faster.