Skip to content

Commit 35bd244

Browse files
committed
feat: track deployed sidecar version history
1 parent e3bd4a4 commit 35bd244

File tree

7 files changed

+296
-1
lines changed

7 files changed

+296
-1
lines changed

commitlint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module.exports = {
77
'type-enum': [
88
2,
99
'always',
10-
['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', 'revert', 'perf']
10+
['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', 'revert', 'perf', 'qol']
1111
],
1212
'footer-max-line-length': [2, 'always', Infinity],
1313
'footer-leading-blank': [0]
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package _202503182109_sidecarVersions
2+
3+
import (
4+
"database/sql"
5+
"github.com/Layr-Labs/sidecar/internal/config"
6+
"gorm.io/gorm"
7+
)
8+
9+
type Migration struct {
10+
}
11+
12+
func (m *Migration) Up(db *sql.DB, grm *gorm.DB, cfg *config.Config) error {
13+
14+
query := `
15+
create table if not exists sidecar_versions (
16+
id serial primary key,
17+
version text not null,
18+
state_root_block_launched_at bigint not null,
19+
created_at timestamp with time zone default current_timestamp
20+
)
21+
`
22+
res := grm.Exec(query)
23+
return res.Error
24+
}
25+
26+
func (m *Migration) GetName() string {
27+
return "202503182109_sidecarVersions"
28+
}

pkg/postgres/migrations/migrator.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package migrations
33
import (
44
"database/sql"
55
"fmt"
6+
_202503182109_sidecarVersions "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202503182109_sidecarVersions"
67
"time"
78

89
_202501241111_addIndexesForRpcFunctions "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202501241111_addIndexesForRpcFunctions"
@@ -179,6 +180,7 @@ func (m *Migrator) MigrateAll() error {
179180
&_202503042014_stakerOperatorIndex.Migration{},
180181
&_202502180836_snapshotUniqueConstraints.Migration{},
181182
&_202503051449_addContractTypeColumn.Migration{},
183+
&_202503182109_sidecarVersions.Migration{},
182184
}
183185

184186
for _, migration := range migrations {

pkg/runtime/runtime.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package runtime
2+
3+
import (
4+
"errors"
5+
"github.com/Layr-Labs/sidecar/internal/config"
6+
"go.uber.org/zap"
7+
"golang.org/x/mod/semver"
8+
"gorm.io/gorm"
9+
)
10+
11+
type SidecarRuntime struct {
12+
grm *gorm.DB
13+
globalConfig *config.Config
14+
logger *zap.Logger
15+
}
16+
17+
func NewSidecarRuntime(grm *gorm.DB, globalConfig *config.Config, l *zap.Logger) *SidecarRuntime {
18+
return &SidecarRuntime{
19+
grm: grm,
20+
globalConfig: globalConfig,
21+
logger: l,
22+
}
23+
}
24+
25+
func (s *SidecarRuntime) GetRecentlyLaunchedSidecarVersion() (*SidecarVersions, error) {
26+
var sv SidecarVersions
27+
res := s.grm.Model(&SidecarVersions{}).Order("id desc").First(&sv)
28+
if res.Error != nil {
29+
if errors.Is(res.Error, gorm.ErrRecordNotFound) {
30+
return nil, nil
31+
}
32+
return nil, res.Error
33+
}
34+
return &sv, nil
35+
}
36+
37+
func (s *SidecarRuntime) InsertRecentlyLaunchedSidecarVersion(version string, stateRootBlockLaunchedAt uint64) (*SidecarVersions, error) {
38+
sv := &SidecarVersions{
39+
Version: version,
40+
StateRootBlockLaunchedAt: stateRootBlockLaunchedAt,
41+
}
42+
res := s.grm.Model(&SidecarVersions{}).Create(&sv)
43+
if res.Error != nil {
44+
return nil, res.Error
45+
}
46+
return sv, nil
47+
}
48+
49+
func (s *SidecarRuntime) ValidateAndUpdateSidecarVersion(version string) error {
50+
if version == "" {
51+
return errors.New("empty version")
52+
}
53+
54+
if version == "unknown" {
55+
s.logger.Sugar().Warnw("runtime version is unknown, not inserting into sidecar_versions", zap.String("version", version))
56+
return nil
57+
}
58+
59+
lastSeenVersion, err := s.GetRecentlyLaunchedSidecarVersion()
60+
if err != nil {
61+
return err
62+
}
63+
64+
if lastSeenVersion != nil {
65+
// new version should be >= last seen version
66+
cmp := semver.Compare(version, lastSeenVersion.Version)
67+
if cmp < 0 {
68+
return errors.New("runtime version is older than last seen version")
69+
}
70+
if cmp == 0 {
71+
s.logger.Sugar().Infow("runtime version is the same as the last seen version", zap.String("version", version))
72+
return nil
73+
}
74+
}
75+
76+
var blockNumber uint64
77+
res := s.grm.Raw("SELECT MAX(eth_block_number) FROM state_roots").Scan(&blockNumber)
78+
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
79+
return res.Error
80+
}
81+
if errors.Is(res.Error, gorm.ErrRecordNotFound) {
82+
blockNumber = s.globalConfig.GetGenesisBlockNumber()
83+
}
84+
85+
res = s.grm.Model(&SidecarVersions{}).Create(&SidecarVersions{
86+
Version: version,
87+
StateRootBlockLaunchedAt: blockNumber,
88+
})
89+
return res.Error
90+
}

pkg/runtime/runtime_test.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package runtime
2+
3+
import (
4+
"fmt"
5+
"github.com/Layr-Labs/sidecar/internal/config"
6+
"github.com/Layr-Labs/sidecar/internal/logger"
7+
"github.com/Layr-Labs/sidecar/internal/tests"
8+
"github.com/Layr-Labs/sidecar/pkg/eigenState/stateManager"
9+
"github.com/Layr-Labs/sidecar/pkg/postgres"
10+
"github.com/Layr-Labs/sidecar/pkg/storage"
11+
"github.com/google/uuid"
12+
"github.com/stretchr/testify/assert"
13+
"go.uber.org/zap"
14+
"gorm.io/gorm"
15+
"os"
16+
"testing"
17+
)
18+
19+
func setup() (
20+
string,
21+
*gorm.DB,
22+
*zap.Logger,
23+
*config.Config,
24+
error,
25+
) {
26+
cfg := config.NewConfig()
27+
cfg.Debug = os.Getenv(config.Debug) == "true"
28+
cfg.DatabaseConfig = *tests.GetDbConfigFromEnv()
29+
30+
l, _ := logger.NewLogger(&logger.LoggerConfig{Debug: cfg.Debug})
31+
32+
dbname, _, grm, err := postgres.GetTestPostgresDatabase(cfg.DatabaseConfig, cfg, l)
33+
if err != nil {
34+
return dbname, nil, nil, nil, err
35+
}
36+
37+
return dbname, grm, l, cfg, nil
38+
}
39+
40+
func insertStateRoot(grm *gorm.DB, blockNumber int) error {
41+
root, _ := uuid.NewRandom()
42+
stateRoot := &stateManager.StateRoot{
43+
EthBlockNumber: uint64(blockNumber),
44+
EthBlockHash: fmt.Sprintf("0xSomething-%d", blockNumber),
45+
StateRoot: root.String(),
46+
}
47+
48+
block := &storage.Block{
49+
Number: uint64(blockNumber),
50+
Hash: "something",
51+
}
52+
53+
res := grm.Model(&storage.Block{}).Create(&block)
54+
if res.Error != nil {
55+
return res.Error
56+
}
57+
58+
res = grm.Model(&stateManager.StateRoot{}).Create(&stateRoot)
59+
return res.Error
60+
}
61+
62+
func Test_SidecarRuntime(t *testing.T) {
63+
dbName, grm, l, cfg, err := setup()
64+
if err != nil {
65+
t.Fatal(err)
66+
}
67+
68+
rtime := NewSidecarRuntime(grm, cfg, l)
69+
70+
t.Run("Should insert a new version when there isnt one", func(t *testing.T) {
71+
version := "v1.0.0"
72+
blockNumber := 1
73+
74+
err = insertStateRoot(grm, blockNumber)
75+
assert.Nil(t, err)
76+
77+
err := rtime.ValidateAndUpdateSidecarVersion(version)
78+
assert.Nil(t, err)
79+
})
80+
t.Run("Should fail due to the version being older", func(t *testing.T) {
81+
version := "v0.1.0"
82+
blockNumber := 2
83+
84+
err = insertStateRoot(grm, blockNumber)
85+
assert.Nil(t, err)
86+
87+
err := rtime.ValidateAndUpdateSidecarVersion(version)
88+
assert.NotNil(t, err)
89+
})
90+
t.Run("Should upgrade the version to a minor release", func(t *testing.T) {
91+
version := "v1.1.0"
92+
blockNumber := 3
93+
94+
err = insertStateRoot(grm, blockNumber)
95+
assert.Nil(t, err)
96+
97+
err := rtime.ValidateAndUpdateSidecarVersion(version)
98+
assert.Nil(t, err)
99+
})
100+
t.Run("Should upgrade the version to a patch release", func(t *testing.T) {
101+
version := "v1.1.1"
102+
blockNumber := 4
103+
104+
err = insertStateRoot(grm, blockNumber)
105+
assert.Nil(t, err)
106+
107+
err := rtime.ValidateAndUpdateSidecarVersion(version)
108+
assert.Nil(t, err)
109+
})
110+
t.Run("Should upgrade the version to a major release", func(t *testing.T) {
111+
version := "v2.0.0"
112+
blockNumber := 5
113+
114+
err = insertStateRoot(grm, blockNumber)
115+
assert.Nil(t, err)
116+
117+
err := rtime.ValidateAndUpdateSidecarVersion(version)
118+
assert.Nil(t, err)
119+
})
120+
t.Run("Should upgrade the version with a commit suffix", func(t *testing.T) {
121+
version := "v2.0.0+abc123"
122+
blockNumber := 6
123+
124+
err = insertStateRoot(grm, blockNumber)
125+
assert.Nil(t, err)
126+
127+
err := rtime.ValidateAndUpdateSidecarVersion(version)
128+
assert.Nil(t, err)
129+
})
130+
t.Run("Should upgrade the version with an RC suffix", func(t *testing.T) {
131+
version := "v2.0.1-rc.1"
132+
blockNumber := 7
133+
134+
err = insertStateRoot(grm, blockNumber)
135+
assert.Nil(t, err)
136+
137+
err := rtime.ValidateAndUpdateSidecarVersion(version)
138+
assert.Nil(t, err)
139+
})
140+
t.Run("Should upgrade the version with an RC suffix and commit suffix", func(t *testing.T) {
141+
version := "v2.0.1-rc.1+abc123"
142+
blockNumber := 8
143+
144+
err = insertStateRoot(grm, blockNumber)
145+
assert.Nil(t, err)
146+
147+
err := rtime.ValidateAndUpdateSidecarVersion(version)
148+
assert.Nil(t, err)
149+
})
150+
151+
t.Cleanup(func() {
152+
postgres.TeardownTestDatabase(dbName, cfg, grm, l)
153+
})
154+
}

pkg/runtime/tables.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package runtime
2+
3+
import "time"
4+
5+
type SidecarVersions struct {
6+
Id uint64 `gorm:"type:serial"`
7+
Version string
8+
StateRootBlockLaunchedAt uint64
9+
CreatedAt *time.Time
10+
}

pkg/sidecar/sidecar.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ package sidecar
33
import (
44
"context"
55
"github.com/Layr-Labs/sidecar/internal/config"
6+
"github.com/Layr-Labs/sidecar/internal/version"
67
"github.com/Layr-Labs/sidecar/pkg/clients/ethereum"
78
"github.com/Layr-Labs/sidecar/pkg/eigenState/stateManager"
89
"github.com/Layr-Labs/sidecar/pkg/metaState/metaStateManager"
910
"github.com/Layr-Labs/sidecar/pkg/pipeline"
1011
"github.com/Layr-Labs/sidecar/pkg/proofs"
1112
"github.com/Layr-Labs/sidecar/pkg/rewards"
1213
"github.com/Layr-Labs/sidecar/pkg/rewardsCalculatorQueue"
14+
"github.com/Layr-Labs/sidecar/pkg/runtime"
1315
"github.com/Layr-Labs/sidecar/pkg/storage"
1416
"go.uber.org/zap"
1517
"sync/atomic"
@@ -33,6 +35,7 @@ type Sidecar struct {
3335
RewardProofs *proofs.RewardsProofsStore
3436
ShutdownChan chan bool
3537
shouldShutdown *atomic.Bool
38+
sidecarRuntime *runtime.SidecarRuntime
3639
}
3740

3841
func NewSidecar(
@@ -50,6 +53,9 @@ func NewSidecar(
5053
) *Sidecar {
5154
shouldShutdown := &atomic.Bool{}
5255
shouldShutdown.Store(false)
56+
57+
rt := runtime.NewSidecarRuntime(em.DB, gCfg, l)
58+
5359
return &Sidecar{
5460
Logger: l,
5561
Config: cfg,
@@ -64,12 +70,17 @@ func NewSidecar(
6470
StateManager: em,
6571
ShutdownChan: make(chan bool),
6672
shouldShutdown: shouldShutdown,
73+
sidecarRuntime: rt,
6774
}
6875
}
6976

7077
func (s *Sidecar) Start(ctx context.Context) {
7178
s.Logger.Info("Starting sidecar")
7279

80+
if err := s.sidecarRuntime.ValidateAndUpdateSidecarVersion(version.GetVersion()); err != nil {
81+
s.Logger.Sugar().Fatalw("Failed to validate and update sidecar version", zap.Error(err))
82+
}
83+
7384
// Spin up a goroutine that listens on a channel for a shutdown signal.
7485
// When the signal is received, set shouldShutdown to true and return.
7586
go func() {

0 commit comments

Comments
 (0)