Skip to content

Commit 40c62a1

Browse files
committed
First draft of migration code
1 parent 931503c commit 40c62a1

File tree

7 files changed

+256
-38
lines changed

7 files changed

+256
-38
lines changed

Diff for: config/config.go

+3-21
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package config
22

33
import (
44
"encoding/hex"
5-
"errors"
65
"fmt"
76
"math"
87
"os"
@@ -128,7 +127,7 @@ type InitOpts struct {
128127
MaxFileSize uint64
129128
ProviderID *uint32
130129
Throttle bool
131-
Scrypt ScryptParams
130+
Scrypt shared.ScryptParams
132131
// ComputeBatchSize must be greater than 0
133132
ComputeBatchSize uint64
134133

@@ -150,25 +149,8 @@ func (o *InitOpts) TotalFiles(labelsPerUnit uint64) int {
150149
return int(math.Ceil(float64(o.TotalLabels(labelsPerUnit)) / float64(o.MaxFileNumLabels())))
151150
}
152151

153-
type ScryptParams struct {
154-
N, R, P uint
155-
}
156-
157-
func (p *ScryptParams) Validate() error {
158-
if p.N == 0 {
159-
return errors.New("scrypt parameter N cannot be 0")
160-
}
161-
if p.R == 0 {
162-
return errors.New("scrypt parameter r cannot be 0")
163-
}
164-
if p.P == 0 {
165-
return errors.New("scrypt parameter p cannot be 0")
166-
}
167-
return nil
168-
}
169-
170-
func DefaultLabelParams() ScryptParams {
171-
return ScryptParams{
152+
func DefaultLabelParams() shared.ScryptParams {
153+
return shared.ScryptParams{
172154
N: 8192,
173155
R: 1,
174156
P: 1,

Diff for: initialization/initialization.go

+12-6
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,8 @@ type Initializer struct {
171171

172172
// these values are atomics so they can be read from multiple other goroutines safely
173173
// write is protected by mtx
174-
nonceValue atomic.Pointer[[]byte]
175174
nonce atomic.Pointer[uint64]
175+
nonceValue atomic.Pointer[[]byte]
176176
lastPosition atomic.Pointer[uint64]
177177
numLabelsWritten atomic.Uint64
178178

@@ -681,13 +681,18 @@ func (init *Initializer) verifyMetadata(m *shared.PostMetadata) error {
681681

682682
func (init *Initializer) saveMetadata() error {
683683
v := shared.PostMetadata{
684+
Version: 1,
685+
684686
NodeId: init.nodeId,
685687
CommitmentAtxId: init.commitmentAtxId,
686-
LabelsPerUnit: init.cfg.LabelsPerUnit,
687-
NumUnits: init.opts.NumUnits,
688-
MaxFileSize: init.opts.MaxFileSize,
689-
Nonce: init.nonce.Load(),
690-
LastPosition: init.lastPosition.Load(),
688+
689+
LabelsPerUnit: init.cfg.LabelsPerUnit,
690+
NumUnits: init.opts.NumUnits,
691+
MaxFileSize: init.opts.MaxFileSize,
692+
Scrypt: init.opts.Scrypt,
693+
694+
Nonce: init.nonce.Load(),
695+
LastPosition: init.lastPosition.Load(),
691696
}
692697
if init.nonceValue.Load() != nil {
693698
v.NonceValue = *init.nonceValue.Load()
@@ -696,5 +701,6 @@ func (init *Initializer) saveMetadata() error {
696701
}
697702

698703
func (init *Initializer) loadMetadata() (*shared.PostMetadata, error) {
704+
// TODO(mafa): migrate metadata if needed before loading it
699705
return LoadMetadata(init.opts.DataDir)
700706
}

Diff for: initialization/migrate_metadata.go

+174
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package initialization
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
9+
"github.com/natefinch/atomic"
10+
"go.uber.org/zap"
11+
12+
"github.com/spacemeshos/post/config"
13+
"github.com/spacemeshos/post/oracle"
14+
"github.com/spacemeshos/post/shared"
15+
)
16+
17+
var migrateData map[int]func(dir string, logger *zap.Logger) (err error)
18+
19+
func init() {
20+
migrateData = make(map[int]func(dir string, logger *zap.Logger) (err error))
21+
migrateData[0] = migrateV0
22+
}
23+
24+
type MetadataVersion struct {
25+
Version int `json:",omitempty"`
26+
}
27+
28+
// MigratePoST migrates the PoST metadata file to the latest version.
29+
func MigratePoST(dir string, logger *zap.Logger) (err error) {
30+
logger.Info("checking PoST for migrations")
31+
32+
filename := filepath.Join(dir, MetadataFileName)
33+
file, err := os.Open(filename)
34+
switch {
35+
case os.IsNotExist(err):
36+
return ErrStateMetadataFileMissing
37+
case err != nil:
38+
return fmt.Errorf("could not open metadata file: %w", err)
39+
}
40+
defer file.Close()
41+
42+
version := MetadataVersion{}
43+
if err := json.NewDecoder(file).Decode(&version); err != nil {
44+
return fmt.Errorf("failed to determine metadata version: %w", err)
45+
}
46+
47+
if version.Version == len(migrateData) {
48+
logger.Info("PoST is up to date, no migration needed")
49+
return nil
50+
}
51+
52+
if version.Version > len(migrateData) {
53+
return fmt.Errorf("PoST metadata version %d is newer than the latest supported version %d", version.Version, len(migrateData))
54+
}
55+
56+
logger.Info("determined PoST version", zap.Int("version", version.Version))
57+
58+
for v := version.Version; v < len(migrateData); v++ {
59+
if err := migrateData[v](dir, logger); err != nil {
60+
return fmt.Errorf("failed to migrate metadata from version %d to version %d: %w", v, v+1, err)
61+
}
62+
63+
logger.Info("migrated PoST successfully to version", zap.Int("version", v+1))
64+
}
65+
66+
logger.Info("PoST migration process finished successfully")
67+
return nil
68+
}
69+
70+
type postMetadataV0 struct {
71+
NodeId []byte
72+
CommitmentAtxId []byte
73+
74+
LabelsPerUnit uint64
75+
NumUnits uint32
76+
MaxFileSize uint64
77+
Nonce *uint64 `json:",omitempty"`
78+
NonceValue shared.NonceValue `json:",omitempty"`
79+
LastPosition *uint64 `json:",omitempty"`
80+
}
81+
82+
// migrateV0 upgrades PoST from version 0 to version 1.
83+
//
84+
// - add version field to postdata_metadata.json (missing in version 0)
85+
// - add NonceValue field to postdata_metadata.json if missing (was introduced before migrations, not every PoST version 0 metadata file has it)
86+
// - re-encode NodeId and CommitmentAtxId as hex strings.
87+
// - add Scrypt field to postdata_metadata.json (missing in version 0), assume default mainnet values.
88+
func migrateV0(dir string, logger *zap.Logger) (err error) {
89+
filename := filepath.Join(dir, MetadataFileName)
90+
file, err := os.Open(filename)
91+
switch {
92+
case os.IsNotExist(err):
93+
return ErrStateMetadataFileMissing
94+
case err != nil:
95+
return fmt.Errorf("could not read metadata file: %w", err)
96+
}
97+
defer file.Close()
98+
99+
old := postMetadataV0{}
100+
if err := json.NewDecoder(file).Decode(&old); err != nil {
101+
return fmt.Errorf("failed to determine metadata version: %w", err)
102+
}
103+
104+
var nodeID shared.NodeID
105+
if len(old.NodeId) != 32 {
106+
return fmt.Errorf("invalid node ID length: %d", len(old.NodeId))
107+
}
108+
copy(nodeID[:], old.NodeId)
109+
110+
var commitmentAtxID shared.ATXID
111+
if len(old.CommitmentAtxId) != 32 {
112+
return fmt.Errorf("invalid commitment ATX ID length: %d", len(old.CommitmentAtxId))
113+
}
114+
copy(commitmentAtxID[:], old.CommitmentAtxId)
115+
116+
new := shared.PostMetadata{
117+
Version: 1,
118+
119+
NodeId: nodeID,
120+
CommitmentAtxId: commitmentAtxID,
121+
122+
LabelsPerUnit: old.LabelsPerUnit,
123+
NumUnits: old.NumUnits,
124+
MaxFileSize: old.MaxFileSize,
125+
Scrypt: config.DefaultLabelParams(), // we don't know the scrypt params, but on mainnet they are the default ones
126+
127+
Nonce: old.Nonce,
128+
NonceValue: old.NonceValue,
129+
LastPosition: old.LastPosition,
130+
}
131+
132+
if new.Nonce != nil && new.NonceValue == nil {
133+
// there is a nonce in the metadata but no nonce value
134+
commitment := oracle.CommitmentBytes(new.NodeId[:], new.CommitmentAtxId[:])
135+
cpuProviderID := CPUProviderID()
136+
137+
wo, err := oracle.New(
138+
oracle.WithProviderID(&cpuProviderID),
139+
oracle.WithCommitment(commitment),
140+
oracle.WithVRFDifficulty(make([]byte, 32)), // we are not looking for it, so set difficulty to 0
141+
oracle.WithScryptParams(new.Scrypt),
142+
oracle.WithLogger(logger),
143+
)
144+
if err != nil {
145+
return fmt.Errorf("failed to create oracle: %w", err)
146+
}
147+
148+
result, err := wo.Position(*new.Nonce)
149+
if err != nil {
150+
return fmt.Errorf("failed to compute nonce value: %w", err)
151+
}
152+
copy(new.NonceValue, result.Output)
153+
}
154+
155+
tmp, err := os.Create(fmt.Sprintf("%s.tmp", filename))
156+
if err != nil {
157+
return fmt.Errorf("create temporary file %s: %w", tmp.Name(), err)
158+
}
159+
defer tmp.Close()
160+
161+
if err := json.NewEncoder(tmp).Encode(new); err != nil {
162+
return fmt.Errorf("failed to encode metadata during migration: %w", err)
163+
}
164+
165+
if err := tmp.Close(); err != nil {
166+
return fmt.Errorf("failed to close tmp file %s: %w", tmp.Name(), err)
167+
}
168+
169+
if err := atomic.ReplaceFile(tmp.Name(), filename); err != nil {
170+
return fmt.Errorf("save file from %s, %s: %w", tmp.Name(), filename, err)
171+
}
172+
173+
return nil
174+
}

Diff for: initialization/vrf_search_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import (
99
"go.uber.org/zap"
1010
"go.uber.org/zap/zaptest"
1111

12-
"github.com/spacemeshos/post/config"
1312
"github.com/spacemeshos/post/internal/postrs"
1413
"github.com/spacemeshos/post/oracle"
14+
"github.com/spacemeshos/post/shared"
1515
)
1616

1717
func TestCheckLabel(t *testing.T) {
@@ -20,7 +20,7 @@ func TestCheckLabel(t *testing.T) {
2020
oracle.WithProviderID(&cpuProviderID),
2121
oracle.WithCommitment(make([]byte, 32)),
2222
oracle.WithVRFDifficulty(make([]byte, 32)),
23-
oracle.WithScryptParams(config.ScryptParams{
23+
oracle.WithScryptParams(shared.ScryptParams{
2424
N: 2,
2525
R: 1,
2626
P: 1,

Diff for: oracle/oracle.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import (
88

99
"go.uber.org/zap"
1010

11-
"github.com/spacemeshos/post/config"
1211
"github.com/spacemeshos/post/internal/postrs"
12+
"github.com/spacemeshos/post/shared"
1313
)
1414

1515
// ErrWorkOracleClosed is returned when calling a method on an already closed WorkOracle instance.
@@ -84,7 +84,7 @@ func WithVRFDifficulty(difficulty []byte) OptionFunc {
8484

8585
// WithScryptParams sets the parameters for the scrypt algorithm.
8686
// At the moment only configuring N is supported. r and p are fixed at 1 (due to limitations in the OpenCL implementation).
87-
func WithScryptParams(params config.ScryptParams) OptionFunc {
87+
func WithScryptParams(params shared.ScryptParams) OptionFunc {
8888
return func(opts *option) error {
8989
if params.P != 1 || params.R != 1 {
9090
return errors.New("invalid scrypt params: only r = 1, p = 1 are supported for initialization")

Diff for: shared/post_metadata.go

+60-5
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,44 @@ package shared
33
import (
44
"encoding/hex"
55
"encoding/json"
6+
"errors"
67
)
78

9+
// ErrStateMetadataFileMissing is returned when the metadata file is missing.
10+
var ErrStateMetadataFileMissing = errors.New("metadata file is missing")
11+
812
// PostMetadata is the data associated with the PoST init procedure, persisted in the datadir next to the init files.
913
type PostMetadata struct {
10-
NodeId []byte
11-
CommitmentAtxId []byte
14+
Version int `json:",omitempty"`
15+
16+
NodeId NodeID
17+
CommitmentAtxId ATXID
1218

1319
LabelsPerUnit uint64
1420
NumUnits uint32
1521
MaxFileSize uint64
16-
Nonce *uint64 `json:",omitempty"`
17-
NonceValue NonceValue `json:",omitempty"`
18-
LastPosition *uint64 `json:",omitempty"`
22+
Scrypt ScryptParams
23+
24+
Nonce *uint64 `json:",omitempty"`
25+
NonceValue NonceValue `json:",omitempty"`
26+
LastPosition *uint64 `json:",omitempty"`
27+
}
28+
29+
type ScryptParams struct {
30+
N, R, P uint
31+
}
32+
33+
func (p *ScryptParams) Validate() error {
34+
if p.N == 0 {
35+
return errors.New("scrypt parameter N cannot be 0")
36+
}
37+
if p.R == 0 {
38+
return errors.New("scrypt parameter r cannot be 0")
39+
}
40+
if p.P == 0 {
41+
return errors.New("scrypt parameter p cannot be 0")
42+
}
43+
return nil
1944
}
2045

2146
type NonceValue []byte
@@ -32,3 +57,33 @@ func (n *NonceValue) UnmarshalJSON(data []byte) (err error) {
3257
*n, err = hex.DecodeString(hexString)
3358
return
3459
}
60+
61+
type NodeID []byte
62+
63+
func (n NodeID) MarshalJSON() ([]byte, error) {
64+
return json.Marshal(hex.EncodeToString(n))
65+
}
66+
67+
func (n *NodeID) UnmarshalJSON(data []byte) (err error) {
68+
var hexString string
69+
if err = json.Unmarshal(data, &hexString); err != nil {
70+
return
71+
}
72+
*n, err = hex.DecodeString(hexString)
73+
return
74+
}
75+
76+
type ATXID []byte
77+
78+
func (a ATXID) MarshalJSON() ([]byte, error) {
79+
return json.Marshal(hex.EncodeToString(a[:]))
80+
}
81+
82+
func (a *ATXID) UnmarshalJSON(data []byte) (err error) {
83+
var hexString string
84+
if err = json.Unmarshal(data, &hexString); err != nil {
85+
return
86+
}
87+
*a, err = hex.DecodeString(hexString)
88+
return
89+
}

0 commit comments

Comments
 (0)