Completed
Pull Request — master (#85)
by thomas
04:06
created

Manager::attemptNextPeer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 1.0002

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 14
cts 15
cp 0.9333
rs 9.2
c 0
b 0
f 0
cc 1
eloc 15
nc 1
nop 1
crap 1.0002
1
<?php
2
3
namespace BitWasp\Bitcoin\Networking\Peer;
4
5
use BitWasp\Bitcoin\Networking\Structure\NetworkAddressInterface;
6
use Evenement\EventEmitter;
7
use React\Promise\Deferred;
8
use React\Promise\RejectedPromise;
9
use React\Promise\Timer\TimeoutException;
10
11
class Manager extends EventEmitter
12
{
13
    /**
14
     * @var Connector
15
     */
16
    private $connector;
17
18
    /**
19
     * @var Peer[]
20
     */
21
    private $outPeers = [];
22
23
    /**
24
     * @var Peer[]
25
     */
26
    private $inPeers = [];
27
28
    /**
29
     * @var int
30
     */
31
    private $nOutPeers = 0;
32
33
    /**
34
     * @var int
35
     */
36
    private $nInPeers = 0;
37
38
    /**
39
     * Manager constructor.
40
     * @param Connector $connector
41
     */
42 6
    public function __construct(Connector $connector)
43
    {
44 6
        $this->connector = $connector;
45 6
    }
46
47
    /**
48
     * Store the newly connected peer, and trigger a new connection if they go away.
49
     *
50
     * @param Peer $peer
51
     * @return Peer
52
     */
53 2
    public function registerOutboundPeer(Peer $peer)
54
    {
55 2
        $next = $this->nOutPeers++;
56
        $peer->on('close', function ($peer) use ($next) {
57 2
            $this->emit('disconnect', [$peer]);
58 2
            unset($this->outPeers[$next]);
59 2
        });
60
61 2
        $this->outPeers[$next] = $peer;
62 2
        $this->emit('outbound', [$peer]);
63 2
        return $peer;
64
    }
65
66
    /**
67
     * @param Peer $peer
68
     */
69 3
    public function registerInboundPeer(Peer $peer)
70
    {
71 3
        $next = $this->nInPeers++;
72 3
        $this->inPeers[$next] = $peer;
73
        $peer->on('close', function () use ($next) {
74
            unset($this->inPeers[$next]);
75 3
        });
76 3
        $this->emit('inbound', [$peer]);
77 3
    }
78
79
    /**
80
     * @param Listener $listener
81
     * @return $this
82
     */
83 3
    public function registerListener(Listener $listener)
84
    {
85
        $listener->on('connection', function (Peer $peer) {
86 3
            $this->registerInboundPeer($peer);
87 3
        });
88
89 3
        return $this;
90
    }
91
92
    /**
93
     * @param NetworkAddressInterface $address
94
     * @return \React\Promise\PromiseInterface
95
     * @throws \Exception
96
     */
97 3
    public function connect(NetworkAddressInterface $address)
98
    {
99 3
        return $this->connector->connect($address);
100
    }
101
102
    /**
103
     * @param Locator $locator
104
     * @return \React\Promise\Promise|\React\Promise\PromiseInterface
105
     */
106 3
    public function getAnotherPeer(Locator $locator)
107
    {
108 3
        $deferred = new Deferred();
109
110
        // Otherwise, rely on the Locator.
111
        try {
112 3
            $deferred->resolve($locator->popAddress());
113 1
        } catch (\Exception $e) {
114
            $locator->queryDnsSeeds()->then(
115
                function () use ($deferred, $locator) {
116
                    $deferred->resolve($locator->popAddress());
117
                },
118
                function ($error) use ($deferred) {
119
                    var_dump($error);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($error); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
120
                    return new RejectedPromise();
121
                }
122
            );
123
        }
124
125 3
        return $deferred->promise();
126
    }
127
128
    /**
129
     * @param Locator $locator
130
     * @return \React\Promise\Promise|\React\Promise\PromiseInterface
131
     */
132 3
    public function attemptNextPeer(Locator $locator) {
133 3
        $attempt = new Deferred();
134
135 1
        $this
136 3
            ->getAnotherPeer($locator)
137
            ->then(function (NetworkAddressInterface $address) use ($attempt, $locator) {
138 3
                return $this->connect($address)->then(
139
                    function (Peer $peer) use ($attempt) {
140 2
                        $this->registerOutboundPeer($peer);
141 2
                        $attempt->resolve($peer);
142 2
                        return $peer;
143 3
                    },
144
                    function (\Exception $error) use ($attempt) {
145 1
                        $attempt->reject($error);
146 2
                    }
147 1
                );
148
            }, function ($error) use ($attempt) {
149
                $attempt->reject($error);
150 3
            });
151
152 3
        return $attempt->promise();
153
    }
154
155
    /**
156
     * @param Locator $locator
157
     * @param int $retries
158
     * @return \React\Promise\PromiseInterface
159
     */
160
    public function connectNextPeer(Locator $locator, $retries = 5)
161
    {
162 3
        $errorBack = function ($error) use ($locator, $retries) {
163 1
            $allowContinue = false;
164 1
            if ($error instanceof \RuntimeException && $error->getMessage() === "Connection refused") {
165 1
                $allowContinue = true;
166
            }
167
168 1
            if ($error instanceof TimeoutException) {
169 1
                $allowContinue = true;
170
            }
171
172 1
            if (!$allowContinue) {
173
                throw $error;
174
            }
175
176 1
            if (0 >= $retries) {
177 1
                throw new \RuntimeException("Connection to peers failed: too many retries");
178
            }
179
180 1
            return $this->connectNextPeer($locator, $retries - 1);
181 3
        };
182
183 1
        return $this
184 3
            ->attemptNextPeer($locator)
185 3
            ->then(null, $errorBack);
186
    }
187
188
    /**
189
     * @param Locator $locator
190
     * @param int $n
191
     * @return \React\Promise\Promise
192
     */
193 3
    public function connectToPeers(Locator $locator, $n)
194
    {
195 3
        $peers = [];
196 3
        for ($i = 0; $i < $n; $i++) {
197 3
            $peers[$i] = $this->connectNextPeer($locator);
198 1
        }
199
200 3
        return \React\Promise\all($peers);
201
    }
202
}
203