1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace BitWasp\Stratum; |
4
|
|
|
|
5
|
|
|
use BitWasp\Stratum\Api\ElectrumClient; |
6
|
|
|
use BitWasp\Stratum\Api\MiningClient; |
7
|
|
|
use BitWasp\Stratum\Notification\AddressNotification; |
8
|
|
|
use BitWasp\Stratum\Notification\HeadersNotification; |
9
|
|
|
use BitWasp\Stratum\Notification\MiningNotification; |
10
|
|
|
use BitWasp\Stratum\Notification\NotificationInterface; |
11
|
|
|
use BitWasp\Stratum\Notification\NumBlocksNotification; |
12
|
|
|
use BitWasp\Stratum\Notification\SetDifficultyNotification; |
13
|
|
|
use BitWasp\Stratum\Request\Request; |
14
|
|
|
use BitWasp\Stratum\Request\RequestFactory; |
15
|
|
|
use Evenement\EventEmitter; |
16
|
|
|
use React\Promise\Deferred; |
17
|
|
|
use React\Stream\Stream; |
18
|
|
|
|
19
|
|
|
class Connection extends EventEmitter |
20
|
|
|
{ |
21
|
|
|
/** |
22
|
|
|
* @var Stream |
23
|
|
|
*/ |
24
|
|
|
private $stream; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @var RequestFactory |
28
|
|
|
*/ |
29
|
|
|
private $factory; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* @var Deferred[] |
33
|
|
|
*/ |
34
|
|
|
private $deferred = []; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* @var string |
38
|
|
|
*/ |
39
|
|
|
private $streamBuffer = ''; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Connection constructor. |
43
|
|
|
* @param Stream $stream |
44
|
|
|
* @param RequestFactory $requestFactory |
45
|
|
|
*/ |
46
|
|
|
public function __construct(Stream $stream, RequestFactory $requestFactory) |
47
|
|
|
{ |
48
|
|
|
$this->factory = $requestFactory; |
49
|
|
|
$this->stream = $stream; |
50
|
|
|
$this->stream->on('data', [$this, 'onData']); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
public function close() |
54
|
|
|
{ |
55
|
|
|
return $this->stream->close(); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* @param string $method |
60
|
|
|
* @param array $params |
61
|
|
|
* @return \React\Promise\Promise|\React\Promise\PromiseInterface |
62
|
|
|
*/ |
63
|
|
|
public function request($method, array $params = []) |
64
|
|
|
{ |
65
|
|
|
$request = $this->factory->create($method, $params); |
66
|
|
|
return $this->send($request); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @param NotificationInterface $notification |
71
|
|
|
* @return \React\Promise\Promise|\React\Promise\PromiseInterface |
72
|
|
|
*/ |
73
|
|
|
public function notify(NotificationInterface $notification) |
74
|
|
|
{ |
75
|
|
|
return $this->stream->write($notification->toRequest()->write()); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* @param Request $request |
80
|
|
|
* @return \React\Promise\Promise|\React\Promise\PromiseInterface |
81
|
|
|
*/ |
82
|
|
|
public function send(Request $request) |
83
|
|
|
{ |
84
|
|
|
$result = new Deferred(); |
85
|
|
|
$this->deferred[$request->getId()] = $result; |
86
|
|
|
$this->stream->write($request->write()); |
87
|
|
|
|
88
|
|
|
return $result->promise(); |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* @param string $data |
93
|
|
|
*/ |
94
|
|
|
public function onData($data) |
95
|
|
|
{ |
96
|
|
|
$buffer = $this->streamBuffer . $data; |
97
|
|
|
|
98
|
|
|
while (($nextPos = strpos($buffer, "\n"))) { |
99
|
|
|
$msg = substr($buffer, 0, $nextPos); |
100
|
|
|
$buffer = substr($buffer, $nextPos); |
101
|
|
|
if (substr($buffer, -1) == "\n") { |
102
|
|
|
$buffer = substr($buffer, 1); |
103
|
|
|
} |
104
|
|
|
$this->onMessage($msg); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
if (!$buffer) { |
108
|
|
|
$this->streamBuffer = ''; |
109
|
|
|
} else { |
110
|
|
|
$this->streamBuffer = $buffer; |
111
|
|
|
} |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* @param string $data |
116
|
|
|
* @throws Exceptions\ApiError |
117
|
|
|
* @throws \Exception |
118
|
|
|
*/ |
119
|
|
|
public function onMessage($data) |
120
|
|
|
{ |
121
|
|
|
$response = $this->factory->response($data); |
122
|
|
|
if (isset($this->deferred[$response->getId()])) { |
123
|
|
|
$this->deferred[$response->getId()]->resolve($response); |
124
|
|
|
} else { |
125
|
|
|
$this->emit('message', [$response]); |
126
|
|
|
|
127
|
|
|
if ($response instanceof Request) { |
128
|
|
|
$params = $response->getParams(); |
129
|
|
|
|
130
|
|
|
switch ($response->getMethod()) { |
131
|
|
|
case ElectrumClient::HEADERS_SUBSCRIBE; |
|
|
|
|
132
|
|
|
if (!isset($params[0])) { |
133
|
|
|
throw new \RuntimeException('Headers notification missing body'); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
$header = $params[0]; |
137
|
|
|
if (count($header) !== 8) { |
138
|
|
|
throw new \RuntimeException('Headers notification missing parameter'); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
$this->emit(ElectrumClient::HEADERS_SUBSCRIBE, [new HeadersNotification($header[0], $header[1], $header[2], $header[3], $header[4], $header[5], $header[6], $header[7])]); |
142
|
|
|
break; |
143
|
|
|
case ElectrumClient::ADDRESS_SUBSCRIBE; |
|
|
|
|
144
|
|
|
if (!isset($params[0]) || !isset($params[1])) { |
145
|
|
|
throw new \RuntimeException('Address notification missing address/txid'); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
$this->emit(ElectrumClient::ADDRESS_SUBSCRIBE, [new AddressNotification($params[0], $params[1])]); |
149
|
|
|
break; |
150
|
|
|
case ElectrumClient::NUMBLOCKS_SUBSCRIBE; |
|
|
|
|
151
|
|
|
if (!isset($params[0])) { |
152
|
|
|
throw new \RuntimeException('Missing notification parameter: height'); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
$this->emit(ElectrumClient::NUMBLOCKS_SUBSCRIBE, [new NumBlocksNotification($params[0])]); |
156
|
|
|
break; |
157
|
|
|
case MiningClient::SET_DIFFICULTY; |
|
|
|
|
158
|
|
|
if (count($params) !== 1) { |
159
|
|
|
throw new \RuntimeException('Missing mining difficulty notification parameter'); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
$this->emit(MiningClient::SET_DIFFICULTY, [new SetDifficultyNotification($params[0])]); |
163
|
|
|
break; |
164
|
|
|
case MiningClient::NOTIFY; |
|
|
|
|
165
|
|
|
if (count($params) !== 9) { |
166
|
|
|
throw new \RuntimeException('Missing mining notification parameter'); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
$this->emit(MiningClient::NOTIFY, [new MiningNotification($params[0], $params[1], $params[2], $params[3], $params[4], $params[5], $params[6], $params[7], $params[8])]); |
170
|
|
|
break; |
171
|
|
|
} |
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
} |
175
|
|
|
} |
176
|
|
|
|
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next
break
.To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.