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  }