aboutsummaryrefslogtreecommitdiffstats
path: root/swarm/network/stream/messages.go
blob: 1e47b7cf9a1b379579ff1ab04366dcdcbf16879f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package stream

import (
    "context"
    "fmt"
    "time"

    "github.com/ethereum/go-ethereum/metrics"
    "github.com/ethereum/go-ethereum/swarm/log"
    bv "github.com/ethereum/go-ethereum/swarm/network/bitvector"
    "github.com/ethereum/go-ethereum/swarm/spancontext"
    "github.com/ethereum/go-ethereum/swarm/storage"
    opentracing "github.com/opentracing/opentracing-go"
)

var syncBatchTimeout = 30 * time.Second

// Stream defines a unique stream identifier.
type Stream struct {
    // Name is used for Client and Server functions identification.
    Name string
    // Key is the name of specific stream data.
    Key string
    // Live defines whether the stream delivers only new data
    // for the specific stream.
    Live bool
}

func NewStream(name string, key string, live bool) Stream {
    return Stream{
        Name: name,
        Key:  key,
        Live: live,
    }
}

// String return a stream id based on all Stream fields.
func (s Stream) String() string {
    t := "h"
    if s.Live {
        t = "l"
    }
    return fmt.Sprintf("%s|%s|%s", s.Name, s.Key, t)
}

// SubcribeMsg is the protocol msg for requesting a stream(section)
type SubscribeMsg struct {
    Stream   Stream
    History  *Range `rlp:"nil"`
    Priority uint8  // delivered on priority channel
}

// RequestSubscriptionMsg is the protocol msg for a node to request subscription to a
// specific stream
type RequestSubscriptionMsg struct {
    Stream   Stream
    History  *Range `rlp:"nil"`
    Priority uint8  // delivered on priority channel
}

func (p *Peer) handleRequestSubscription(ctx context.Context, req *RequestSubscriptionMsg) (err error) {
    log.Debug(fmt.Sprintf("handleRequestSubscription: streamer %s to subscribe to %s with stream %s", p.streamer.addr, p.ID(), req.Stream))
    return p.streamer.Subscribe(p.ID(), req.Stream, req.History, req.Priority)
}

func (p *Peer) handleSubscribeMsg(ctx context.Context, req *SubscribeMsg) (err error) {
    metrics.GetOrRegisterCounter("peer.handlesubscribemsg", nil).Inc(1)

    defer func() {
        if err != nil {
            // The error will be sent as a subscribe error message
            // and will not be returned as it will prevent any new message
            // exchange between peers over p2p. Instead, error will be returned
            // only if there is one from sending subscribe error message.
            err = p.Send(context.TODO(), SubscribeErrorMsg{
                Error: err.Error(),
            })
        }
    }()

    log.Debug("received subscription", "from", p.streamer.addr, "peer", p.ID(), "stream", req.Stream, "history", req.History)

    f, err := p.streamer.GetServerFunc(req.Stream.Name)
    if err != nil {
        return err
    }

    s, err := f(p, req.Stream.Key, req.Stream.Live)
    if err != nil {
        return err
    }
    os, err := p.setServer(req.Stream, s, req.Priority)
    if err != nil {
        return err
    }

    var from uint64
    var to uint64
    if !req.Stream.Live && req.History != nil {
        from = req.History.From
        to = req.History.To
    }

    go func() {
        if err := p.SendOfferedHashes(os, from, to); err != nil {
            log.Warn("SendOfferedHashes error", "peer", p.ID().TerminalString(), "err", err)
        }
    }()

    if req.Stream.Live && req.History != nil {
        // subscribe to the history stream
        s, err := f(p, req.Stream.Key, false)
        if err != nil {
            return err
        }

        os, err := p.setServer(getHistoryStream(req.Stream), s, getHistoryPriority(req.Priority))
        if err != nil {
            return err
        }
        go func() {
            if err := p.SendOfferedHashes(os, req.History.From, req.History.To); err != nil {
                log.Warn("SendOfferedHashes error", "peer", p.ID().TerminalString(), "err", err)
            }
        }()
    }

    return nil
}

type SubscribeErrorMsg struct {
    Error string
}

func (p *Peer) handleSubscribeErrorMsg(req *SubscribeErrorMsg) (err error) {
    return fmt.Errorf("subscribe to peer %s: %v", p.ID(), req.Error)
}

type UnsubscribeMsg struct {
    Stream Stream
}

func (p *Peer) handleUnsubscribeMsg(req *UnsubscribeMsg) error {
    return p.removeServer(req.Stream)
}

type QuitMsg struct {
    Stream Stream
}

func (p *Peer) handleQuitMsg(req *QuitMsg) error {
    return p.removeClient(req.Stream)
}

// OfferedHashesMsg is the protocol msg for offering to hand over a
// stream section
type OfferedHashesMsg struct {
    Stream         Stream // name of Stream
    From, To       uint64 // peer and db-specific entry count
    Hashes         []byte // stream of hashes (128)
    *HandoverProof        // HandoverProof
}

// String pretty prints OfferedHashesMsg
func (m OfferedHashesMsg) String() string {
    return fmt.Sprintf("Stream '%v' [%v-%v] (%v)", m.Stream, m.From, m.To, len(m.Hashes)/HashSize)
}

// handleOfferedHashesMsg protocol msg handler calls the incoming streamer interface
// Filter method
func (p *Peer) handleOfferedHashesMsg(ctx context.Context, req *OfferedHashesMsg) error {
    metrics.GetOrRegisterCounter("peer.handleofferedhashes", nil).Inc(1)

    var sp opentracing.Span
    ctx, sp = spancontext.StartSpan(
        ctx,
        "handle.offered.hashes")
    defer sp.Finish()

    c, _, err := p.getOrSetClient(req.Stream, req.From, req.To)
    if err != nil {
        return err
    }
    hashes := req.Hashes
    want, err := bv.New(len(hashes) / HashSize)
    if err != nil {
        return fmt.Errorf("error initiaising bitvector of length %v: %v", len(hashes)/HashSize, err)
    }

    ctr := 0
    errC := make(chan error)
    ctx, cancel := context.WithTimeout(ctx, syncBatchTimeout)

    ctx = context.WithValue(ctx, "source", p.ID().String())
    for i := 0; i < len(hashes); i += HashSize {
        hash := hashes[i : i+HashSize]

        if wait := c.NeedData(ctx, hash); wait != nil {
            ctr++
            want.Set(i/HashSize, true)
            // create request and wait until the chunk data arrives and is stored
            go func(w func(context.Context) error) {
                select {
                case errC <- w(ctx):
                case <-ctx.Done():
                }
            }(wait)
        }
    }

    go func() {
        defer cancel()
        for i := 0; i < ctr; i++ {
            select {
            case err := <-errC:
                if err != nil {
                    log.Debug("client.handleOfferedHashesMsg() error waiting for chunk, dropping peer", "peer", p.ID(), "err", err)
                    p.Drop(err)
                    return
                }
            case <-ctx.Done():
                log.Debug("client.handleOfferedHashesMsg() context done", "ctx.Err()", ctx.Err())
                return
            case <-c.quit:
                log.Debug("client.handleOfferedHashesMsg() quit")
                return
            }
        }
        select {
        case c.next <- c.batchDone(p, req, hashes):
        case <-c.quit:
            log.Debug("client.handleOfferedHashesMsg() quit")
        case <-ctx.Done():
            log.Debug("client.handleOfferedHashesMsg() context done", "ctx.Err()", ctx.Err())
        }
    }()
    // only send wantedKeysMsg if all missing chunks of the previous batch arrived
    // except
    if c.stream.Live {
        c.sessionAt = req.From
    }
    from, to := c.nextBatch(req.To + 1)
    log.Trace("set next batch", "peer", p.ID(), "stream", req.Stream, "from", req.From, "to", req.To, "addr", p.streamer.addr)
    if from == to {
        return nil
    }

    msg := &WantedHashesMsg{
        Stream: req.Stream,
        Want:   want.Bytes(),
        From:   from,
        To:     to,
    }
    go func() {
        log.Trace("sending want batch", "peer", p.ID(), "stream", msg.Stream, "from", msg.From, "to", msg.To)
        select {
        case err := <-c.next:
            if err != nil {
                log.Warn("c.next error dropping peer", "err", err)
                p.Drop(err)
                return
            }
        case <-c.quit:
            log.Debug("client.handleOfferedHashesMsg() quit")
            return
        case <-ctx.Done():
            log.Debug("client.handleOfferedHashesMsg() context done", "ctx.Err()", ctx.Err())
            return
        }
        log.Trace("sending want batch", "peer", p.ID(), "stream", msg.Stream, "from", msg.From, "to", msg.To)
        err := p.SendPriority(ctx, msg, c.priority)
        if err != nil {
            log.Warn("SendPriority error", "err", err)
        }
    }()
    return nil
}

// WantedHashesMsg is the protocol msg data for signaling which hashes
// offered in OfferedHashesMsg downstream peer actually wants sent over
type WantedHashesMsg struct {
    Stream   Stream
    Want     []byte // bitvector indicating which keys of the batch needed
    From, To uint64 // next interval offset - empty if not to be continued
}

// String pretty prints WantedHashesMsg
func (m WantedHashesMsg) String() string {
    return fmt.Sprintf("Stream '%v', Want: %x, Next: [%v-%v]", m.Stream, m.Want, m.From, m.To)
}

// handleWantedHashesMsg protocol msg handler
// * sends the next batch of unsynced keys
// * sends the actual data chunks as per WantedHashesMsg
func (p *Peer) handleWantedHashesMsg(ctx context.Context, req *WantedHashesMsg) error {
    metrics.GetOrRegisterCounter("peer.handlewantedhashesmsg", nil).Inc(1)

    log.Trace("received wanted batch", "peer", p.ID(), "stream", req.Stream, "from", req.From, "to", req.To)
    s, err := p.getServer(req.Stream)
    if err != nil {
        return err
    }
    hashes := s.currentBatch
    // launch in go routine since GetBatch blocks until new hashes arrive
    go func() {
        if err := p.SendOfferedHashes(s, req.From, req.To); err != nil {
            log.Warn("SendOfferedHashes error", "err", err)
        }
    }()
    // go p.SendOfferedHashes(s, req.From, req.To)
    l := len(hashes) / HashSize

    log.Trace("wanted batch length", "peer", p.ID(), "stream", req.Stream, "from", req.From, "to", req.To, "lenhashes", len(hashes), "l", l)
    want, err := bv.NewFromBytes(req.Want, l)
    if err != nil {
        return fmt.Errorf("error initiaising bitvector of length %v: %v", l, err)
    }
    for i := 0; i < l; i++ {
        if want.Get(i) {
            metrics.GetOrRegisterCounter("peer.handlewantedhashesmsg.actualget", nil).Inc(1)

            hash := hashes[i*HashSize : (i+1)*HashSize]
            data, err := s.GetData(ctx, hash)
            if err != nil {
                return fmt.Errorf("handleWantedHashesMsg get data %x: %v", hash, err)
            }
            chunk := storage.NewChunk(hash, data)
            if err := p.Deliver(ctx, chunk, s.priority); err != nil {
                return err
            }
        }
    }
    return nil
}

// Handover represents a statement that the upstream peer hands over the stream section
type Handover struct {
    Stream     Stream // name of stream
    Start, End uint64 // index of hashes
    Root       []byte // Root hash for indexed segment inclusion proofs
}

// HandoverProof represents a signed statement that the upstream peer handed over the stream section
type HandoverProof struct {
    Sig []byte // Sign(Hash(Serialisation(Handover)))
    *Handover
}

// Takeover represents a statement that downstream peer took over (stored all data)
// handed over
type Takeover Handover

//  TakeoverProof represents a signed statement that the downstream peer took over
// the stream section
type TakeoverProof struct {
    Sig []byte // Sign(Hash(Serialisation(Takeover)))
    *Takeover
}

// TakeoverProofMsg is the protocol msg sent by downstream peer
type TakeoverProofMsg TakeoverProof

// String pretty prints TakeoverProofMsg
func (m TakeoverProofMsg) String() string {
    return fmt.Sprintf("Stream: '%v' [%v-%v], Root: %x, Sig: %x", m.Stream, m.Start, m.End, m.Root, m.Sig)
}

func (p *Peer) handleTakeoverProofMsg(ctx context.Context, req *TakeoverProofMsg) error {
    _, err := p.getServer(req.Stream)
    // store the strongest takeoverproof for the stream in streamer
    return err
}