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