aboutsummaryrefslogtreecommitdiffstats
path: root/swarm/network/messages.go
blob: d920def9593e1b245870c45aecbec9dcf50b2528 (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
// Copyright 2016 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 network

import (
    "fmt"
    "net"
    "time"

    "github.com/ethereum/go-ethereum/contracts/chequebook"
    "github.com/ethereum/go-ethereum/p2p/discover"
    "github.com/ethereum/go-ethereum/swarm/network/kademlia"
    "github.com/ethereum/go-ethereum/swarm/services/swap"
    "github.com/ethereum/go-ethereum/swarm/storage"
)

/*
BZZ protocol Message Types and Message Data Types
*/

// bzz protocol message codes
const (
    statusMsg          = iota // 0x01
    storeRequestMsg           // 0x02
    retrieveRequestMsg        // 0x03
    peersMsg                  // 0x04
    syncRequestMsg            // 0x05
    deliveryRequestMsg        // 0x06
    unsyncedKeysMsg           // 0x07
    paymentMsg                // 0x08
)

/*
 Handshake

* Version: 8 byte integer version of the protocol
* ID: arbitrary byte sequence client identifier human readable
* Addr: the address advertised by the node, format similar to DEVp2p wire protocol
* Swap: info for the swarm accounting protocol
* NetworkID: 8 byte integer network identifier
* Caps: swarm-specific capabilities, format identical to devp2p
* SyncState: syncronisation state (db iterator key and address space etc) persisted about the peer

*/
type statusMsgData struct {
    Version   uint64
    ID        string
    Addr      *peerAddr
    Swap      *swap.SwapProfile
    NetworkId uint64
}

func (self *statusMsgData) String() string {
    return fmt.Sprintf("Status: Version: %v, ID: %v, Addr: %v, Swap: %v, NetworkId: %v", self.Version, self.ID, self.Addr, self.Swap, self.NetworkId)
}

/*
 store requests are forwarded to the peers in their kademlia proximity bin
 if they are distant
 if they are within our storage radius or have any incentive to store it
 then attach your nodeID to the metadata
 if the storage request is sufficiently close (within our proxLimit, i. e., the
 last row of the routing table)
*/
type storeRequestMsgData struct {
    Key   storage.Key // hash of datasize | data
    SData []byte      // the actual chunk Data
    // optional
    Id             uint64     // request ID. if delivery, the ID is retrieve request ID
    requestTimeout *time.Time // expiry for forwarding - [not serialised][not currently used]
    storageTimeout *time.Time // expiry of content - [not serialised][not currently used]
    from           *peer      // [not serialised] protocol registers the requester
}

func (self storeRequestMsgData) String() string {
    var from string
    if self.from == nil {
        from = "self"
    } else {
        from = self.from.Addr().String()
    }
    end := len(self.SData)
    if len(self.SData) > 10 {
        end = 10
    }
    return fmt.Sprintf("from: %v, Key: %v; ID: %v, requestTimeout: %v, storageTimeout: %v, SData %x", from, self.Key, self.Id, self.requestTimeout, self.storageTimeout, self.SData[:end])
}

/*
Retrieve request

Timeout in milliseconds. Note that zero timeout retrieval requests do not request forwarding, but prompt for a peers message response. therefore they serve also
as messages to retrieve peers.

MaxSize specifies the maximum size that the peer will accept. This is useful in
particular if we allow storage and delivery of multichunk payload representing
the entire or partial subtree unfolding from the requested root key.
So when only interested in limited part of a stream (infinite trees) or only
testing chunk availability etc etc, we can indicate it by limiting the size here.

Request ID can be newly generated or kept from the request originator.
If request ID Is missing or zero, the request is handled as a lookup only
prompting a peers response but not launching a search. Lookup requests are meant
to be used to bootstrap kademlia tables.

In the special case that the key is the zero value as well, the remote peer's
address is assumed (the message is to be handled as a self lookup request).
The response is a PeersMsg with the peers in the kademlia proximity bin
corresponding to the address.
*/

type retrieveRequestMsgData struct {
    Key      storage.Key // target Key address of chunk to be retrieved
    Id       uint64      // request id, request is a lookup if missing or zero
    MaxSize  uint64      // maximum size of delivery accepted
    MaxPeers uint64      // maximum number of peers returned
    Timeout  uint64      // the longest time we are expecting a response
    timeout  *time.Time  // [not serialied]
    from     *peer       //
}

func (self *retrieveRequestMsgData) String() string {
    var from string
    if self.from == nil {
        from = "ourselves"
    } else {
        from = self.from.Addr().String()
    }
    var target []byte
    if len(self.Key) > 3 {
        target = self.Key[:4]
    }
    return fmt.Sprintf("from: %v, Key: %x; ID: %v, MaxSize: %v, MaxPeers: %d", from, target, self.Id, self.MaxSize, self.MaxPeers)
}

// lookups are encoded by missing request ID
func (self *retrieveRequestMsgData) isLookup() bool {
    return self.Id == 0
}

// sets timeout fields
func (self *retrieveRequestMsgData) setTimeout(t *time.Time) {
    self.timeout = t
    if t != nil {
        self.Timeout = uint64(t.UnixNano())
    } else {
        self.Timeout = 0
    }
}

func (self *retrieveRequestMsgData) getTimeout() (t *time.Time) {
    if self.Timeout > 0 && self.timeout == nil {
        timeout := time.Unix(int64(self.Timeout), 0)
        t = &timeout
        self.timeout = t
    }
    return
}

// peerAddr is sent in StatusMsg as part of the handshake
type peerAddr struct {
    IP   net.IP
    Port uint16
    ID   []byte // the 64 byte NodeID (ECDSA Public Key)
    Addr kademlia.Address
}

// peerAddr pretty prints as enode
func (self *peerAddr) String() string {
    var nodeid discover.NodeID
    copy(nodeid[:], self.ID)
    return discover.NewNode(nodeid, self.IP, 0, self.Port).String()
}

/*
peers Msg is one response to retrieval; it is always encouraged after a retrieval
request to respond with a list of peers in the same kademlia proximity bin.
The encoding of a peer is identical to that in the devp2p base protocol peers
messages: [IP, Port, NodeID]
note that a node's DPA address is not the NodeID but the hash of the NodeID.

Timeout serves to indicate whether the responder is forwarding the query within
the timeout or not.

NodeID serves as the owner of payment contracts and signer of proofs of transfer.

The Key is the target (if response to a retrieval request) or missing (zero value)
peers address (hash of NodeID) if retrieval request was a self lookup.

Peers message is requested by retrieval requests with a missing or zero value request ID
*/
type peersMsgData struct {
    Peers   []*peerAddr //
    Timeout uint64      //
    timeout *time.Time  // indicate whether responder is expected to deliver content
    Key     storage.Key // present if a response to a retrieval request
    Id      uint64      // present if a response to a retrieval request
    from    *peer
}

// peers msg pretty printer
func (self *peersMsgData) String() string {
    var from string
    if self.from == nil {
        from = "ourselves"
    } else {
        from = self.from.Addr().String()
    }
    var target []byte
    if len(self.Key) > 3 {
        target = self.Key[:4]
    }
    return fmt.Sprintf("from: %v, Key: %x; ID: %v, Peers: %v", from, target, self.Id, self.Peers)
}

func (self *peersMsgData) setTimeout(t *time.Time) {
    self.timeout = t
    if t != nil {
        self.Timeout = uint64(t.UnixNano())
    } else {
        self.Timeout = 0
    }
}

/*
syncRequest

is sent after the handshake to initiate syncing
the syncState of the remote node is persisted in kaddb and set on the
peer/protocol instance when the node is registered by hive as online{
*/

type syncRequestMsgData struct {
    SyncState *syncState `rlp:"nil"`
}

func (self *syncRequestMsgData) String() string {
    return fmt.Sprintf("%v", self.SyncState)
}

/*
deliveryRequest

is sent once a batch of sync keys is filtered. The ones not found are
sent as a list of syncReuest (hash, priority) in the Deliver field.
When the source receives the sync request it continues to iterate
and fetch at most N items as yet unsynced.
At the same time responds with deliveries of the items.
*/
type deliveryRequestMsgData struct {
    Deliver []*syncRequest
}

func (self *deliveryRequestMsgData) String() string {
    return fmt.Sprintf("sync request for new chunks\ndelivery request for %v chunks", len(self.Deliver))
}

/*
unsyncedKeys

is sent first after the handshake if SyncState iterator brings up hundreds, thousands?
and subsequently sent as a response to deliveryRequestMsgData.

Syncing is the iterative process of exchanging unsyncedKeys and deliveryRequestMsgs
both ways.

State contains the sync state sent by the source. When the source receives the
sync state it continues to iterate and fetch at most N items as yet unsynced.
At the same time responds with deliveries of the items.
*/
type unsyncedKeysMsgData struct {
    Unsynced []*syncRequest
    State    *syncState
}

func (self *unsyncedKeysMsgData) String() string {
    return fmt.Sprintf("sync: keys of %d new chunks (state %v) => synced: %v", len(self.Unsynced), self.State, self.State.Synced)
}

/*
payment

is sent when the swap balance is tilted in favour of the remote peer
and in absolute units exceeds the PayAt parameter in the remote peer's profile
*/

type paymentMsgData struct {
    Units   uint               // units actually paid for (checked against amount by swap)
    Promise *chequebook.Cheque // payment with cheque
}

func (self *paymentMsgData) String() string {
    return fmt.Sprintf("payment for %d units: %v", self.Units, self.Promise)
}