Line data Source code
1 : #ifndef HEADER_fd_src_discof_restore_utils_fd_wfs_h 2 : #define HEADER_fd_src_discof_restore_utils_fd_wfs_h 3 : 4 : #include "../../../util/fd_util_base.h" 5 : 6 : /* Wait-for-supermajority (WFS) is the coordinated-restart boot mode. 7 : Given a target (slot S, bank hash H) and a nonzero expected shred 8 : version V, the validator attempts to load full or full+incremental 9 : snapshot(s) reaching slot S. It then verifies the bank hash, and 10 : refuses to replay or lead until a supermajority (>=80%) of stake is 11 : observed back online. This lets every validator agree on the fork 12 : before resuming after a hard fork. 13 : 14 : Tiles that classify a boot must invoke fd_wfs_mode() and obey the 15 : per-mode contract below; the snapshot selection path only needs to 16 : know whether WFS is configured. 17 : 18 : The classification has one input tuple (S, H, V) from config and 19 : one runtime input (boot_slot): the latter is the effective slot the 20 : validator booted at, i.e. the resulting bank slot after loading the 21 : snapshot(s). WFS is configured when (S!=0 && H!=0 && V!=0). 22 : The classifier is a pure function of its inputs and yields exactly 23 : one mode: 24 : 25 : DISABLED : not configured, behave as a normal boot. 26 : UNRESOLVED : configured but boot_slot not yet known. 27 : MATCH : configured and boot_slot==S. The bank is verified, 28 : and tiles must wait until a supermajority of stake 29 : is observed back online. 30 : NOOP : configured but boot_slot>S (network has moved on). 31 : Treated as a normal boot. 32 : ERROR : configured but boot_slot<S and no snapshot bridged 33 : the gap. The operator must supply a snapshot at S. 34 : 35 : Specific rules: 36 : - boot_slot is the last manifest before FD_SSMSG_DONE, which 37 : ssload makes the boot bank. Take the latest, never the 38 : maximum: a reset can legitimately lower the slot. 39 : - Booting from genesis with WFS configured is not allowed (ERROR). 40 : - WFS completion is signalled once. Consumers MUST be idempotent: 41 : a repeat signal after completion is dropped. 42 : - In MATCH mode the leader gate (the requirement that the 43 : validator's vote account is rooted before it may lead) is 44 : suppressed, otherwise a coordinated restart deadlocks: no 45 : validator may lead, so no vote can ever root. The suppression 46 : is permanent for the lifetime of the process; it is not undone 47 : when WFS completes. 48 : 49 : Regarding snapshot(s) (down)load: 50 : - A coordinated WFS restart implies moving backward in slots, and 51 : a change in V. That means that any local snapshot on disk from 52 : V_old could correspond to slot>S, i.e. it would appear to be in 53 : the future. 54 : - When WFS uses a full-only snapshot, the full snapshot's shred 55 : version is V. However, when full+incr is used, the full 56 : snapshot's shred version might be V_old, and only the 57 : incremental's is V. This is why the shred version is verified 58 : once on the boot bank after the last manifest, never 59 : per-snapshot. 60 : - At boot time, and especially around WFS, there is no trusted 61 : way to determine whether the network has moved past slot S. 62 : The validator needs to boot to observe the network, but it 63 : needs to know the network state to decide how to boot. 64 : The alternatives are to treat WFS configuration as: 65 : - strict: 66 : - if WFS is configured, the validator only boots at slot 67 : S, otherwise it crashes. Only mode MATCH is supported. 68 : - Guarantees: (S, H, V) are verified. 69 : - Operational nuances: the operator must remove the WFS 70 : config right after the network moves past WFS, otherwise 71 : a subsequent validator restart will fail. 72 : - relaxed (Agave's behavior): 73 : - if WFS is configured, the validator has the option to 74 : treat it as a NOOP if it believes that the network has 75 : already moved forward. 76 : - Guarantees: 77 : - in MATCH: (S, H, V) are verified. 78 : - in NOOP: only (V) is verified; (S, H) are skipped! 79 : - Operational nuances: during WFS, the operator must 80 : carefully choose "trusted" download sources (or copy 81 : the snapshots) and delete obsolete local snapshots. 82 : Firedancer chooses to follow Agave's behavior (relaxed). 83 : - The validator chooses between the local snapshot(s) and what it 84 : could download, keeping the local ones while they are recent 85 : enough. WFS adds three constraints: a local snapshot short of 86 : S is not kept when downloading reaches S, a local incremental 87 : short of S is never kept (because it would be the boot), and a 88 : peer reaching S is preferred over one that cannot. 89 : - When WFS is configured and a candidate full snapshot is behind 90 : slot S, [snapshots.incremental_snapshots] is overridden to true 91 : so an incremental can reach S. Provisioning is separate and 92 : always reserves for incrementals, since it runs before the mode 93 : is knowable (fd_wfs_needs_incr). 94 : 95 : In short: selection is WFS-agnostic, classification is WFS-aware. 96 : WFS contributes a constraint, never an objective; the mode above is 97 : a verdict on whatever slot selection landed on. */ 98 : 99 0 : #define FD_WFS_MODE_DISABLED (0) 100 0 : #define FD_WFS_MODE_UNRESOLVED (1) 101 0 : #define FD_WFS_MODE_MATCH (2) 102 0 : #define FD_WFS_MODE_NOOP (3) 103 0 : #define FD_WFS_MODE_ERROR (4) 104 : 105 : FD_PROTOTYPES_BEGIN 106 : 107 : /* fd_wfs_configured returns 1 iff the config enables WFS. Arguments 108 : slot, hash_is_zero, and shred_version come from consensus config. 109 : All three conditions must hold together for WFS to be enabled. 110 : 111 : hash_is_zero is derived via string-empty check in topology.c and via 112 : byte-zero memcmp in tiles. These are equivalent because config 113 : rejects the all-zeros base58 encoding. */ 114 : 115 : FD_FN_CONST static inline int 116 : fd_wfs_configured( ulong slot, 117 : int hash_is_zero, 118 0 : ulong shred_version ) { 119 0 : return slot!=0UL && !hash_is_zero && shred_version!=0UL; 120 0 : } 121 : 122 : /* fd_wfs_needs_incr returns whether to provision for incrementals; 123 : WFS forces it on, as the mode is unknowable before any load. */ 124 : 125 : FD_FN_CONST static inline int 126 : fd_wfs_needs_incr( int incremental_snapshots, 127 : ulong slot, 128 : int hash_is_zero, 129 0 : ulong shred_version ) { 130 0 : return incremental_snapshots || fd_wfs_configured( slot, hash_is_zero, shred_version ); 131 0 : } 132 : 133 : /* fd_wfs_mode classifies WFS given the config triple and boot_slot, 134 : the effective boot slot (the bank slot of the last snapshot loaded). 135 : Pass boot_slot==ULONG_MAX until the boot snapshot is known 136 : (yields UNRESOLVED when configured). */ 137 : 138 : FD_FN_CONST static inline int 139 : fd_wfs_mode( ulong slot, 140 : int hash_is_zero, 141 : ulong shred_version, 142 0 : ulong boot_slot ) { 143 0 : if( !fd_wfs_configured( slot, hash_is_zero, shred_version ) ) return FD_WFS_MODE_DISABLED; 144 0 : if( boot_slot==ULONG_MAX ) return FD_WFS_MODE_UNRESOLVED; 145 0 : if( boot_slot==slot ) return FD_WFS_MODE_MATCH; 146 0 : if( boot_slot>slot ) return FD_WFS_MODE_NOOP; 147 0 : return FD_WFS_MODE_ERROR; 148 0 : } 149 : 150 : FD_FN_CONST static inline char const * 151 0 : fd_wfs_mode_str( int mode ) { 152 0 : switch( mode ) { 153 0 : case FD_WFS_MODE_DISABLED: return "disabled"; 154 0 : case FD_WFS_MODE_UNRESOLVED: return "unresolved"; 155 0 : case FD_WFS_MODE_MATCH: return "match"; 156 0 : case FD_WFS_MODE_NOOP: return "no-op"; 157 0 : case FD_WFS_MODE_ERROR: return "error"; 158 0 : default: return "unknown"; 159 0 : } 160 0 : } 161 : 162 : FD_PROTOTYPES_END 163 : 164 : #endif /* HEADER_fd_src_discof_restore_utils_fd_wfs_h */