github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/sync/initial-sync/fsm.go (about) 1 package initialsync 2 3 import ( 4 "errors" 5 "fmt" 6 "sort" 7 "time" 8 9 "github.com/libp2p/go-libp2p-core/peer" 10 types "github.com/prysmaticlabs/eth2-types" 11 "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" 12 "github.com/prysmaticlabs/prysm/proto/interfaces" 13 "github.com/prysmaticlabs/prysm/shared/timeutils" 14 ) 15 16 const ( 17 stateNew stateID = iota 18 stateScheduled 19 stateDataParsed 20 stateSkipped 21 stateSent 22 ) 23 24 const ( 25 eventTick eventID = iota 26 eventDataReceived 27 ) 28 29 // stateID is unique handle for a state. 30 type stateID uint8 31 32 // eventID is unique handle for an event. 33 type eventID uint8 34 35 // stateMachineManager is a collection of managed FSMs. 36 type stateMachineManager struct { 37 keys []types.Slot 38 machines map[types.Slot]*stateMachine 39 handlers map[stateID]map[eventID]eventHandlerFn 40 } 41 42 // stateMachine holds a state of a single block processing FSM. 43 // Each FSM allows deterministic state transitions: State(S) x Event(E) -> Actions (A), State(S'). 44 type stateMachine struct { 45 smm *stateMachineManager 46 start types.Slot 47 state stateID 48 pid peer.ID 49 blocks []interfaces.SignedBeaconBlock 50 updated time.Time 51 } 52 53 // eventHandlerFn is an event handler function's signature. 54 type eventHandlerFn func(m *stateMachine, data interface{}) (newState stateID, err error) 55 56 // newStateMachineManager returns fully initialized state machine manager. 57 func newStateMachineManager() *stateMachineManager { 58 return &stateMachineManager{ 59 keys: make([]types.Slot, 0, lookaheadSteps), 60 machines: make(map[types.Slot]*stateMachine, lookaheadSteps), 61 handlers: make(map[stateID]map[eventID]eventHandlerFn), 62 } 63 } 64 65 // addHandler attaches an event handler to a state event. 66 func (smm *stateMachineManager) addEventHandler(event eventID, state stateID, fn eventHandlerFn) { 67 if _, ok := smm.handlers[state]; !ok { 68 smm.handlers[state] = make(map[eventID]eventHandlerFn) 69 } 70 if _, ok := smm.handlers[state][event]; !ok { 71 smm.handlers[state][event] = fn 72 } 73 } 74 75 // addStateMachine allocates memory for new FSM. 76 func (smm *stateMachineManager) addStateMachine(startSlot types.Slot) *stateMachine { 77 smm.machines[startSlot] = &stateMachine{ 78 smm: smm, 79 start: startSlot, 80 state: stateNew, 81 blocks: []interfaces.SignedBeaconBlock{}, 82 updated: timeutils.Now(), 83 } 84 smm.recalculateMachineAttribs() 85 return smm.machines[startSlot] 86 } 87 88 // removeStateMachine frees memory of a processed/finished FSM. 89 func (smm *stateMachineManager) removeStateMachine(startSlot types.Slot) error { 90 if _, ok := smm.machines[startSlot]; !ok { 91 return fmt.Errorf("state for machine %v is not found", startSlot) 92 } 93 smm.machines[startSlot].blocks = nil 94 delete(smm.machines, startSlot) 95 smm.recalculateMachineAttribs() 96 return nil 97 } 98 99 // removeAllStateMachines removes all managed machines. 100 func (smm *stateMachineManager) removeAllStateMachines() error { 101 for _, key := range smm.keys { 102 if err := smm.removeStateMachine(key); err != nil { 103 return err 104 } 105 } 106 smm.recalculateMachineAttribs() 107 return nil 108 } 109 110 // recalculateMachineAttribs updates cached attributes, which are used for efficiency. 111 func (smm *stateMachineManager) recalculateMachineAttribs() { 112 keys := make([]types.Slot, 0, lookaheadSteps) 113 for key := range smm.machines { 114 keys = append(keys, key) 115 } 116 sort.Slice(keys, func(i, j int) bool { 117 return keys[i] < keys[j] 118 }) 119 smm.keys = keys 120 } 121 122 // findStateMachine returns a state machine for a given start slot (if exists). 123 func (smm *stateMachineManager) findStateMachine(startSlot types.Slot) (*stateMachine, bool) { 124 fsm, ok := smm.machines[startSlot] 125 return fsm, ok 126 } 127 128 // highestStartSlot returns the start slot for the latest known state machine. 129 func (smm *stateMachineManager) highestStartSlot() (types.Slot, error) { 130 if len(smm.keys) == 0 { 131 return 0, errors.New("no state machine exist") 132 } 133 key := smm.keys[len(smm.keys)-1] 134 return smm.machines[key].start, nil 135 } 136 137 // allMachinesInState checks whether all registered state machines are in the same state. 138 func (smm *stateMachineManager) allMachinesInState(state stateID) bool { 139 if len(smm.machines) == 0 { 140 return false 141 } 142 for _, fsm := range smm.machines { 143 if fsm.state != state { 144 return false 145 } 146 } 147 return true 148 } 149 150 // String returns human readable representation of a FSM collection. 151 func (smm *stateMachineManager) String() string { 152 return fmt.Sprintf("%v", smm.machines) 153 } 154 155 // setState updates the current state of a given state machine. 156 func (m *stateMachine) setState(name stateID) { 157 if m.state == name { 158 return 159 } 160 m.state = name 161 m.updated = timeutils.Now() 162 } 163 164 // trigger invokes the event handler on a given state machine. 165 func (m *stateMachine) trigger(event eventID, data interface{}) error { 166 handlers, ok := m.smm.handlers[m.state] 167 if !ok { 168 return fmt.Errorf("no event handlers registered for event: %v, state: %v", event, m.state) 169 } 170 if handlerFn, ok := handlers[event]; ok { 171 state, err := handlerFn(m, data) 172 if err != nil { 173 return err 174 } 175 m.setState(state) 176 } 177 return nil 178 } 179 180 // isFirst checks whether a given machine has the lowest start slot. 181 func (m *stateMachine) isFirst() bool { 182 return m.start == m.smm.keys[0] 183 } 184 185 // isLast checks whether a given machine has the highest start slot. 186 func (m *stateMachine) isLast() bool { 187 return m.start == m.smm.keys[len(m.smm.keys)-1] 188 } 189 190 // String returns human-readable representation of a FSM state. 191 func (m *stateMachine) String() string { 192 return fmt.Sprintf("{%d:%s}", helpers.SlotToEpoch(m.start), m.state) 193 } 194 195 // String returns human-readable representation of a state. 196 func (s stateID) String() string { 197 states := map[stateID]string{ 198 stateNew: "new", 199 stateScheduled: "scheduled", 200 stateDataParsed: "dataParsed", 201 stateSkipped: "skipped", 202 stateSent: "sent", 203 } 204 if _, ok := states[s]; !ok { 205 return "stateUnknown" 206 } 207 return states[s] 208 } 209 210 // String returns human-readable representation of an event. 211 func (e eventID) String() string { 212 events := map[eventID]string{ 213 eventTick: "tick", 214 eventDataReceived: "dataReceived", 215 } 216 if _, ok := events[e]; !ok { 217 return "eventUnknown" 218 } 219 return events[e] 220 }