Skip to content

Commit

Permalink
First draft of migration code
Browse files Browse the repository at this point in the history
  • Loading branch information
fasmat committed Aug 12, 2023
1 parent 0e03876 commit 04b4b8d
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 31 deletions.
24 changes: 3 additions & 21 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package config

import (
"encoding/hex"
"errors"
"fmt"
"math"
"os"
Expand Down Expand Up @@ -128,7 +127,7 @@ type InitOpts struct {
MaxFileSize uint64
ProviderID *uint32
Throttle bool
Scrypt ScryptParams
Scrypt shared.ScryptParams
// ComputeBatchSize must be greater than 0
ComputeBatchSize uint64

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

type ScryptParams struct {
N, R, P uint
}

func (p *ScryptParams) Validate() error {
if p.N == 0 {
return errors.New("scrypt parameter N cannot be 0")
}
if p.R == 0 {
return errors.New("scrypt parameter r cannot be 0")
}
if p.P == 0 {
return errors.New("scrypt parameter p cannot be 0")
}
return nil
}

func DefaultLabelParams() ScryptParams {
return ScryptParams{
func DefaultLabelParams() shared.ScryptParams {
return shared.ScryptParams{
N: 8192,
R: 1,
P: 1,
Expand Down
1 change: 1 addition & 0 deletions initialization/initialization.go
Original file line number Diff line number Diff line change
Expand Up @@ -696,5 +696,6 @@ func (init *Initializer) saveMetadata() error {
}

func (init *Initializer) loadMetadata() (*shared.PostMetadata, error) {
// TODO(mafa): migrate metadata if needed before loading it
return shared.LoadMetadata(init.opts.DataDir)
}
172 changes: 172 additions & 0 deletions initialization/migrate_metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package initialization

import (
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/natefinch/atomic"
"go.uber.org/zap"

"github.com/spacemeshos/post/config"
"github.com/spacemeshos/post/oracle"
"github.com/spacemeshos/post/shared"
)

var migrateData map[int]func(dir string, logger *zap.Logger) (err error)

func init() {
migrateData = make(map[int]func(dir string, logger *zap.Logger) (err error))
migrateData[0] = migrateV0
}

type MetadataVersion struct {
Version int `json:",omitempty"`
}

// MigratePoST migrates the PoST metadata file to the latest version.
func MigratePoST(dir string, logger *zap.Logger) (err error) {
logger.Info("checking PoST for migrations")

filename := filepath.Join(dir, shared.MetadataFileName)
file, err := os.Open(filename)
switch {
case os.IsNotExist(err):
return shared.ErrStateMetadataFileMissing
case err != nil:
return fmt.Errorf("could not open metadata file: %w", err)
}
defer file.Close()

version := MetadataVersion{}
if err := json.NewDecoder(file).Decode(&version); err != nil {
return fmt.Errorf("failed to determine metadata version: %w", err)
}

if version.Version == len(migrateData) {
logger.Info("PoST is up to date, no migration needed")
return nil
}

if version.Version > len(migrateData) {
return fmt.Errorf("PoST metadata version %d is newer than the latest supported version %d", version.Version, len(migrateData))
}

logger.Info("determined PoST version", zap.Int("version", version.Version))

for v := version.Version; v < len(migrateData); v++ {
if err := migrateData[v](dir, logger); err != nil {
return fmt.Errorf("failed to migrate metadata from version %d to version %d: %w", v, v+1, err)
}

logger.Info("migrated PoST successfully to version", zap.Int("version", v+1))
}

logger.Info("PoST migration process finished successfully")
return nil
}

type postMetadataV0 struct {
NodeId []byte
CommitmentAtxId []byte

LabelsPerUnit uint64
NumUnits uint32
MaxFileSize uint64
Nonce *uint64 `json:",omitempty"`
NonceValue shared.NonceValue `json:",omitempty"`
LastPosition *uint64 `json:",omitempty"`
}

// migrateV0 upgrades PoST from version 0 to version 1.
//
// - add version field to postdata_metadata.json (missing in version 0)
// - add NonceValue field to postdata_metadata.json if missing (was introduced before migrations, not every PoST version 0 metadata file has it)
// - re-encode NodeId and CommitmentAtxId as hex strings.
func migrateV0(dir string, logger *zap.Logger) (err error) {
filename := filepath.Join(dir, shared.MetadataFileName)
file, err := os.Open(filename)
switch {
case os.IsNotExist(err):
return shared.ErrStateMetadataFileMissing
case err != nil:
return fmt.Errorf("could not read metadata file: %w", err)
}
defer file.Close()

old := postMetadataV0{}
if err := json.NewDecoder(file).Decode(&old); err != nil {
return fmt.Errorf("failed to determine metadata version: %w", err)
}

var nodeID shared.NodeID
if len(old.NodeId) != 32 {
return fmt.Errorf("invalid node ID length: %d", len(old.NodeId))
}
copy(nodeID[:], old.NodeId)

var commitmentAtxID shared.ATXID
if len(old.CommitmentAtxId) != 32 {
return fmt.Errorf("invalid commitment ATX ID length: %d", len(old.CommitmentAtxId))
}
copy(commitmentAtxID[:], old.CommitmentAtxId)

new := shared.PostMetadata{
Version: 1,

NodeId: nodeID,
CommitmentAtxId: commitmentAtxID,

LabelsPerUnit: old.LabelsPerUnit,
NumUnits: old.NumUnits,
MaxFileSize: old.MaxFileSize,
Scrypt: config.DefaultLabelParams(), // we don't know the scrypt params, but on mainnet they are the default ones

Nonce: old.Nonce,
NonceValue: old.NonceValue,
LastPosition: old.LastPosition,
}

if new.Nonce != nil && new.NonceValue == nil {
// there is a nonce in the metadata but no nonce value
commitment := oracle.CommitmentBytes(new.NodeId[:], new.CommitmentAtxId[:])

wo, err := oracle.New(
oracle.WithProviderID(CPUProviderID()),
oracle.WithCommitment(commitment),
oracle.WithVRFDifficulty(make([]byte, 32)), // we are not looking for it, so set difficulty to 0
oracle.WithScryptParams(new.Scrypt),
oracle.WithLogger(logger),
)
if err != nil {
return fmt.Errorf("failed to create oracle: %w", err)
}

result, err := wo.Position(*new.Nonce)
if err != nil {
return fmt.Errorf("failed to compute nonce value: %w", err)
}
copy(new.NonceValue, result.Output)
}

tmp, err := os.Create(fmt.Sprintf("%s.tmp", filename))
if err != nil {
return fmt.Errorf("create temporary file %s: %w", tmp.Name(), err)
}
defer tmp.Close()

if err := json.NewEncoder(tmp).Encode(new); err != nil {
return fmt.Errorf("failed to encode metadata during migration: %w", err)
}

if err := tmp.Close(); err != nil {
return fmt.Errorf("failed to close tmp file %s: %w", tmp.Name(), err)
}

if err := atomic.ReplaceFile(tmp.Name(), filename); err != nil {
return fmt.Errorf("save file from %s, %s: %w", tmp.Name(), filename, err)
}

return nil
}
2 changes: 1 addition & 1 deletion initialization/vrf_search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestCheckLabel(t *testing.T) {
oracle.WithProviderID(&cpuProviderID),
oracle.WithCommitment(make([]byte, 32)),
oracle.WithVRFDifficulty(make([]byte, 32)),
oracle.WithScryptParams(config.ScryptParams{
oracle.WithScryptParams(shared.ScryptParams{
N: 2,
R: 1,
P: 1,
Expand Down
4 changes: 2 additions & 2 deletions oracle/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (

"go.uber.org/zap"

"github.com/spacemeshos/post/config"
"github.com/spacemeshos/post/internal/postrs"
"github.com/spacemeshos/post/shared"
)

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

// WithScryptParams sets the parameters for the scrypt algorithm.
// At the moment only configuring N is supported. r and p are fixed at 1 (due to limitations in the OpenCL implementation).
func WithScryptParams(params config.ScryptParams) OptionFunc {
func WithScryptParams(params shared.ScryptParams) OptionFunc {
return func(opts *option) error {
if params.P != 1 || params.R != 1 {
return errors.New("invalid scrypt params: only r = 1, p = 1 are supported for initialization")
Expand Down
59 changes: 54 additions & 5 deletions shared/post_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,34 @@ var ErrStateMetadataFileMissing = errors.New("metadata file is missing")
type PostMetadata struct {
Version int `json:",omitempty"`

NodeId []byte
CommitmentAtxId []byte
NodeId NodeID
CommitmentAtxId ATXID

LabelsPerUnit uint64
NumUnits uint32
MaxFileSize uint64
Nonce *uint64 `json:",omitempty"`
NonceValue NonceValue `json:",omitempty"`
LastPosition *uint64 `json:",omitempty"`
Scrypt ScryptParams

Nonce *uint64 `json:",omitempty"`
NonceValue NonceValue `json:",omitempty"`
LastPosition *uint64 `json:",omitempty"`
}

type ScryptParams struct {
N, R, P uint
}

func (p *ScryptParams) Validate() error {
if p.N == 0 {
return errors.New("scrypt parameter N cannot be 0")
}
if p.R == 0 {
return errors.New("scrypt parameter r cannot be 0")
}
if p.P == 0 {
return errors.New("scrypt parameter p cannot be 0")
}
return nil
}

type NonceValue []byte
Expand All @@ -42,6 +61,36 @@ func (n *NonceValue) UnmarshalJSON(data []byte) (err error) {
return
}

type NodeID []byte

func (n NodeID) MarshalJSON() ([]byte, error) {
return json.Marshal(hex.EncodeToString(n))
}

func (n *NodeID) UnmarshalJSON(data []byte) (err error) {
var hexString string
if err = json.Unmarshal(data, &hexString); err != nil {
return
}
*n, err = hex.DecodeString(hexString)
return
}

type ATXID []byte

func (a ATXID) MarshalJSON() ([]byte, error) {
return json.Marshal(hex.EncodeToString(a[:]))
}

func (a *ATXID) UnmarshalJSON(data []byte) (err error) {
var hexString string
if err = json.Unmarshal(data, &hexString); err != nil {
return
}
*a, err = hex.DecodeString(hexString)
return
}

const MetadataFileName = "postdata_metadata.json"

func SaveMetadata(dir string, v *PostMetadata) error {
Expand Down
5 changes: 3 additions & 2 deletions verifying/verifying_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import (
"errors"

"github.com/spacemeshos/post/config"
"github.com/spacemeshos/post/shared"
)

type option struct {
powFlags config.PowFlags
// scrypt parameters for labels initialization
labelScrypt config.ScryptParams
labelScrypt shared.ScryptParams

powCreatorId []byte
}
Expand All @@ -33,7 +34,7 @@ func applyOpts(options ...OptionFunc) (*option, error) {

type OptionFunc func(*option) error

func WithLabelScryptParams(params config.ScryptParams) OptionFunc {
func WithLabelScryptParams(params shared.ScryptParams) OptionFunc {
return func(o *option) error {
o.labelScrypt = params
return nil
Expand Down

0 comments on commit 04b4b8d

Please sign in to comment.