github.com/iotexproject/iotex-core@v1.14.1-rc1/api/listener.go (about) 1 package api 2 3 import ( 4 "encoding/hex" 5 "sync" 6 7 "github.com/iotexproject/go-pkgs/cache/ttl" 8 "github.com/pkg/errors" 9 "go.uber.org/zap" 10 11 apitypes "github.com/iotexproject/iotex-core/api/types" 12 "github.com/iotexproject/iotex-core/blockchain/block" 13 "github.com/iotexproject/iotex-core/pkg/fastrand" 14 "github.com/iotexproject/iotex-core/pkg/log" 15 ) 16 17 const ( 18 _idSize = 16 19 _idRetry = 1e6 20 ) 21 22 var ( 23 errorUnsupportedType = errors.New("type is unsupported") 24 errorCapacityReached = errors.New("capacity has been reached") 25 errListenerNotFound = errors.New("subscription not found") 26 ) 27 28 type ( 29 // chainListener implements the Listener interface 30 chainListener struct { 31 maxCapacity int 32 streamMap *ttl.Cache // all registered <Responder, chan error> 33 idGenerator *randID 34 mu sync.Mutex 35 } 36 ) 37 38 // NewChainListener returns a new blockchain chainListener 39 func NewChainListener(c int) apitypes.Listener { 40 s, _ := ttl.NewCache(ttl.EvictOnErrorOption()) 41 return &chainListener{ 42 maxCapacity: c, 43 streamMap: s, 44 idGenerator: newIDGenerator(_idSize), 45 } 46 } 47 48 // Start starts the chainListener 49 func (cl *chainListener) Start() error { 50 return nil 51 } 52 53 // Stop stops the block chainListener 54 func (cl *chainListener) Stop() error { 55 // notify all responders to exit 56 cl.streamMap.Range(func(_, value interface{}) error { 57 r, ok := value.(apitypes.Responder) 58 if !ok { 59 log.L().Error("streamMap stores a value which is not a Responder") 60 return errorUnsupportedType 61 } 62 r.Exit() 63 return nil 64 }) 65 cl.streamMap.Reset() 66 return nil 67 } 68 69 // ReceiveBlock handles the block 70 func (cl *chainListener) ReceiveBlock(blk *block.Block) error { 71 // pass the block to every responder 72 cl.streamMap.Range(func(key, value interface{}) error { 73 r, ok := value.(apitypes.Responder) 74 if !ok { 75 log.L().Error("streamMap stores a value which is not a Responder") 76 return errorUnsupportedType 77 } 78 err := r.Respond(key.(string), blk) 79 if err != nil { 80 log.L().Error("responder failed to process block", zap.Error(err)) 81 } 82 return err 83 }) 84 return nil 85 } 86 87 // AddResponder adds a new responder 88 func (cl *chainListener) AddResponder(responder apitypes.Responder) (string, error) { 89 cl.mu.Lock() 90 defer cl.mu.Unlock() 91 if cl.streamMap.Count() >= cl.maxCapacity { 92 return "", errorCapacityReached 93 } 94 95 listenerID, i := "", 0 96 // An new id is assumed to be found, because combinations (2^62) is far larger than capacity 97 for ; i < _idRetry; i++ { 98 listenerID = cl.idGenerator.newID() 99 if _, exist := cl.streamMap.Get(listenerID); !exist { 100 break 101 } 102 } 103 if i == _idRetry { 104 return "", errors.New("No peer id is available") 105 } 106 107 cl.streamMap.Set(listenerID, responder) 108 return listenerID, nil 109 } 110 111 // RemoveResponder delete the responder 112 func (cl *chainListener) RemoveResponder(listenerID string) (bool, error) { 113 cl.mu.Lock() 114 defer cl.mu.Unlock() 115 value, exist := cl.streamMap.Get(listenerID) 116 if !exist { 117 return false, errListenerNotFound 118 } 119 r, ok := value.(apitypes.Responder) 120 if !ok { 121 log.L().Error("streamMap stores a value which is not a Responder") 122 return false, errListenerNotFound 123 } 124 r.Exit() 125 return cl.streamMap.Delete(listenerID), nil 126 } 127 128 type randID struct { 129 length uint8 130 } 131 132 func newIDGenerator(length uint8) *randID { 133 return &randID{length: length} 134 } 135 136 func (id *randID) newID() string { 137 token := make([]byte, id.length) 138 fastrand.Read(token) 139 return "0x" + hex.EncodeToString(token) 140 }