You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

330 lines
8.5 KiB

  1. package statesync
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "sync"
  7. "time"
  8. "github.com/tendermint/tendermint/internal/p2p"
  9. "github.com/tendermint/tendermint/light/provider"
  10. ssproto "github.com/tendermint/tendermint/proto/tendermint/statesync"
  11. proto "github.com/tendermint/tendermint/proto/tendermint/types"
  12. "github.com/tendermint/tendermint/types"
  13. )
  14. var (
  15. errNoConnectedPeers = errors.New("no available peers to dispatch request to")
  16. errUnsolicitedResponse = errors.New("unsolicited light block response")
  17. errNoResponse = errors.New("peer failed to respond within timeout")
  18. errPeerAlreadyBusy = errors.New("peer is already processing a request")
  19. errDisconnected = errors.New("dispatcher has been disconnected")
  20. )
  21. // dispatcher keeps a list of peers and allows concurrent requests for light
  22. // blocks. NOTE: It is not the responsibility of the dispatcher to verify the
  23. // light blocks.
  24. type dispatcher struct {
  25. availablePeers *peerlist
  26. requestCh chan<- p2p.Envelope
  27. timeout time.Duration
  28. mtx sync.Mutex
  29. calls map[types.NodeID]chan *types.LightBlock
  30. running bool
  31. }
  32. func newDispatcher(requestCh chan<- p2p.Envelope, timeout time.Duration) *dispatcher {
  33. return &dispatcher{
  34. availablePeers: newPeerList(),
  35. timeout: timeout,
  36. requestCh: requestCh,
  37. calls: make(map[types.NodeID]chan *types.LightBlock),
  38. running: true,
  39. }
  40. }
  41. // LightBlock uses the request channel to fetch a light block from the next peer
  42. // in a list, tracks the call and waits for the reactor to pass along the response
  43. func (d *dispatcher) LightBlock(ctx context.Context, height int64) (*types.LightBlock, types.NodeID, error) {
  44. d.mtx.Lock()
  45. // check to see that the dispatcher is connected to at least one peer
  46. if d.availablePeers.Len() == 0 && len(d.calls) == 0 {
  47. d.mtx.Unlock()
  48. return nil, "", errNoConnectedPeers
  49. }
  50. d.mtx.Unlock()
  51. // fetch the next peer id in the list and request a light block from that
  52. // peer
  53. peer := d.availablePeers.Pop(ctx)
  54. lb, err := d.lightBlock(ctx, height, peer)
  55. return lb, peer, err
  56. }
  57. // Providers turns the dispatcher into a set of providers (per peer) which can
  58. // be used by a light client
  59. func (d *dispatcher) Providers(chainID string, timeout time.Duration) []provider.Provider {
  60. d.mtx.Lock()
  61. defer d.mtx.Unlock()
  62. providers := make([]provider.Provider, d.availablePeers.Len())
  63. peers := d.availablePeers.Peers()
  64. for index, peer := range peers {
  65. providers[index] = &blockProvider{
  66. peer: peer,
  67. dispatcher: d,
  68. chainID: chainID,
  69. timeout: timeout,
  70. }
  71. }
  72. return providers
  73. }
  74. func (d *dispatcher) stop() {
  75. d.mtx.Lock()
  76. defer d.mtx.Unlock()
  77. d.running = false
  78. for peer, call := range d.calls {
  79. close(call)
  80. delete(d.calls, peer)
  81. }
  82. }
  83. func (d *dispatcher) start() {
  84. d.mtx.Lock()
  85. defer d.mtx.Unlock()
  86. d.running = true
  87. }
  88. func (d *dispatcher) lightBlock(ctx context.Context, height int64, peer types.NodeID) (*types.LightBlock, error) {
  89. // dispatch the request to the peer
  90. callCh, err := d.dispatch(peer, height)
  91. if err != nil {
  92. return nil, err
  93. }
  94. // wait for a response, cancel or timeout
  95. select {
  96. case resp := <-callCh:
  97. return resp, nil
  98. case <-ctx.Done():
  99. d.release(peer)
  100. return nil, nil
  101. case <-time.After(d.timeout):
  102. d.release(peer)
  103. return nil, errNoResponse
  104. }
  105. }
  106. // respond allows the underlying process which receives requests on the
  107. // requestCh to respond with the respective light block
  108. func (d *dispatcher) respond(lb *proto.LightBlock, peer types.NodeID) error {
  109. d.mtx.Lock()
  110. defer d.mtx.Unlock()
  111. // check that the response came from a request
  112. answerCh, ok := d.calls[peer]
  113. if !ok {
  114. // this can also happen if the response came in after the timeout
  115. return errUnsolicitedResponse
  116. }
  117. // release the peer after returning the response
  118. defer d.availablePeers.Append(peer)
  119. defer close(answerCh)
  120. defer delete(d.calls, peer)
  121. if lb == nil {
  122. answerCh <- nil
  123. return nil
  124. }
  125. block, err := types.LightBlockFromProto(lb)
  126. if err != nil {
  127. fmt.Println("error with converting light block")
  128. return err
  129. }
  130. answerCh <- block
  131. return nil
  132. }
  133. func (d *dispatcher) addPeer(peer types.NodeID) {
  134. d.availablePeers.Append(peer)
  135. }
  136. func (d *dispatcher) removePeer(peer types.NodeID) {
  137. d.mtx.Lock()
  138. defer d.mtx.Unlock()
  139. if _, ok := d.calls[peer]; ok {
  140. delete(d.calls, peer)
  141. } else {
  142. d.availablePeers.Remove(peer)
  143. }
  144. }
  145. // dispatch takes a peer and allocates it a channel so long as it's not already
  146. // busy and the receiving channel is still running. It then dispatches the message
  147. func (d *dispatcher) dispatch(peer types.NodeID, height int64) (chan *types.LightBlock, error) {
  148. d.mtx.Lock()
  149. defer d.mtx.Unlock()
  150. ch := make(chan *types.LightBlock, 1)
  151. // check if the dispatcher is running or not
  152. if !d.running {
  153. close(ch)
  154. return ch, errDisconnected
  155. }
  156. // this should happen only if we add the same peer twice (somehow)
  157. if _, ok := d.calls[peer]; ok {
  158. close(ch)
  159. return ch, errPeerAlreadyBusy
  160. }
  161. d.calls[peer] = ch
  162. // send request
  163. d.requestCh <- p2p.Envelope{
  164. To: peer,
  165. Message: &ssproto.LightBlockRequest{
  166. Height: uint64(height),
  167. },
  168. }
  169. return ch, nil
  170. }
  171. // release appends the peer back to the list and deletes the allocated call so
  172. // that a new call can be made to that peer
  173. func (d *dispatcher) release(peer types.NodeID) {
  174. d.mtx.Lock()
  175. defer d.mtx.Unlock()
  176. if call, ok := d.calls[peer]; ok {
  177. close(call)
  178. delete(d.calls, peer)
  179. }
  180. d.availablePeers.Append(peer)
  181. }
  182. //----------------------------------------------------------------
  183. // blockProvider is a p2p based light provider which uses a dispatcher connected
  184. // to the state sync reactor to serve light blocks to the light client
  185. //
  186. // TODO: This should probably be moved over to the light package but as we're
  187. // not yet officially supporting p2p light clients we'll leave this here for now.
  188. type blockProvider struct {
  189. peer types.NodeID
  190. chainID string
  191. timeout time.Duration
  192. dispatcher *dispatcher
  193. }
  194. func (p *blockProvider) LightBlock(ctx context.Context, height int64) (*types.LightBlock, error) {
  195. // FIXME: The provider doesn't know if the dispatcher is still connected to
  196. // that peer. If the connection is dropped for whatever reason the
  197. // dispatcher needs to be able to relay this back to the provider so it can
  198. // return ErrConnectionClosed instead of ErrNoResponse
  199. ctx, cancel := context.WithTimeout(ctx, p.timeout)
  200. defer cancel()
  201. lb, _ := p.dispatcher.lightBlock(ctx, height, p.peer)
  202. if lb == nil {
  203. return nil, provider.ErrNoResponse
  204. }
  205. if err := lb.ValidateBasic(p.chainID); err != nil {
  206. return nil, provider.ErrBadLightBlock{Reason: err}
  207. }
  208. return lb, nil
  209. }
  210. // ReportEvidence should allow for the light client to report any light client
  211. // attacks. This is a no op as there currently isn't a way to wire this up to
  212. // the evidence reactor (we should endeavor to do this in the future but for now
  213. // it's not critical for backwards verification)
  214. func (p *blockProvider) ReportEvidence(ctx context.Context, ev types.Evidence) error {
  215. return nil
  216. }
  217. // String implements stringer interface
  218. func (p *blockProvider) String() string { return string(p.peer) }
  219. //----------------------------------------------------------------
  220. // peerList is a rolling list of peers. This is used to distribute the load of
  221. // retrieving blocks over all the peers the reactor is connected to
  222. type peerlist struct {
  223. mtx sync.Mutex
  224. peers []types.NodeID
  225. waiting []chan types.NodeID
  226. }
  227. func newPeerList() *peerlist {
  228. return &peerlist{
  229. peers: make([]types.NodeID, 0),
  230. waiting: make([]chan types.NodeID, 0),
  231. }
  232. }
  233. func (l *peerlist) Len() int {
  234. l.mtx.Lock()
  235. defer l.mtx.Unlock()
  236. return len(l.peers)
  237. }
  238. func (l *peerlist) Pop(ctx context.Context) types.NodeID {
  239. l.mtx.Lock()
  240. if len(l.peers) == 0 {
  241. // if we don't have any peers in the list we block until a peer is
  242. // appended
  243. wait := make(chan types.NodeID, 1)
  244. l.waiting = append(l.waiting, wait)
  245. // unlock whilst waiting so that the list can be appended to
  246. l.mtx.Unlock()
  247. select {
  248. case peer := <-wait:
  249. return peer
  250. case <-ctx.Done():
  251. return ""
  252. }
  253. }
  254. peer := l.peers[0]
  255. l.peers = l.peers[1:]
  256. l.mtx.Unlock()
  257. return peer
  258. }
  259. func (l *peerlist) Append(peer types.NodeID) {
  260. l.mtx.Lock()
  261. defer l.mtx.Unlock()
  262. if len(l.waiting) > 0 {
  263. wait := l.waiting[0]
  264. l.waiting = l.waiting[1:]
  265. wait <- peer
  266. close(wait)
  267. } else {
  268. l.peers = append(l.peers, peer)
  269. }
  270. }
  271. func (l *peerlist) Remove(peer types.NodeID) {
  272. l.mtx.Lock()
  273. defer l.mtx.Unlock()
  274. for i, p := range l.peers {
  275. if p == peer {
  276. l.peers = append(l.peers[:i], l.peers[i+1:]...)
  277. return
  278. }
  279. }
  280. }
  281. func (l *peerlist) Peers() []types.NodeID {
  282. l.mtx.Lock()
  283. defer l.mtx.Unlock()
  284. return l.peers
  285. }