github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/cmd/dynamic_startup.go (about) 1 package cmd 2 3 import ( 4 "context" 5 "encoding/hex" 6 "fmt" 7 "path/filepath" 8 "strconv" 9 "strings" 10 11 "github.com/onflow/crypto" 12 13 "github.com/onflow/flow-go/cmd/util/cmd/common" 14 "github.com/onflow/flow-go/model/bootstrap" 15 "github.com/onflow/flow-go/model/flow" 16 "github.com/onflow/flow-go/state/protocol" 17 badgerstate "github.com/onflow/flow-go/state/protocol/badger" 18 utilsio "github.com/onflow/flow-go/utils/io" 19 ) 20 21 // ValidateDynamicStartupFlags will validate flags necessary for dynamic node startup 22 // - assert dynamic-startup-access-publickey is valid ECDSA_P256 public key hex 23 // - assert dynamic-startup-access-address is not empty 24 // - assert dynamic-startup-startup-epoch-phase is > 0 (EpochPhaseUndefined) 25 func ValidateDynamicStartupFlags(accessPublicKey, accessAddress string, startPhase flow.EpochPhase) error { 26 b, err := hex.DecodeString(strings.TrimPrefix(accessPublicKey, "0x")) 27 if err != nil { 28 return fmt.Errorf("invalid flag --dynamic-startup-access-publickey: %w", err) 29 } 30 31 _, err = crypto.DecodePublicKey(crypto.ECDSAP256, b) 32 if err != nil { 33 return fmt.Errorf("invalid flag --dynamic-startup-access-publickey: %w", err) 34 } 35 36 if accessAddress == "" { 37 return fmt.Errorf("invalid flag --dynamic-startup-access-address can not be empty") 38 } 39 40 if startPhase <= flow.EpochPhaseUndefined { 41 return fmt.Errorf("invalid flag --dynamic-startup-startup-epoch-phase unknown epoch phase") 42 } 43 44 return nil 45 } 46 47 // DynamicStartPreInit is the pre-init func that will check if a node has already bootstrapped 48 // from a root protocol snapshot. If not attempt to get a protocol snapshot where the following 49 // conditions are met. 50 // 1. Target epoch < current epoch (in the past), set root snapshot to current snapshot 51 // 2. Target epoch == "current", wait until target phase == current phase before setting root snapshot 52 // 3. Target epoch > current epoch (in future), wait until target epoch and target phase is reached before 53 // setting root snapshot 54 func DynamicStartPreInit(nodeConfig *NodeConfig) error { 55 ctx := context.Background() 56 57 log := nodeConfig.Logger.With().Str("component", "dynamic-startup").Logger() 58 59 // skip dynamic startup if the protocol state is bootstrapped 60 isBootstrapped, err := badgerstate.IsBootstrapped(nodeConfig.DB) 61 if err != nil { 62 return fmt.Errorf("could not check if state is boostrapped: %w", err) 63 } 64 if isBootstrapped { 65 log.Info().Msg("protocol state already bootstrapped, skipping dynamic startup") 66 return nil 67 } 68 69 // skip dynamic startup if a root snapshot file is specified - this takes priority 70 rootSnapshotPath := filepath.Join(nodeConfig.BootstrapDir, bootstrap.PathRootProtocolStateSnapshot) 71 if utilsio.FileExists(rootSnapshotPath) { 72 log.Info(). 73 Str("root_snapshot_path", rootSnapshotPath). 74 Msg("protocol state is not bootstrapped, will bootstrap using configured root snapshot file, skipping dynamic startup") 75 return nil 76 } 77 78 // get flow client with secure client connection to download protocol snapshot from access node 79 config, err := common.NewFlowClientConfig(nodeConfig.DynamicStartupANAddress, nodeConfig.DynamicStartupANPubkey, flow.ZeroID, false) 80 if err != nil { 81 return fmt.Errorf("failed to create flow client config for node dynamic startup pre-init: %w", err) 82 } 83 84 flowClient, err := common.FlowClient(config) 85 if err != nil { 86 return fmt.Errorf("failed to create flow client for node dynamic startup pre-init: %w", err) 87 } 88 89 getSnapshotFunc := func(ctx context.Context) (protocol.Snapshot, error) { 90 return common.GetSnapshot(ctx, flowClient) 91 } 92 93 // validate dynamic startup epoch flag 94 startupEpoch, err := validateDynamicStartEpochFlags(ctx, getSnapshotFunc, nodeConfig.DynamicStartupEpoch) 95 if err != nil { 96 return fmt.Errorf("failed to validate flag --dynamic-start-epoch: %w", err) 97 } 98 99 startupPhase := flow.GetEpochPhase(nodeConfig.DynamicStartupEpochPhase) 100 101 // validate the rest of the dynamic startup flags 102 err = ValidateDynamicStartupFlags(nodeConfig.DynamicStartupANPubkey, nodeConfig.DynamicStartupANAddress, startupPhase) 103 if err != nil { 104 return err 105 } 106 107 snapshot, err := common.GetSnapshotAtEpochAndPhase( 108 ctx, 109 log, 110 startupEpoch, 111 startupPhase, 112 nodeConfig.BaseConfig.DynamicStartupSleepInterval, 113 getSnapshotFunc, 114 ) 115 if err != nil { 116 return fmt.Errorf("failed to get snapshot at start up epoch (%d) and phase (%s): %w", startupEpoch, startupPhase.String(), err) 117 } 118 119 // set the root snapshot in the config - we will use this later to bootstrap 120 nodeConfig.RootSnapshot = snapshot 121 return nil 122 } 123 124 // validateDynamicStartEpochFlags parse the start epoch flag and return the uin64 value, 125 // if epoch = current return the current epoch counter 126 func validateDynamicStartEpochFlags(ctx context.Context, getSnapshot common.GetProtocolSnapshot, flagEpoch string) (uint64, error) { 127 128 // if flag is not `current` sentinel, it must be a specific epoch counter (uint64) 129 if flagEpoch != "current" { 130 epochCounter, err := strconv.ParseUint(flagEpoch, 10, 64) 131 if err != nil { 132 return 0, fmt.Errorf("invalid epoch counter flag (%s): %w", flagEpoch, err) 133 } 134 return epochCounter, nil 135 } 136 137 // we are using the current epoch, retrieve latest snapshot to determine this value 138 snapshot, err := getSnapshot(ctx) 139 if err != nil { 140 return 0, fmt.Errorf("failed to get snapshot: %w", err) 141 } 142 143 epochCounter, err := snapshot.Epochs().Current().Counter() 144 if err != nil { 145 return 0, fmt.Errorf("failed to get current epoch counter: %w", err) 146 } 147 148 return epochCounter, nil 149 }