Completed
Push — master ( 1c3099...b9c07c )
by thomas
07:41
created

Peer   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 529
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 13

Test Coverage

Coverage 49.47%

Importance

Changes 7
Bugs 0 Features 3
Metric Value
wmc 50
c 7
b 0
f 3
lcom 2
cbo 13
dl 0
loc 529
ccs 94
cts 190
cp 0.4947
rs 8.6207

40 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A isDownloadPeer() 0 4 1
A setDownloadPeerFlag() 0 8 2
A getRemoteAddr() 0 4 1
A getLocalAddr() 0 4 1
A checkWeRequestedRelay() 0 4 1
A checkPeerRequestedRelay() 0 4 1
A requestRelay() 0 5 1
A hasFilter() 0 4 1
A getFilter() 0 8 2
A ready() 0 4 1
A send() 0 6 1
B onData() 0 18 6
A setupConnection() 0 21 1
A timeoutWithoutVersion() 0 13 2
A inboundConnection() 0 23 2
B connect() 0 33 2
A intentionalClose() 0 5 1
A close() 0 6 1
A version() 0 13 1
A verack() 0 4 1
A inv() 0 4 1
A getdata() 0 4 1
A notfound() 0 4 1
A addr() 0 4 1
A getaddr() 0 4 1
A ping() 0 4 1
A pong() 0 4 1
A tx() 0 4 1
A getblocks() 0 7 1
A getheaders() 0 7 1
A block() 0 4 1
A headers() 0 4 1
A alert() 0 4 1
A filteradd() 0 4 1
A filterload() 0 4 1
A filterclear() 0 4 1
A merkleblock() 0 4 1
A mempool() 0 4 1
A reject() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Peer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Peer, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace BitWasp\Bitcoin\Networking\Peer;
4
5
use BitWasp\Bitcoin\Block\BlockInterface;
6
use BitWasp\Bitcoin\Block\FilteredBlock;
7
use BitWasp\Bitcoin\Bloom\BloomFilter;
8
use BitWasp\Bitcoin\Chain\BlockLocator;
9
use BitWasp\Bitcoin\Networking\Messages\Version;
10
use BitWasp\Bitcoin\Networking\Messages\Ping;
11
use BitWasp\Bitcoin\Networking\NetworkMessage;
12
use BitWasp\Bitcoin\Networking\NetworkSerializable;
13
use BitWasp\Bitcoin\Networking\Structure\AlertDetail;
14
use BitWasp\Bitcoin\Networking\Structure\Inventory;
15
use BitWasp\Bitcoin\Networking\Structure\NetworkAddressInterface;
16
use BitWasp\Bitcoin\Networking\Structure\NetworkAddressTimestamp;
17
use BitWasp\Bitcoin\Crypto\EcAdapter\Signature\SignatureInterface;
18
use BitWasp\Bitcoin\Transaction\TransactionInterface;
19
use BitWasp\Buffertools\Buffer;
20
use BitWasp\Buffertools\Parser;
21
use Evenement\EventEmitter;
22
use React\EventLoop\LoopInterface;
23
use React\EventLoop\Timer\Timer;
24
use React\Promise\Deferred;
25
use React\Socket\Connection;
26
use React\SocketClient\Connector;
27
use React\Stream\Stream;
28
29
class Peer extends EventEmitter
30
{
31
    const USER_AGENT = "bitcoin-php/v0.1";
32
    const PROTOCOL_VERSION = "70000";
33
34
    /**
35
     * @var string
36
     */
37
    private $buffer = '';
38
39
    /**
40
     * @var NetworkAddressInterface
41
     */
42
    private $localAddr;
43
44
    /**
45
     * @var NetworkAddressInterface
46
     */
47
    private $remoteAddr;
48
49
    /**
50
     * @var BloomFilter
51
     */
52
    private $filter;
53
54
    /**
55
     * @var LoopInterface
56
     */
57
    private $loop;
58
59
    /**
60
     * @var \BitWasp\Bitcoin\Networking\Messages\Factory
61
     */
62
    private $msgs;
63
64
    /**
65
     * @var Stream
66
     */
67
    private $stream;
68
69
    /**
70
     * @var bool
71
     */
72
    private $exchangedVersion = false;
73
74
    /**
75
     * @var int
76
     */
77
    private $lastPongTime;
78
79
    /**
80
     * @var bool
81
     */
82
    private $inbound;
83
84
    /**
85
     * Whether we want this peer to relay tx's to us.
86
     * @var bool
87
     */
88
    private $relayToUs = false;
89
90
    /**
91
     * @var bool
92
     */
93
    private $relayToPeer = false;
94
95
    private $downloadPeer = false;
96
97
    /**
98
     * @param NetworkAddressInterface $local
99
     * @param \BitWasp\Bitcoin\Networking\Messages\Factory $msgs
100
     * @param LoopInterface $loop
101
     */
102 18
    public function __construct(
103
        NetworkAddressInterface $local,
104
        \BitWasp\Bitcoin\Networking\Messages\Factory $msgs,
105
        LoopInterface $loop
106
    ) {
107 18
        $this->localAddr = $local;
108 18
        $this->msgs = $msgs;
109 18
        $this->loop = $loop;
110 18
        $this->lastPongTime = time();
111 18
    }
112
113
    /**
114
     * @return bool
115
     */
116
    public function isDownloadPeer()
117
    {
118
        return $this->downloadPeer;
119
    }
120
121
    /**
122
     * @param $flag
123
     */
124
    public function setDownloadPeerFlag($flag)
125
    {
126
        if (!is_bool($flag)) {
127
            throw new \InvalidArgumentException('Flag must be a bool');
128
        }
129
130
        $this->downloadPeer = $flag;
131
    }
132
133
    /**
134
     * @return NetworkAddressInterface
135
     */
136
    public function getRemoteAddr()
137
    {
138
        return $this->remoteAddr;
139
    }
140
141
    /**
142
     * @return NetworkAddressInterface
143
     */
144
    public function getLocalAddr()
145
    {
146
        return $this->localAddr;
147
    }
148
149
    /**
150
     * Set to true by calling requestRelay()
151
     * @return bool
152
     */
153
    public function checkWeRequestedRelay()
154
    {
155
        return $this->relayToUs;
156
    }
157
158
    /**
159
     * Check if peer sent version message requesting relay, or, set a filter.
160
     * @return bool
161
     */
162
    public function checkPeerRequestedRelay()
163
    {
164
        return $this->relayToPeer;
165
    }
166
167
    /**
168
     * Must be called before connect(). This tells the remote node to relay transactions to us.
169
     */
170 3
    public function requestRelay()
171
    {
172 3
        $this->relayToUs = true;
173 3
        return $this;
174
    }
175
176
    /**
177
     * @return bool
178
     */
179
    public function hasFilter()
180
    {
181
        return $this->filter !== null;
182
    }
183
184
    /**
185
     * @return BloomFilter
186
     * @throws \Exception
187
     */
188
    public function getFilter()
189
    {
190
        if (!$this->hasFilter()) {
191
            throw new \Exception('No filter set for peer');
192
        }
193
194
        return $this->filter;
195
    }
196
197
    /**
198
     * @return bool
199
     */
200
    public function ready()
201
    {
202
        return $this->exchangedVersion;
203
    }
204
205
    /**
206
     * @param NetworkSerializable $msg
207
     */
208 15
    public function send(NetworkSerializable $msg)
209
    {
210 15
        $net = $msg->getNetworkMessage();
211 15
        $this->stream->write($net->getBinary());
212 15
        $this->emit('send', [$net]);
213 15
    }
214
215
    /**
216
     * Handler for incoming data. Buffers possibly fragmented packets since they arrive sequentially.
217
     * Before finishing the version exchange, this will only emit Version and VerAck messages.
218
     */
219 15
    private function onData()
220
    {
221 15
        $tmp = $this->buffer;
222 15
        $parser = new Parser(new Buffer($tmp));
223
224
        try {
225 15
            while ($message = $this->msgs->parse($parser)) {
226 15
                $tmp = $parser->getBuffer()->slice($parser->getPosition())->getBinary();
227 15
                $command = $message->getCommand();
228 15
                if ($this->exchangedVersion || ($command == 'version' || $command == 'verack')) {
229 15
                    $this->emit('msg', [$this, $message]);
230 15
                }
231 15
            }
232 15
        } catch (\Exception $e) {
233 15
            $this->buffer = $tmp;
234
            // Do nothing - it was probably a fragmented message
235
        }
236 15
    }
237
238
    /**
239
     * Initializes basic peer functionality - used in server and client contexts
240
     *
241
     * @return $this
242
     */
243 15
    private function setupConnection()
244
    {
245
        $this->stream->on('data', function ($data) {
246 15
            $this->buffer .= $data;
247 15
            $this->emit('data');
248 15
        });
249
250
        $this->stream->on('close', function () {
251 9
            $this->close();
252 15
        });
253
254
        $this->on('data', function () {
255 15
            $this->onData();
256 15
        });
257
258
        $this->on('msg', function (Peer $peer, NetworkMessage $msg) {
259 15
            $this->emit($msg->getCommand(), [$peer, $msg->getPayload()]);
260 15
        });
261
262 15
        return $this;
263
    }
264
265
    /**
266
     * @param int $timeout
267
     * @return $this
268
     */
269
    public function timeoutWithoutVersion($timeout = 20)
270
    {
271
        $this->once('data', function () use ($timeout) {
272
            $this->loop->addPeriodicTimer($timeout, function (Timer $timer) {
0 ignored issues
show
Documentation introduced by
$timeout is of type integer, but the function expects a object<React\EventLoop\numeric>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
273
                if (false === $this->exchangedVersion) {
274
                    $this->intentionalClose();
275
                }
276
                $timer->cancel();
277
            });
278
        });
279
280
        return $this;
281
    }
282
283
    /**
284
     * @param Connection $connection
285
     * @return \React\Promise\Promise|\React\Promise\PromiseInterface
286
     */
287 9
    public function inboundConnection(Connection $connection)
288
    {
289 9
        $this->stream = $connection;
290 9
        $this->inbound = true;
291 9
        $this->setupConnection();
292 9
        $deferred = new Deferred();
293
294
        $this->on('version', function (Peer $peer, Version $version) {
295 9
            $this->remoteAddr = $version->getSenderAddress();
296 9
            $this->version();
297 9
        });
298
299
        $this->on('verack', function () use ($deferred) {
300 9
            if (false === $this->exchangedVersion) {
301 9
                $this->exchangedVersion = true;
302 9
                $this->verack();
303 9
                $this->emit('ready', [$this]);
304 9
                $deferred->resolve($this);
305 9
            }
306 9
        });
307
308 9
        return $deferred->promise();
309
    }
310
    
311
    /**
312
     * @param Connector $connector
313
     * @param NetworkAddressInterface $remoteAddr
314
     * @return \React\Promise\Promise|\React\Promise\PromiseInterface
315
     */
316 15
    public function connect(Connector $connector, NetworkAddressInterface $remoteAddr)
317
    {
318 15
        $deferred = new Deferred();
319 15
        $this->remoteAddr = $remoteAddr;
320 15
        $this->inbound = false;
321
322
        $connector
323 15
            ->create($this->remoteAddr->getIp(), $this->remoteAddr->getPort())
324
            ->then(function (Stream $stream) use ($deferred) {
325 15
                $this->stream = $stream;
326 15
                $this->setupConnection();
327
328
                $this->on('version', function () {
329 15
                    $this->verack();
330 15
                });
331
332
                $this->on('verack', function () use ($deferred) {
333 12
                    if (false === $this->exchangedVersion) {
334 12
                        $this->exchangedVersion = true;
335 12
                        $this->emit('ready', [$this]);
336 12
                        $deferred->resolve($this);
337 12
                    }
338 15
                });
339
340 15
                $this->version();
341
342 15
            }, function ($error) use ($deferred) {
343 3
                $deferred->reject($error);
344 15
            });
345
346
347 15
        return $deferred->promise();
348
    }
349
350
    /**
351
     *
352
     */
353
    public function intentionalClose()
354
    {
355
        $this->emit('intentionaldisconnect', [$this]);
356
        $this->close();
357
    }
358
359
    /**
360
     *
361
     */
362 9
    public function close()
363
    {
364 9
        $this->emit('close', [$this]);
365 9
        $this->stream->end();
366 9
        $this->removeAllListeners();
367 9
    }
368
369
    /**
370
     * @return \BitWasp\Bitcoin\Networking\Messages\Version
371
     */
372 15
    public function version()
373
    {
374 15
        $this->send($this->msgs->version(
375 15
            self::PROTOCOL_VERSION,
376 15
            Buffer::hex('0000000000000001', 8),
377 15
            time(),
378 15
            $this->remoteAddr,
379 15
            $this->localAddr,
380 15
            new Buffer(self::USER_AGENT),
381 15
            '363709',
382 15
            $this->relayToUs
383 15
        ));
384 15
    }
385
386
    /**
387
     *
388
     */
389 15
    public function verack()
390
    {
391 15
        $this->send($this->msgs->verack());
392 15
    }
393
394
    /**
395
     * @param Inventory[] $vInv
396
     */
397
    public function inv(array $vInv)
398
    {
399
        $this->send($this->msgs->inv($vInv));
400
    }
401
402
    /**
403
     * @param Inventory[] $vInv
404
     */
405
    public function getdata(array $vInv)
406
    {
407
        $this->send($this->msgs->getdata($vInv));
408
    }
409
410
    /**
411
     * @param array $vInv
412
     */
413
    public function notfound(array $vInv)
414
    {
415
        $this->send($this->msgs->notfound($vInv));
416
    }
417
418
    /**
419
     * @param NetworkAddressTimestamp[] $vNetAddr
420
     */
421
    public function addr(array $vNetAddr)
422
    {
423
        $this->send($this->msgs->addr($vNetAddr));
424
    }
425
426
    /**
427
     *
428
     */
429
    public function getaddr()
430
    {
431
        $this->send($this->msgs->getaddr());
432
    }
433
434
    /**
435
     *
436
     */
437
    public function ping()
438
    {
439
        $this->send($this->msgs->ping());
440
    }
441
442
    /**
443
     * @param Ping $ping
444
     */
445
    public function pong(Ping $ping)
446
    {
447
        $this->send($this->msgs->pong($ping));
448
    }
449
450
    /**
451
     * @param TransactionInterface $tx
452
     */
453
    public function tx(TransactionInterface $tx)
454
    {
455
        $this->send($this->msgs->tx($tx));
456
    }
457
458
    /**
459
     * @param BlockLocator $locator
460
     */
461
    public function getblocks(BlockLocator $locator)
462
    {
463
        $this->send($this->msgs->getblocks(
464
            self::PROTOCOL_VERSION,
465
            $locator
466
        ));
467
    }
468
469
    /**
470
     * @param BlockLocator $locator
471
     */
472
    public function getheaders(BlockLocator $locator)
473
    {
474
        $this->send($this->msgs->getheaders(
475
            self::PROTOCOL_VERSION,
476
            $locator
477
        ));
478
    }
479
480
    /**
481
     * @param BlockInterface $block
482
     */
483
    public function block(BlockInterface $block)
484
    {
485
        $this->send($this->msgs->block($block));
486
    }
487
488
    /**
489
     * @param array $vHeaders
490
     */
491
    public function headers(array $vHeaders)
492
    {
493
        $this->send($this->msgs->headers($vHeaders));
494
    }
495
496
    /**
497
     * @param AlertDetail $detail
498
     * @param SignatureInterface $signature
499
     */
500
    public function alert(AlertDetail $detail, SignatureInterface $signature)
501
    {
502
        $this->send($this->msgs->alert($detail, $signature));
503
    }
504
505
    /**
506
     * @param Buffer $data
507
     */
508
    public function filteradd(Buffer $data)
509
    {
510
        $this->send($this->msgs->filteradd($data));
511
    }
512
513
    /**
514
     * @param BloomFilter $filter
515
     */
516
    public function filterload(BloomFilter $filter)
517
    {
518
        $this->send($this->msgs->filterload($filter));
519
    }
520
521
    /**
522
     *
523
     */
524
    public function filterclear()
525
    {
526
        $this->send($this->msgs->filterclear());
527
    }
528
529
    /**
530
     * @param FilteredBlock $filtered
531
     */
532
    public function merkleblock(FilteredBlock $filtered)
533
    {
534
        $this->send($this->msgs->merkleblock($filtered));
535
    }
536
537
    /**
538
     *
539
     */
540
    public function mempool()
541
    {
542
        $this->send($this->msgs->mempool());
543
    }
544
545
    /**
546
     * Issue a Reject message, with a required $msg, $code, and $reason
547
     *
548
     * @param Buffer $msg
549
     * @param int $code
550
     * @param Buffer $reason
551
     * @param Buffer $data
552
     */
553
    public function reject(Buffer $msg, $code, Buffer $reason, Buffer $data = null)
554
    {
555
        $this->send($this->msgs->reject($msg, $code, $reason, $data));
556
    }
557
}
558