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.

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