Completed
Pull Request — master (#42)
by thomas
04:49
created

Peer::alert()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 2
crap 2
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\BufferInterface;
21
use BitWasp\Buffertools\Parser;
22
use Evenement\EventEmitter;
23
use React\EventLoop\LoopInterface;
24
use React\EventLoop\Timer\Timer;
25
use React\Promise\Deferred;
26
use React\Socket\Connection;
27
use React\SocketClient\Connector;
28
use React\Stream\Stream;
29
30
class Peer extends EventEmitter
31
{
32
    const USER_AGENT = "bitcoin-php/v0.1";
33
    const PROTOCOL_VERSION = "70000";
34
35
    /**
36
     * @var string
37
     */
38
    private $buffer = '';
39
40
    /**
41
     * @var NetworkAddressInterface
42
     */
43
    private $localAddr;
44
45
    /**
46
     * @var NetworkAddressInterface
47
     */
48
    private $remoteAddr;
49
50
    /**
51
     * @var BloomFilter
52
     */
53
    private $filter;
54
55
    /**
56
     * @var LoopInterface
57
     */
58
    private $loop;
59
60
    /**
61
     * @var \BitWasp\Bitcoin\Networking\Messages\Factory
62
     */
63
    private $msgs;
64
65
    /**
66
     * @var Stream
67
     */
68
    private $stream;
69
70
    /**
71
     * @var bool
72
     */
73
    private $exchangedVersion = false;
74
75
    /**
76
     * @var int
77
     */
78
    private $lastPongTime;
79
80
    /**
81
     * @var bool
82
     */
83
    private $inbound;
84
85
    /**
86
     * Whether we want this peer to relay tx's to us.
87
     * @var bool
88
     */
89
    private $relayToUs = false;
90
91
    /**
92
     * @var bool
93
     */
94
    private $relayToPeer = false;
95
96
    private $downloadPeer = false;
97
98
    /**
99
     * @param NetworkAddressInterface $local
100
     * @param \BitWasp\Bitcoin\Networking\Messages\Factory $msgs
101
     * @param LoopInterface $loop
102
     */
103 17
    public function __construct(
104
        NetworkAddressInterface $local,
105
        \BitWasp\Bitcoin\Networking\Messages\Factory $msgs,
106
        LoopInterface $loop
107
    ) {
108 17
        $this->localAddr = $local;
109 17
        $this->msgs = $msgs;
110 17
        $this->loop = $loop;
111 17
        $this->lastPongTime = time();
112 17
    }
113
114
    /**
115
     * @return bool
116
     */
117
    public function isDownloadPeer()
118
    {
119
        return $this->downloadPeer;
120
    }
121
122
    /**
123
     * @param $flag
124
     */
125
    public function setDownloadPeerFlag($flag)
126
    {
127
        if (!is_bool($flag)) {
128
            throw new \InvalidArgumentException('Flag must be a bool');
129
        }
130
131
        $this->downloadPeer = $flag;
132
    }
133
134
    /**
135
     * @return NetworkAddressInterface
136
     */
137
    public function getRemoteAddr()
138
    {
139
        return $this->remoteAddr;
140
    }
141
142
    /**
143
     * @return NetworkAddressInterface
144
     */
145
    public function getLocalAddr()
146
    {
147
        return $this->localAddr;
148
    }
149
150
    /**
151
     * Set to true by calling requestRelay()
152
     * @return bool
153
     */
154
    public function checkWeRequestedRelay()
155
    {
156
        return $this->relayToUs;
157
    }
158
159
    /**
160
     * Check if peer sent version message requesting relay, or, set a filter.
161
     * @return bool
162
     */
163
    public function checkPeerRequestedRelay()
164
    {
165
        return $this->relayToPeer;
166
    }
167
168
    /**
169
     * Must be called before connect(). This tells the remote node to relay transactions to us.
170
     */
171 3
    public function requestRelay()
172
    {
173 3
        $this->relayToUs = true;
174 3
        return $this;
175
    }
176
177
    /**
178
     * @return bool
179
     */
180
    public function hasFilter()
181
    {
182
        return $this->filter !== null;
183
    }
184
185
    /**
186
     * @return BloomFilter
187
     * @throws \Exception
188
     */
189
    public function getFilter()
190
    {
191
        if (!$this->hasFilter()) {
192
            throw new \Exception('No filter set for peer');
193
        }
194
195
        return $this->filter;
196
    }
197
198
    /**
199
     * @return bool
200
     */
201
    public function ready()
202
    {
203
        return $this->exchangedVersion;
204
    }
205
206
    /**
207
     * @param NetworkSerializable $msg
208
     */
209 14
    public function send(NetworkSerializable $msg)
210
    {
211 14
        $net = $msg->getNetworkMessage();
212 14
        $this->stream->write($net->getBinary());
213 14
        $this->emit('send', [$net]);
214 14
    }
215
216
    /**
217
     * Handler for incoming data. Buffers possibly fragmented packets since they arrive sequentially.
218
     * Before finishing the version exchange, this will only emit Version and VerAck messages.
219
     */
220 14
    private function onData()
221
    {
222 14
        $tmp = $this->buffer;
223 14
        $parser = new Parser(new Buffer($tmp));
224
225
        try {
226 14
            while ($message = $this->msgs->parse($parser)) {
227 14
                $tmp = $parser->getBuffer()->slice($parser->getPosition())->getBinary();
228 14
                $command = $message->getCommand();
229 14
                if ($this->exchangedVersion || ($command == 'version' || $command == 'verack')) {
230 14
                    $this->emit('msg', [$this, $message]);
231 14
                }
232 14
            }
233 14
        } catch (\Exception $e) {
234 14
            $this->buffer = $tmp;
235
            // Do nothing - it was probably a fragmented message
236
        }
237 14
    }
238
239
    /**
240
     * Initializes basic peer functionality - used in server and client contexts
241
     *
242
     * @return $this
243
     */
244 14
    private function setupConnection()
245
    {
246
        $this->stream->on('data', function ($data) {
247 14
            $this->buffer .= $data;
248 14
            $this->emit('data');
249 14
        });
250
251
        $this->stream->on('close', function () {
252 8
            $this->close();
253 14
        });
254
255
        $this->on('data', function () {
256 14
            $this->onData();
257 14
        });
258
259
        $this->on('msg', function (Peer $peer, NetworkMessage $msg) {
260 14
            $this->emit($msg->getCommand(), [$peer, $msg->getPayload()]);
261 14
        });
262
263 14
        return $this;
264
    }
265
266
    /**
267
     * @param int $timeout
268
     * @return $this
269
     */
270
    public function timeoutWithoutVersion($timeout = 20)
271
    {
272
        $this->once('data', function () use ($timeout) {
273
            $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...
274
                if (false === $this->exchangedVersion) {
275
                    $this->intentionalClose();
276
                }
277
                $timer->cancel();
278
            });
279
        });
280
281
        return $this;
282
    }
283
284
    /**
285
     * @param Connection $connection
286
     * @return \React\Promise\Promise|\React\Promise\PromiseInterface
287
     */
288 9
    public function inboundConnection(Connection $connection)
289
    {
290 9
        $this->stream = $connection;
291 9
        $this->inbound = true;
292 9
        $this->setupConnection();
293 9
        $deferred = new Deferred();
294
295
        $this->on('version', function (Peer $peer, Version $version) {
296 9
            $this->remoteAddr = $version->getSenderAddress();
297 9
            $this->version();
298 9
        });
299
300
        $this->on('verack', function () use ($deferred) {
301 9
            if (false === $this->exchangedVersion) {
302 9
                $this->exchangedVersion = true;
303 9
                $this->verack();
304 9
                $this->emit('ready', [$this]);
305 9
                $deferred->resolve($this);
306 9
            }
307 9
        });
308
309 9
        return $deferred->promise();
310
    }
311
    
312
    /**
313
     * @param Connector $connector
314
     * @param NetworkAddressInterface $remoteAddr
315
     * @return \React\Promise\Promise|\React\Promise\PromiseInterface
316
     */
317 14
    public function connect(Connector $connector, NetworkAddressInterface $remoteAddr)
318
    {
319 14
        $deferred = new Deferred();
320 14
        $this->remoteAddr = $remoteAddr;
321 14
        $this->inbound = false;
322
323
        $connector
324 14
            ->create($this->remoteAddr->getIp(), $this->remoteAddr->getPort())
325
            ->then(function (Stream $stream) use ($deferred) {
326 14
                $this->stream = $stream;
327 14
                $this->setupConnection();
328
329
                $this->on('version', function () {
330 14
                    $this->verack();
331 14
                });
332
333
                $this->on('verack', function () use ($deferred) {
334 11
                    if (false === $this->exchangedVersion) {
335 11
                        $this->exchangedVersion = true;
336 11
                        $this->emit('ready', [$this]);
337 11
                        $deferred->resolve($this);
338 11
                    }
339 14
                });
340
341 14
                $this->version();
342
343 14
            }, function ($error) use ($deferred) {
344 2
                $deferred->reject($error);
345 14
            });
346
347
348 14
        return $deferred->promise();
349
    }
350
351
    /**
352
     *
353
     */
354
    public function intentionalClose()
355
    {
356
        $this->emit('intentionaldisconnect', [$this]);
357
        $this->close();
358
    }
359
360
    /**
361
     *
362
     */
363 9
    public function close()
364
    {
365 9
        $this->emit('close', [$this]);
366 9
        $this->stream->end();
367 9
        $this->removeAllListeners();
368 9
    }
369
370
    /**
371
     * @return \BitWasp\Bitcoin\Networking\Messages\Version
372
     */
373 14
    public function version()
374
    {
375 14
        $this->send($this->msgs->version(
376 14
            self::PROTOCOL_VERSION,
377 14
            Buffer::hex('0000000000000001', 8),
378 14
            time(),
379 14
            $this->remoteAddr,
380 14
            $this->localAddr,
381 14
            new Buffer(self::USER_AGENT),
382 14
            '363709',
383 14
            $this->relayToUs
384 14
        ));
385 14
    }
386
387
    /**
388
     *
389
     */
390 14
    public function verack()
391
    {
392 14
        $this->send($this->msgs->verack());
393 14
    }
394
395
    /**
396
     *
397
     */
398
    public function sendheaders()
399
    {
400
        $this->send($this->msgs->sendheaders());
401
    }
402
403
    /**
404
     * @param Inventory[] $vInv
405
     */
406
    public function inv(array $vInv)
407
    {
408
        $this->send($this->msgs->inv($vInv));
409
    }
410
411
    /**
412
     * @param Inventory[] $vInv
413
     */
414
    public function getdata(array $vInv)
415
    {
416
        $this->send($this->msgs->getdata($vInv));
417
    }
418
419
    /**
420
     * @param array $vInv
421
     */
422
    public function notfound(array $vInv)
423
    {
424
        $this->send($this->msgs->notfound($vInv));
425
    }
426
427
    /**
428
     * @param NetworkAddressTimestamp[] $vNetAddr
429
     */
430
    public function addr(array $vNetAddr)
431
    {
432
        $this->send($this->msgs->addr($vNetAddr));
433
    }
434
435
    /**
436
     *
437
     */
438
    public function getaddr()
439
    {
440
        $this->send($this->msgs->getaddr());
441
    }
442
443
    /**
444
     *
445
     */
446
    public function ping()
447
    {
448
        $this->send($this->msgs->ping());
449
    }
450
451
    /**
452
     * @param Ping $ping
453
     */
454
    public function pong(Ping $ping)
455
    {
456
        $this->send($this->msgs->pong($ping));
457
    }
458
459
    /**
460
     * @param TransactionInterface $tx
461
     */
462
    public function tx(TransactionInterface $tx)
463
    {
464
        $this->send($this->msgs->tx($tx));
465
    }
466
467
    /**
468
     * @param BlockLocator $locator
469
     */
470
    public function getblocks(BlockLocator $locator)
471
    {
472
        $this->send($this->msgs->getblocks(
473
            self::PROTOCOL_VERSION,
474
            $locator
475
        ));
476
    }
477
478
    /**
479
     * @param BlockLocator $locator
480
     */
481
    public function getheaders(BlockLocator $locator)
482
    {
483
        $this->send($this->msgs->getheaders(
484
            self::PROTOCOL_VERSION,
485
            $locator
486
        ));
487
    }
488
489
    /**
490
     * @param BlockInterface $block
491
     */
492
    public function block(BlockInterface $block)
493
    {
494
        $this->send($this->msgs->block($block));
495
    }
496
497
    /**
498
     * @param array $vHeaders
499
     */
500
    public function headers(array $vHeaders)
501
    {
502
        $this->send($this->msgs->headers($vHeaders));
503
    }
504
505
    /**
506
     * @param AlertDetail $detail
507
     * @param SignatureInterface $signature
508
     */
509
    public function alert(AlertDetail $detail, SignatureInterface $signature)
510
    {
511
        $this->send($this->msgs->alert($detail, $signature));
512
    }
513
514
    /**
515
     * @param int $feeRate
516
     */
517
    public function feefilter($feeRate)
518
    {
519
        $this->send($this->msgs->feefilter($feeRate));
520
    }
521
522
    /**
523
     * @param BufferInterface $data
524
     */
525
    public function filteradd(BufferInterface $data)
526
    {
527
        $this->send($this->msgs->filteradd($data));
528
    }
529
530
    /**
531
     * @param BloomFilter $filter
532
     */
533
    public function filterload(BloomFilter $filter)
534
    {
535
        $this->send($this->msgs->filterload($filter));
536
    }
537
538
    /**
539
     *
540
     */
541
    public function filterclear()
542
    {
543
        $this->send($this->msgs->filterclear());
544
    }
545
546
    /**
547
     * @param FilteredBlock $filtered
548
     */
549
    public function merkleblock(FilteredBlock $filtered)
550
    {
551
        $this->send($this->msgs->merkleblock($filtered));
552
    }
553
554
    /**
555
     *
556
     */
557
    public function mempool()
558
    {
559
        $this->send($this->msgs->mempool());
560
    }
561
562
    /**
563
     * Issue a Reject message, with a required $msg, $code, and $reason
564
     *
565
     * @param BufferInterface $msg
566
     * @param int $code
567
     * @param BufferInterface $reason
568
     * @param BufferInterface $data
569
     */
570
    public function reject(BufferInterface $msg, $code, BufferInterface $reason, BufferInterface $data = null)
571
    {
572
        $this->send($this->msgs->reject($msg, $code, $reason, $data));
573
    }
574
}
575