Skip to content
Draft
5 changes: 1 addition & 4 deletions rocketpool/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,7 @@ func run(c *cli.Context) error {
updateLog := log.NewColorLogger(UpdateColor)

// Create the state manager
m, err := state.NewNetworkStateManager(rp, cfg, rp.Client, bc, &updateLog)
if err != nil {
return err
}
m := state.NewNetworkStateManager(rp, cfg.Smartnode.GetStateManagerContracts(), bc, &updateLog)
stateLocker := collectors.NewStateLocker()

// Initialize tasks
Expand Down
23 changes: 14 additions & 9 deletions rocketpool/watchtower/generate-rewards-tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,13 @@ func (t *generateRewardsTree) generateRewardsTree(index uint64) {
generationPrefix := fmt.Sprintf("[Interval %d Tree]", index)
t.log.Printlnf("%s Starting generation of Merkle rewards tree for interval %d.", generationPrefix, index)

// Get previous rewards pool addresses
previousRewardsPoolAddresses := t.cfg.Smartnode.GetPreviousRewardsPoolAddresses()

rewardsClient := rprewards.NewRewardsExecutionClient(t.rp)

// Find the event for this interval
rewardsEvent, err := rprewards.GetRewardSnapshotEvent(t.rp, t.cfg, index, nil)
rewardsEvent, err := rewardsClient.GetRewardSnapshotEvent(previousRewardsPoolAddresses, index, nil)
if err != nil {
t.handleError(fmt.Errorf("%s Error getting event for interval %d: %w", generationPrefix, index, err))
return
Expand All @@ -164,11 +169,7 @@ func (t *generateRewardsTree) generateRewardsTree(index uint64) {
address, err := client.RocketStorage.GetAddress(opts, crypto.Keccak256Hash([]byte("contract.addressrocketTokenRETH")))
if err == nil {
// Create the state manager with using the primary or fallback (not necessarily archive) EC
stateManager, err = state.NewNetworkStateManager(client, t.cfg, t.rp.Client, t.bc, &t.log)
if err != nil {
t.handleError(fmt.Errorf("error creating new NetworkStateManager with Archive EC: %w", err))
return
}
stateManager = state.NewNetworkStateManager(client, t.cfg.Smartnode.GetStateManagerContracts(), t.bc, &t.log)
} else {
// Check if an Archive EC is provided, and if using it would potentially resolve the error
errMessage := err.Error()
Expand Down Expand Up @@ -199,12 +200,16 @@ func (t *generateRewardsTree) generateRewardsTree(index uint64) {
t.handleError(fmt.Errorf("Error verifying rETH address with Archive EC: %w", err))
return
}
// Create the state manager with the archive EC
stateManager, err = state.NewNetworkStateManager(client, t.cfg, ec, t.bc, &t.log)

// Create a new rocketpool-go instance
archiveRP, err := rocketpool.NewRocketPool(ec, *t.rp.RocketStorageContract.Address)
if err != nil {
t.handleError(fmt.Errorf("Error creating new NetworkStateManager with ARchive EC: %w", err))
t.handleError(fmt.Errorf("Error instantiating client with Archive EC: %w", err))
return
}

// Create the state manager with the archive EC
stateManager = state.NewNetworkStateManager(archiveRP, t.cfg.Smartnode.GetStateManagerContracts(), t.bc, &t.log)
} else {
// No archive node specified
t.handleError(fmt.Errorf("***ERROR*** Primary EC cannot retrieve state for historical block %d and the Archive EC is not specified.", elBlockHeader.Number.Uint64()))
Expand Down
5 changes: 1 addition & 4 deletions rocketpool/watchtower/submit-network-balances.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,7 @@ func (t *submitNetworkBalances) getNetworkBalances(elBlockHeader *types.Header,
}

// Create a new state gen manager
mgr, err := state.NewNetworkStateManager(client, t.cfg, client.Client, t.bc, t.log)
if err != nil {
return networkBalances{}, fmt.Errorf("error creating network state manager for EL block %s, Beacon slot %d: %w", elBlock, beaconBlock, err)
}
mgr := state.NewNetworkStateManager(client, t.cfg.Smartnode.GetStateManagerContracts(), t.bc, t.log)

// Create a new state for the target block
state, err := mgr.GetStateForSlot(beaconBlock)
Expand Down
6 changes: 1 addition & 5 deletions rocketpool/watchtower/submit-rewards-tree-rolling.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,11 +282,7 @@ func (t *submitRewardsTree_Rolling) run(headState *state.NetworkState) error {
}

// Generate the rewards state
stateMgr, err := state.NewNetworkStateManager(client, t.cfg, client.Client, t.bc, &t.log)
if err != nil {
t.handleError(fmt.Errorf("error creating state manager for rewards slot: %w", err))
return
}
stateMgr := state.NewNetworkStateManager(client, t.cfg.Smartnode.GetStateManagerContracts(), t.bc, &t.log)
state, err := stateMgr.GetStateForSlot(snapshotEnd.ConsensusBlock)
if err != nil {
t.handleError(fmt.Errorf("error getting state for rewards slot: %w", err))
Expand Down
5 changes: 1 addition & 4 deletions rocketpool/watchtower/submit-rewards-tree-stateless.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,10 +321,7 @@ func (t *submitRewardsTree_Stateless) generateTreeImpl(rp *rocketpool.RocketPool
t.log.Printlnf("Rewards checkpoint has passed, starting Merkle tree generation for interval %d in the background.\n%s Snapshot Beacon block = %d, EL block = %d, running from %s to %s", currentIndex, t.generationPrefix, snapshotBeaconBlock, elBlockIndex, startTime, endTime)

// Create a new state gen manager
mgr, err := state.NewNetworkStateManager(rp, t.cfg, rp.Client, t.bc, t.log)
if err != nil {
return fmt.Errorf("error creating network state manager for EL block %d, Beacon slot %d: %w", elBlockIndex, snapshotBeaconBlock, err)
}
mgr := state.NewNetworkStateManager(rp, t.cfg.Smartnode.GetStateManagerContracts(), t.bc, t.log)

// Create a new state for the target block
state, err := mgr.GetStateForSlot(snapshotBeaconBlock)
Expand Down
5 changes: 1 addition & 4 deletions rocketpool/watchtower/watchtower.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,7 @@ func run(c *cli.Context) error {
updateLog := log.NewColorLogger(UpdateColor)

// Create the state manager
m, err := state.NewNetworkStateManager(rp, cfg, rp.Client, bc, &updateLog)
if err != nil {
return err
}
m := state.NewNetworkStateManager(rp, cfg.Smartnode.GetStateManagerContracts(), bc, &updateLog)

// Get the node address
nodeAccount, err := w.GetNodeAccount()
Expand Down
24 changes: 12 additions & 12 deletions shared/services/beacon/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@ type BeaconHead struct {
PreviousJustifiedEpoch uint64
}
type ValidatorStatus struct {
Pubkey types.ValidatorPubkey
Index string
WithdrawalCredentials common.Hash
Balance uint64
Status ValidatorState
EffectiveBalance uint64
Slashed bool
ActivationEligibilityEpoch uint64
ActivationEpoch uint64
ExitEpoch uint64
WithdrawableEpoch uint64
Exists bool
Pubkey types.ValidatorPubkey `json:"pubkey"`
Index string `json:"index"`
WithdrawalCredentials common.Hash `json:"withdrawal_credentials"`
Balance uint64 `json:"balance"`
Status ValidatorState `json:"status"`
EffectiveBalance uint64 `json:"effective_balance"`
Slashed bool `json:"slashed"`
ActivationEligibilityEpoch uint64 `json:"activation_eligibility_epoch"`
ActivationEpoch uint64 `json:"activation_epoch"`
ExitEpoch uint64 `json:"exit_epoch"`
WithdrawableEpoch uint64 `json:"withdrawable_epoch"`
Exists bool `json:"exists"`
}
type Eth1Data struct {
DepositRoot common.Hash
Expand Down
92 changes: 84 additions & 8 deletions shared/services/beacon/config.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,63 @@
package beacon

import (
"encoding/json"
"fmt"
"time"

"github.com/ethereum/go-ethereum/common/hexutil"
)

type Eth2Config struct {
GenesisForkVersion []byte
GenesisValidatorsRoot []byte
GenesisEpoch uint64
GenesisTime uint64
SecondsPerSlot uint64
SlotsPerEpoch uint64
SecondsPerEpoch uint64
EpochsPerSyncCommitteePeriod uint64
GenesisForkVersion []byte `json:"genesis_fork_version"`
GenesisValidatorsRoot []byte `json:"genesis_validators_root"`
GenesisEpoch uint64 `json:"genesis_epoch"`
GenesisTime uint64 `json:"genesis_time"`
SecondsPerSlot uint64 `json:"seconds_per_slot"`
SlotsPerEpoch uint64 `json:"slots_per_epoch"`
SecondsPerEpoch uint64 `json:"seconds_per_epoch"`
EpochsPerSyncCommitteePeriod uint64 `json:"epochs_per_sync_committee_period"`
}

func (c *Eth2Config) MarshalJSON() ([]byte, error) {
// GenesisForkVersion and GenesisValidatorsRoot are returned as hex strings with 0x prefixes.
// The other fields are returned as uint64s.
type Alias Eth2Config
return json.Marshal(&struct {
GenesisForkVersion string `json:"genesis_fork_version"`
GenesisValidatorsRoot string `json:"genesis_validators_root"`
*Alias
}{
GenesisForkVersion: hexutil.Encode(c.GenesisForkVersion),
GenesisValidatorsRoot: hexutil.Encode(c.GenesisValidatorsRoot),
Alias: (*Alias)(c),
})
}

func (c *Eth2Config) UnmarshalJSON(data []byte) error {
type Alias Eth2Config
aux := &struct {
GenesisForkVersion string `json:"genesis_fork_version"`
GenesisValidatorsRoot string `json:"genesis_validators_root"`
*Alias
}{
Alias: (*Alias)(c),
}

err := json.Unmarshal(data, &aux)
if err != nil {
return err
}

c.GenesisForkVersion, err = hexutil.Decode(aux.GenesisForkVersion)
if err != nil {
return err
}
c.GenesisValidatorsRoot, err = hexutil.Decode(aux.GenesisValidatorsRoot)
if err != nil {
return err
}
return nil
}

// GetSlotTime returns the time of a given slot for the network described by Eth2Config.
Expand Down Expand Up @@ -48,3 +93,34 @@ func (c *Eth2Config) FirstSlotAtLeast(t int64) uint64 {
}
return c.GenesisEpoch*c.SlotsPerEpoch + slotsSinceGenesis
}

func (c *Eth2Config) SlotToEpoch(slot uint64) uint64 {
return slot / c.SlotsPerEpoch
}

func (c *Eth2Config) EpochToSlot(epoch uint64) uint64 {
return epoch * c.SlotsPerEpoch
}

func (c *Eth2Config) SlotOfEpoch(epoch uint64, slot uint64) (uint64, error) {
if slot > c.SlotsPerEpoch-1 {
return 0, fmt.Errorf("slot %d is not in range 0 - %d", slot, c.SlotsPerEpoch-1)
}
return epoch*c.SlotsPerEpoch + slot, nil
}

func (c *Eth2Config) LastSlotOfEpoch(epoch uint64) uint64 {
out, err := c.SlotOfEpoch(epoch, c.SlotsPerEpoch-1)
if err != nil {
panic("SlotOfEpoch should never return an error when passed SlotsPerEpoch - 1")
}
return out
}

func (c *Eth2Config) FirstSlotOfEpoch(epoch uint64) uint64 {
out, err := c.SlotOfEpoch(epoch, 0)
if err != nil {
panic("SlotOfEpoch should never return an error when passed 0")
}
return out
}
53 changes: 53 additions & 0 deletions shared/services/beacon/config_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package beacon

import (
"slices"
"testing"
"time"
)
Expand Down Expand Up @@ -55,3 +56,55 @@ func TestFirstSlotAtLeast(t *testing.T) {
t.Fatal("Whole number seconds shouldn't round up")
}
}

func TestMarshalJSON(t *testing.T) {
config := &Eth2Config{
GenesisForkVersion: []byte{0x00, 0x00, 0x00, 0x08},
GenesisValidatorsRoot: []byte{0xfe, 0x44, 0x33, 0x22},
GenesisEpoch: 10,
GenesisTime: 10000,
SecondsPerSlot: 4,
SlotsPerEpoch: 32,
SecondsPerEpoch: 32 * 4,
EpochsPerSyncCommitteePeriod: 256,
}

json, err := config.MarshalJSON()
if err != nil {
t.Fatalf("error marshalling config: %v", err)
}

unmarshalled := &Eth2Config{}
err = unmarshalled.UnmarshalJSON(json)
if err != nil {
t.Fatalf("error unmarshalling config: %v", err)
}

if !slices.Equal(unmarshalled.GenesisForkVersion, config.GenesisForkVersion) {
t.Fatalf("genesis fork version should be %v, instead got %v", config.GenesisForkVersion, unmarshalled.GenesisForkVersion)
}

if !slices.Equal(unmarshalled.GenesisValidatorsRoot, config.GenesisValidatorsRoot) {
t.Fatalf("genesis validators root should be %v, instead got %v", config.GenesisValidatorsRoot, unmarshalled.GenesisValidatorsRoot)
}

if unmarshalled.GenesisEpoch != config.GenesisEpoch {
t.Fatalf("genesis epoch should be %v, instead got %v", config.GenesisEpoch, unmarshalled.GenesisEpoch)
}

if unmarshalled.GenesisTime != config.GenesisTime {
t.Fatalf("genesis time should be %v, instead got %v", config.GenesisTime, unmarshalled.GenesisTime)
}

if unmarshalled.SecondsPerSlot != config.SecondsPerSlot {
t.Fatalf("seconds per slot should be %v, instead got %v", config.SecondsPerSlot, unmarshalled.SecondsPerSlot)
}

if unmarshalled.SlotsPerEpoch != config.SlotsPerEpoch {
t.Fatalf("slots per epoch should be %v, instead got %v", config.SlotsPerEpoch, unmarshalled.SlotsPerEpoch)
}

if unmarshalled.EpochsPerSyncCommitteePeriod != config.EpochsPerSyncCommitteePeriod {
t.Fatalf("epochs per sync committee period should be %v, instead got %v", config.EpochsPerSyncCommitteePeriod, unmarshalled.EpochsPerSyncCommitteePeriod)
}
}
18 changes: 18 additions & 0 deletions shared/services/config/smartnode-config.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ const (
RewardsExtensionSSZ RewardsExtension = ".ssz"
)

// Contract addresses for multicall / network state manager
type StateManagerContracts struct {
Multicaller common.Address
BalanceBatcher common.Address
}

// Configuration for the Smartnode
type SmartnodeConfig struct {
Title string `yaml:"-"`
Expand Down Expand Up @@ -851,6 +857,10 @@ func (cfg *SmartnodeConfig) GetRethAddress() common.Address {
}

func getDefaultDataDir(config *RocketPoolConfig) string {
if config == nil {
// Handle tests. Eventually we'll refactor so this isn't necessary.
return ""
}
return filepath.Join(config.RocketPoolDirectory, "data")
}

Expand Down Expand Up @@ -1012,6 +1022,14 @@ func (cfg *SmartnodeConfig) GetBalanceBatcherAddress() string {
return cfg.balancebatcherAddress[cfg.Network.Value.(config.Network)]
}

// Utility function to get the state manager contracts
func (cfg *SmartnodeConfig) GetStateManagerContracts() StateManagerContracts {
return StateManagerContracts{
Multicaller: common.HexToAddress(cfg.GetMulticallAddress()),
BalanceBatcher: common.HexToAddress(cfg.GetBalanceBatcherAddress()),
}
}

func (cfg *SmartnodeConfig) GetFlashbotsProtectUrl() string {
return cfg.flashbotsProtectUrl[cfg.Network.Value.(config.Network)]
}
Expand Down
5 changes: 1 addition & 4 deletions shared/services/proposals/proposal-manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,7 @@ func NewProposalManager(log *log.ColorLogger, cfg *config.RocketPoolConfig, rp *
return nil, fmt.Errorf("error creating node tree manager: %w", err)
}

stateMgr, err := state.NewNetworkStateManager(rp, cfg, rp.Client, bc, log)
if err != nil {
return nil, fmt.Errorf("error creating network state manager: %w", err)
}
stateMgr := state.NewNetworkStateManager(rp, cfg.Smartnode.GetStateManagerContracts(), bc, log)

logPrefix := "[PDAO Proposals]"
return &ProposalManager{
Expand Down
Loading