├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── src
├── BananoBlock.php
├── BananoCLI.php
├── BananoIPC.php
├── BananoRPC.php
├── BananoRPCExt.php
├── BananoTool.php
├── BananoWS.php
└── Util
│ ├── Base.php
│ ├── Bin.php
│ └── Uint.php
└── test
├── BananoBlock
├── change.php
├── open.php
├── receive.php
└── send.php
├── BananoCLI.php
├── BananoIPC.php
├── BananoIPCListen.php
├── BananoRPC.php
├── BananoRPCExt.php
├── BananoTool
├── account2public.php
├── den2den.php
├── den2raw.php
├── diff2mult.php
├── hashHexs.php
├── hex2mnem.php
├── keys.php
├── mnem2hex.php
├── mnem2mseed.php
├── mseed2keys.php
├── mult2diff.php
├── private2public.php
├── public2account.php
├── raw2den.php
├── seed2keys.php
├── sign.php
├── string2burn.php
├── validSign.php
├── validWork.php
└── work.php
├── BananoWS.php
├── autoload.php
└── speed
├── BananoIPC.php
└── BananoRPC.php
/.gitignore:
--------------------------------------------------------------------------------
1 | composer.lock
2 | .project
3 | /vendor
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 MikeRow
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BananoPHP
2 |
3 | PHP libraries and tools for Banano currency
4 |
5 | Examples at [BananoPHP/test](https://github.com/MikeRow/BananoPHP/tree/master/test)
6 |
7 | ---
8 |
9 | ## Install
10 |
11 |
12 | composer require mikerow/bananophp
13 |
14 |
15 | ---
16 |
17 | ## Features
18 |
19 | - [BananoBlock](https://github.com/MikeRow/BananoPHP/blob/master/src/BananoBlock.php)
20 |
21 | class for building Banano blocks
22 |
23 | - [BananoCLI](https://github.com/MikeRow/BananoPHP/blob/master/src/BananoCLI.php)
24 |
25 | class for interfacing to Banano node CLI
26 |
27 | - [BananoIPC](https://github.com/MikeRow/BananoPHP/blob/master/src/BananoIPC.php)
28 |
29 | class for interfacing to Banano node IPC
30 |
31 | - [BananoRPC](https://github.com/MikeRow/BananoPHP/blob/master/src/BananoRPC.php)
32 |
33 | class for interfacing to Banano node RPC
34 |
35 | - [BananoRPCExt](https://github.com/MikeRow/BananoPHP/blob/master/src/BananoRPCExt.php)
36 |
37 | additional functions for BananoRPC
38 |
39 | - [BananoTool](https://github.com/MikeRow/BananoPHP/blob/master/src/BananoTool.php)
40 |
41 | class for node-independent Banano functions
42 |
43 | - [BananoWS](https://github.com/MikeRow/BananoPHP/blob/master/src/BananoWS.php)
44 |
45 | class for interfacing to Banano node WebSocket
46 |
47 | ---
48 |
49 | ## FAQ
50 |
51 | #### How to perform calculations with Banano denominations or raws?
52 |
53 | PHP faces troubles when dealing with Banano amounts ...
54 |
55 |
56 | - Data type `float` isn't precise at certain decimal depths
57 | - Data type `integer` size is limited to 64 bit
58 |
59 | A good solution is to perform calculations in raws using [GNU Multiple Precision](https://www.php.net/manual/en/book.gmp.php)
60 |
61 |
62 |
63 |
64 | #### Why not use libsodium instead of Salt or php-blake2?
65 |
66 | Some limitations prevent the use of libsodium ...
67 |
68 |
69 | - Functions `sodium_crypto_sign_*` use SHA-2 instead Blake2
70 | - Functions `sodium_crypto_generichash_*` don't allow output smaller than 16 bytes
71 |
72 |
73 |
74 |
75 | ---
76 |
77 | ## To do
78 |
79 | - Add Epoch v2 support to BananoBlock
80 | - Add FlatBuffers support to BananoWS
81 | - Increase FlatBuffers performances
82 | - Enable listening on IPC
83 |
84 | ---
85 |
86 | ## Credits
87 |
88 | Thanks a lot for the work and effort of
89 |
90 | - [strawbrary/php-blake2](https://github.com/strawbrary/php-blake2)
91 | - [Textalk/websocket-php](https://github.com/Textalk/websocket-php)
92 | - [google/flatbuffers](https://github.com/google/flatbuffers)
93 | - [Bit-Wasp/bitcoin-lib-php](https://github.com/Bit-Wasp/bitcoin-lib-php)
94 | - [aceat64/EasyBitcoin-PHP](https://github.com/aceat64/EasyBitcoin-PHP)
95 | - [jaimehgb/RaiBlocksPHP](https://github.com/jaimehgb/RaiBlocksPHP)
96 | - [Sergey Kroshnin](https://github.com/SergiySW)
97 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "mikerow/bananophp",
3 | "type" : "library",
4 | "description" : "PHP libraries and tools for Banano currency",
5 | "keywords" : [
6 | "banano",
7 | "php",
8 | "cli",
9 | "rpc",
10 | "ipc",
11 | "websocket",
12 | "tools",
13 | "blocks"
14 | ],
15 | "homepage" : "https://github.com/MikeRow/BananoPHP",
16 | "license" : "MIT",
17 | "authors" : [{
18 | "name" : "MikeRow",
19 | "role" : "Developer"
20 | }
21 | ],
22 | "funding" : [{
23 | "type" : "Banano",
24 | "url" : "https://creeper.banano.cc/explorer/account/ban_1mikerow9bqzyqo4ejra6ugr1srerq1egwmacerquch3dz1wry7mkrz4768m"
25 | }
26 | ],
27 | "minimum-stability" : "stable",
28 | "require" : {
29 | "php-64bit" : "^7.2.0",
30 | "ext-curl" : "*",
31 | "ext-sockets" : "*",
32 | "ext-gmp" : "*",
33 | "ext-mbstring" : "*",
34 | "ext-openssl" : "*",
35 | "mikerow/salt" : "*",
36 | "bitwasp/bitcoin-lib" : "1.0.*",
37 | "textalk/websocket" : "*",
38 | "google/flatbuffers" : "*"
39 | },
40 | "suggest" : {
41 | "ext-blake2" : "Install blake2 extension in order to speed up some functions: https://github.com/strawbrary/php-blake2"
42 | },
43 | "autoload" : {
44 | "psr-4" : {
45 | "MikeRow\\BananoPHP\\" : "src/"
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/src/BananoBlock.php:
--------------------------------------------------------------------------------
1 | privateKey = $private_key;
45 | $this->publicKey = BananoTool::private2public($private_key);
46 | $this->account = BananoTool::public2account($this->publicKey);
47 | }
48 |
49 |
50 | // *
51 | // * Set previous block
52 | // *
53 |
54 | public function setPrev(string $prev_block_id, array $prev_block)
55 | {
56 | if (strlen($prev_block_id) != 64 || !hex2bin($prev_block_id)) {
57 | throw new BananoBlockException("Invalid previous block ID: $prev_block_id");
58 | }
59 | if (count($prev_block) < 8) {
60 | throw new BananoBlockException("Invalid previous block array count: less than 8");
61 | }
62 |
63 | $this->prevBlockId = $prev_block_id;
64 | $this->prevBlock = $prev_block;
65 | }
66 |
67 |
68 | // *
69 | // * Automatically set previous block
70 | // *
71 |
72 | public function autoPrev(bool $auto)
73 | {
74 | $this->prevAuto = $auto;
75 | }
76 |
77 |
78 | // *
79 | // * Set work
80 | // *
81 |
82 | public function setWork(string $work)
83 | {
84 | if (strlen($work) != 16 || !hex2bin($work)) {
85 | throw new BananoBlockException("Invalid work: $work");
86 | }
87 |
88 | $this->work = $work;
89 | }
90 |
91 |
92 | // *
93 | // * Build open block
94 | // *
95 |
96 | public function open(string $pairing_block_id, string $received_amount, string $representative): array
97 | {
98 | // Check inputs
99 | if (strlen($pairing_block_id) != 64 || !hex2bin($pairing_block_id)) {
100 | throw new BananoBlockException("Invalid pairing block ID: $pairing_block_id");
101 | }
102 | if (!ctype_digit($received_amount)) {
103 | throw new BananoBlockException("Invalid received amount: $received_amount");
104 | }
105 | if (!BananoTool::account2public($representative, false)) {
106 | throw new BananoBlockException("Invalid representative: $representative");
107 | }
108 | if ($this->work != null) {
109 | if (strlen($this->work) != 16 || !hex2bin($this->work)) {
110 | throw new BananoBlockException("Invalid work: {$this->work}");
111 | }
112 | }
113 |
114 | // Build block
115 | $balance = dechex($received_amount);
116 | $balance = str_repeat('0', (32 - strlen($balance))) . $balance;
117 |
118 | $this->rawBlockId = [];
119 | $this->rawBlockId[] = BananoTool::PREAMBLE_HEX;
120 | $this->rawBlockId[] = $this->publicKey;
121 | $this->rawBlockId[] = BananoTool::EMPTY32_HEX;
122 | $this->rawBlockId[] = BananoTool::account2public($representative);
123 | $this->rawBlockId[] = $balance;
124 | $this->rawBlockId[] = $pairing_block_id;
125 |
126 | $this->blockId = BananoTool::hashHexs($this->rawBlockId);
127 | $this->signature = BananoTool::sign($this->blockId, $this->privateKey);
128 |
129 | $this->block = [
130 | 'type' => 'state',
131 | 'account' => $this->account,
132 | 'previous' => BananoTool::EMPTY32_HEX,
133 | 'representative' => $representative,
134 | 'balance' => hexdec($balance),
135 | 'link' => $pairing_block_id,
136 | 'signature' => $this->signature,
137 | 'work' => $this->work
138 | ];
139 |
140 | if ($this->prevAuto) {
141 | $this->prevBlock = $this->block;
142 | $this->prevBlockId = $this->blockId;
143 | } else {
144 | $this->prevBlock = [];
145 | $this->prevBlockId = null;
146 | }
147 |
148 | $this->work = null;
149 |
150 | return $this->block;
151 | }
152 |
153 |
154 | // *
155 | // * Build receive block
156 | // *
157 |
158 | public function receive(string $pairing_block_id, string $received_amount, string $representative = null): array
159 | {
160 | // Check previous block info and ID
161 | if (!isset($this->prevBlock['balance']) ||
162 | !isset($this->prevBlock['representative']) ||
163 | !ctype_digit($this->prevBlock['balance']) ||
164 | !BananoTool::account2public($this->prevBlock['representative'], false)
165 | ) {
166 | throw new BananoBlockException("Invalid previous block");
167 | }
168 | if (strlen($this->prevBlockId) != 64 || !hex2bin($this->prevBlockId)) {
169 | throw new BananoBlockException("Invalid previous block ID: {$this->prevBlockId}");
170 | }
171 |
172 | // Check inputs
173 | if (strlen($pairing_block_id) != 64 || !hex2bin($pairing_block_id)) {
174 | throw new BananoBlockException("Invalid pairing block ID: $pairing_block_id");
175 | }
176 | if (!ctype_digit($received_amount)) {
177 | throw new BananoBlockException("Invalid received amount: $received_amount");
178 | }
179 | if ($representative == null) {
180 | $representative = $this->prevBlock['representative'];
181 | }
182 | if (!BananoTool::account2public($representative, false)) {
183 | throw new BananoBlockException("Invalid representative: $representative");
184 | }
185 | if ($this->work != null) {
186 | if (strlen($this->work) != 16 || !hex2bin($this->work)) {
187 | throw new BananoBlockException("Invalid work: {$this->work}");
188 | }
189 | }
190 |
191 | // Build block
192 | $balance = dechex(
193 | gmp_strval(
194 | gmp_add($this->prevBlock['balance'], $received_amount)
195 | )
196 | );
197 | $balance = str_repeat('0', (32 - strlen($balance))) . $balance;
198 |
199 | $this->rawBlockId = [];
200 | $this->rawBlockId[] = BananoTool::PREAMBLE_HEX;
201 | $this->rawBlockId[] = $this->publicKey;
202 | $this->rawBlockId[] = $this->prevBlockId;
203 | $this->rawBlockId[] = BananoTool::account2public($representative);
204 | $this->rawBlockId[] = $balance;
205 | $this->rawBlockId[] = $pairing_block_id;
206 |
207 | $this->blockId = BananoTool::hashHexs($this->rawBlockId);
208 | $this->signature = BananoTool::sign($this->blockId, $this->privateKey);
209 |
210 | $this->block = [
211 | 'type' => 'state',
212 | 'account' => $this->account,
213 | 'previous' => $this->prevBlockId,
214 | 'representative' => $representative,
215 | 'balance' => hexdec($balance),
216 | 'link' => $pairing_block_id,
217 | 'signature' => $this->signature,
218 | 'work' => $this->work
219 | ];
220 |
221 | if ($this->prevAuto) {
222 | $this->prevBlock = $this->block;
223 | $this->prevBlockId = $this->blockId;
224 | } else {
225 | $this->prevBlock = [];
226 | $this->prevBlockId = null;
227 | }
228 |
229 | $this->work = null;
230 |
231 | return $this->block;
232 | }
233 |
234 |
235 | // *
236 | // * Build send block
237 | // *
238 |
239 | public function send(string $destination, string $sending_amount, string $representative = null): array
240 | {
241 | // Check previous block info and ID
242 | if (!isset($this->prevBlock['balance']) ||
243 | !isset($this->prevBlock['representative']) ||
244 | !ctype_digit($this->prevBlock['balance']) ||
245 | !BananoTool::account2public($this->prevBlock['representative'], false)
246 | ) {
247 | throw new BananoBlockException("Invalid previous block");
248 | }
249 | if (strlen($this->prevBlockId) != 64 || !hex2bin($this->prevBlockId)) {
250 | throw new BananoBlockException("Invalid previous block ID: {$this->prevBlockId}");
251 | }
252 |
253 | // Check inputs
254 | if (!BananoTool::account2public($destination, false)) {
255 | throw new BananoBlockException("Invalid destination: $destination");
256 | }
257 | if (!ctype_digit($sending_amount)) {
258 | throw new BananoBlockException("Invalid sending amount: $sending_amount");
259 | }
260 | if ($representative == null) {
261 | $representative = $this->prevBlock['representative'];
262 | }
263 | if (!BananoTool::account2public($representative, false)) {
264 | throw new BananoBlockException("Invalid representative: $representative");
265 | }
266 | if ($this->work != null) {
267 | if (strlen($this->work) != 16 || !hex2bin($this->work)) {
268 | throw new BananoBlockException("Invalid work: {$this->work}");
269 | }
270 | }
271 |
272 | // Build block
273 | $balance = dechex(
274 | gmp_strval(
275 | gmp_sub($this->prevBlock['balance'], $sending_amount)
276 | )
277 | );
278 | if (strpos($balance, '-') !== false) {
279 | throw new BananoBlockException("Insufficient balance: $balance");
280 | }
281 | $balance = str_repeat('0', (32 - strlen($balance))) . $balance;
282 |
283 | $this->rawBlockId = [];
284 | $this->rawBlockId[] = BananoTool::PREAMBLE_HEX;
285 | $this->rawBlockId[] = $this->publicKey;
286 | $this->rawBlockId[] = $this->prevBlockId;
287 | $this->rawBlockId[] = BananoTool::account2public($representative);
288 | $this->rawBlockId[] = $balance;
289 | $this->rawBlockId[] = BananoTool::account2public($destination);
290 |
291 | $this->blockId = BananoTool::hashHexs($this->rawBlockId);
292 | $this->signature = BananoTool::sign($this->blockId, $this->privateKey);
293 |
294 | $this->block = [
295 | 'type' => 'state',
296 | 'account' => $this->account,
297 | 'previous' => $this->prevBlockId,
298 | 'representative' => $representative,
299 | 'balance' => hexdec($balance),
300 | 'link' => $destination,
301 | 'signature' => $this->signature,
302 | 'work' => $this->work
303 | ];
304 |
305 | if ($this->prevAuto) {
306 | $this->prevBlock = $this->block;
307 | $this->prevBlockId = $this->blockId;
308 | } else {
309 | $this->prevBlock = [];
310 | $this->prevBlockId = null;
311 | }
312 |
313 | $this->work = null;
314 |
315 | return $this->block;
316 | }
317 |
318 |
319 | // *
320 | // * Build change block
321 | // *
322 |
323 | public function change(string $representative): array
324 | {
325 | // Check previous block info and ID
326 | if (!isset($this->prevBlock['balance']) ||
327 | !isset($this->prevBlock['representative']) ||
328 | !ctype_digit($this->prevBlock['balance']) ||
329 | !BananoTool::account2public($this->prevBlock['representative'], false)
330 | ) {
331 | throw new BananoBlockException("Invalid previous block");
332 | }
333 | if (strlen($this->prevBlockId) != 64 || !hex2bin($this->prevBlockId)) {
334 | throw new BananoBlockException("Invalid previous block ID: {$this->prevBlockId}");
335 | }
336 |
337 | // Check inputs
338 | if (!BananoTool::account2public($representative, false)) {
339 | throw new BananoBlockException("Invalid representative: $representative");
340 | }
341 | if ($this->work != null) {
342 | if (strlen($this->work) != 16 || !hex2bin($this->work)) {
343 | throw new BananoBlockException("Invalid work: {$this->work}");
344 | }
345 | }
346 |
347 | // Build block
348 | $balance = dechex($this->prevBlock['balance']);
349 | $balance = str_repeat('0', (32 - strlen($balance))) . $balance;
350 |
351 | $this->rawBlockId = [];
352 | $this->rawBlockId[] = BananoTool::PREAMBLE_HEX;
353 | $this->rawBlockId[] = $this->publicKey;
354 | $this->rawBlockId[] = $this->prevBlockId;
355 | $this->rawBlockId[] = BananoTool::account2public($representative);
356 | $this->rawBlockId[] = $balance;
357 | $this->rawBlockId[] = BananoTool::EMPTY32_HEX;
358 |
359 | $this->blockId = BananoTool::hashHexs($this->rawBlockId);
360 | $this->signature = BananoTool::sign($this->blockId, $this->privateKey);
361 |
362 | $this->block = [
363 | 'type' => 'state',
364 | 'account' => $this->account,
365 | 'previous' => $this->prevBlockId,
366 | 'representative' => $representative,
367 | 'balance' => hexdec($balance),
368 | 'link' => BananoTool::EMPTY32_HEX,
369 | 'signature' => $this->signature,
370 | 'work' => $this->work
371 | ];
372 |
373 | if ($this->prevAuto) {
374 | $this->prevBlock = $this->block;
375 | $this->prevBlockId = $this->blockId;
376 | } else {
377 | $this->prevBlock = [];
378 | $this->prevBlockId = null;
379 | }
380 |
381 | $this->work = null;
382 |
383 | return $this->block;
384 | }
385 | }
386 |
--------------------------------------------------------------------------------
/src/BananoCLI.php:
--------------------------------------------------------------------------------
1 | pathToApp = escapeshellarg($path_to_app);
31 | }
32 |
33 |
34 | // *
35 | // * Call
36 | // *
37 |
38 | public function __call($method, array $params)
39 | {
40 | $this->id++;
41 | $this->response = null;
42 | $this->status = null;
43 | $this->error = null;
44 |
45 | if (!isset($params[0])) {
46 | $params[0] = [];
47 | }
48 |
49 | $request = ' --' . $method;
50 |
51 | if (isset($params[0])) {
52 | foreach ($params[0] as $key => $value) {
53 | $request .= ' --' . $key . '=' . $value;
54 | }
55 | }
56 |
57 | $this->error = exec($this->pathToApp . $request . ' 2>&1', $this->response, $this->status);
58 |
59 | if ($this->status == 0) {
60 | $this->error = null;
61 | return $this->response;
62 | } else {
63 | $this->response = null;
64 | return false;
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/BananoIPC.php:
--------------------------------------------------------------------------------
1 | pathToSocket = (string) $params[0];
51 | } else {
52 | $this->pathToSocket = '/tmp/banano';
53 | }
54 |
55 | // Timeout
56 | if (isset($options['timeout'])) {
57 | $this->options['timeout'] = (float) $options['timeout'];
58 | } else {
59 | $this->options['timeout'] = 15;
60 | }
61 |
62 | // Flags
63 | if (isset($options['flags'])) {
64 | $this->options['flags'] = (int) $options['flags'];
65 | } else {
66 | $this->options['flags'] = STREAM_CLIENT_CONNECT;
67 | }
68 |
69 | // Context
70 | if (isset($options['context']) && is_array($options['context'])) {
71 | $this->options['context'] = stream_context_create($options['context']);
72 | } else {
73 | $this->options['context'] = stream_context_create([]);
74 | }
75 |
76 |
77 | // * TCP
78 |
79 | } elseif ($transport_type == 'tcp') {
80 | // Hostname
81 | if (isset($params[0])) {
82 | $this->hostname = (string) $params[0];
83 | } else {
84 | $this->hostname = 'localhost';
85 | }
86 |
87 | // Port
88 | if (isset($params[1])) {
89 | $this->port = (int) $params[1];
90 | } else {
91 | $this->port = 7077;
92 | }
93 |
94 | // Timeout
95 | if (isset($options['timeout'])) {
96 | $this->options['timeout'] = (float) $options['timeout'];
97 | } else {
98 | $this->options['timeout'] = 15;
99 | }
100 |
101 | // Flags
102 | if (isset($options['flags'])) {
103 | $this->options['flags'] = (int) $options['flags'];
104 | } else {
105 | $this->options['flags'] = STREAM_CLIENT_CONNECT;
106 | }
107 |
108 | // Context
109 | if (isset($options['context']) && is_array($options['context'])) {
110 | $this->options['context'] = stream_context_create($options['context']);
111 | } else {
112 | $this->options['context'] = stream_context_create([]);
113 | }
114 | } else {
115 | throw new BananoIPCException("Invalid transport type: $transport_type");
116 | }
117 |
118 | $this->transportType = $transport_type;
119 | $this->bananoEncoding = 2;
120 | $this->bananoPreamble = 'N' . chr($this->bananoEncoding) . chr(0) . chr(0);
121 | $this->listen = false;
122 | }
123 |
124 |
125 | // *
126 | // * Set listen
127 | // *
128 |
129 | public function setListen(bool $listen)
130 | {
131 | $this->listen = $listen;
132 | }
133 |
134 |
135 | // *
136 | // * Set Banano encoding
137 | // *
138 |
139 | public function setBananoEncoding(int $banano_encoding)
140 | {
141 | if ($banano_encoding != 1 &&
142 | $banano_encoding != 2 &&
143 | $banano_encoding != 3 &&
144 | $banano_encoding != 4
145 | ) {
146 | throw new BananoIPCException("Invalid Banano encoding: $banano_encoding");
147 | }
148 |
149 | $this->bananoEncoding = $banano_encoding;
150 | $this->bananoPreamble = 'N' . chr($this->bananoEncoding) . chr(0) . chr(0);
151 | }
152 |
153 |
154 | // *
155 | // * Set Banano API key
156 | // *
157 |
158 | public function setBananoAPIKey(string $banano_api_key)
159 | {
160 | if (empty($banano_api_key)){
161 | throw new BananoIPCException("Invalid Banano API key: $banano_api_key");
162 | }
163 |
164 | $this->bananoAPIKey = (string) $banano_api_key;
165 | }
166 |
167 |
168 | // *
169 | // * Open connection
170 | // *
171 |
172 | public function open(): bool
173 | {
174 | // * Unix domain socket
175 |
176 | if ($this->transportType == 'unix') {
177 | $this->transport = stream_socket_client(
178 | "unix://{$this->pathToSocket}",
179 | $this->errorCode,
180 | $this->error,
181 | $this->options['timeout'],
182 | $this->options['flags'],
183 | $this->options['context']
184 | );
185 |
186 |
187 | // * TCP
188 |
189 | } elseif ($this->transportType == 'tcp') {
190 | $this->transport = stream_socket_client(
191 | "tcp://{$this->hostname}:{$this->port}",
192 | $this->errorCode,
193 | $this->error,
194 | $this->options['timeout'],
195 | $this->options['flags'],
196 | $this->options['context']
197 | );
198 | } else {
199 | throw new BananoIPCException("Invalid transport type");
200 | }
201 |
202 | if ($this->transport) {
203 | return true;
204 | } else {
205 | return false;
206 | }
207 | }
208 |
209 |
210 | // *
211 | // * Close connection
212 | // *
213 |
214 | public function close()
215 | {
216 | if ($this->transport != null) {
217 | stream_socket_shutdown($this->transport, STREAM_SHUT_RDWR);
218 | $this->transport = null;
219 | }
220 | }
221 |
222 |
223 | // *
224 | // * Call
225 | // *
226 |
227 | public function __call($method, array $params)
228 | {
229 | // Check transport connection
230 | if ($this->transport == null) {
231 | throw new BananoIPCException("Transport connection is not opened");
232 | }
233 |
234 | $this->id++;
235 | $this->response = null;
236 | $this->responseRaw = null;
237 | $this->responseType = null;
238 | $this->responseTime = null;
239 | $this->error = null;
240 | $this->errorCode = null;
241 |
242 | if (!isset($params[0])) {
243 | $params[0] = [];
244 | }
245 |
246 |
247 | // *
248 | // * Request: Banano encoding switch
249 | // *
250 |
251 | // * 1/2
252 |
253 | if ($this->bananoEncoding == 1 ||
254 | $this->bananoEncoding == 2
255 | ) {
256 | $request = $params[0];
257 | $request['action'] = $method;
258 |
259 | $request = json_encode($request);
260 |
261 |
262 | // * 3
263 |
264 | } elseif ($this->bananoEncoding == 3) {
265 | if (!class_exists('\\MikeRow\\Bandano\\BananoAPI\\' . $method, true)) {
266 | $this->error = 'Invalid call';
267 | return false;
268 | }
269 |
270 | $builder = new \Google\FlatBuffers\FlatbufferBuilder(0);
271 |
272 | foreach ($params[0] as $key => $value) {
273 | $params[0][$key] = $builder->createString($value);
274 | }
275 |
276 | $correlation_id = $builder->createString((string) $this->id);
277 | $credentials = $builder->createString($this->bananoAPIKey);
278 | $message_type = constant("\\MikeRow\\Bandano\\BananoAPI\\Message::$method");
279 |
280 | // Build arguments
281 | call_user_func_array(
282 | '\\MikeRow\\Bandano\\BananoAPI\\' . $method . '::start' . $method,
283 | [$builder]
284 | );
285 |
286 | foreach ($params[0] as $key => $value) {
287 | if (!method_exists('\\MikeRow\\Bandano\\BananoAPI\\' . $method, 'add' . $key)) {
288 | $this->error = 'Invalid call';
289 | return false;
290 | }
291 | call_user_func_array(
292 | '\\MikeRow\\Bandano\\BananoAPI\\' . $method . '::add' . $key,
293 | [$builder, $value]
294 | );
295 | }
296 |
297 | $message = call_user_func_array(
298 | '\\MikeRow\\Bandano\\BananoAPI\\' . $method . '::end' . $method,
299 | [$builder]
300 | );
301 |
302 | // Build envelope
303 | $envelope = \MikeRow\Bandano\BananoAPI\Envelope::createEnvelope(
304 | $builder,
305 | null,
306 | $credentials,
307 | $correlation_id,
308 | $message_type,
309 | $message
310 | );
311 |
312 | $builder->finish($envelope);
313 | $request = $builder->sizedByteArray();
314 |
315 |
316 | // * 4
317 |
318 | } elseif ($this->bananoEncoding == 4) {
319 | $request = [
320 | 'correlation_id' => (string) $this->id,
321 | 'message_type' => $method,
322 | 'message' => $params[0]
323 | ];
324 |
325 | // Banano API key
326 | if ($this->bananoAPIKey != null) {
327 | $request['credentials'] = $this->bananoAPIKey;
328 | }
329 |
330 | $request = json_encode($request);
331 | } else {
332 | throw new BananoIPCException("Invalid Banano encoding");
333 | }
334 |
335 | $buffer = $this->bananoPreamble . pack("N", strlen($request)) . $request;
336 |
337 |
338 | // *
339 | // * Request/Response: transport switch
340 | // *
341 |
342 | if ($this->transportType == 'unix' ||
343 | $this->transportType == 'tcp'
344 | ) {
345 | // Request
346 | $socket = fwrite($this->transport, $buffer);
347 | if ($socket === false) {
348 | $this->error = 'Unable to send request';
349 | return false;
350 | }
351 |
352 | // If listening, skip response
353 | if ($this->listen) {
354 | return;
355 | }
356 |
357 | // Response lenght
358 | $size = fread($this->transport, 4);
359 | if ($size === false) {
360 | $this->error = 'Unable to receive response lenght';
361 | return false;
362 | }
363 | if (strlen($size) == 0) {
364 | $this->error = 'Unable to receive response lenght';
365 | return false;
366 | }
367 |
368 | $size = unpack("N", $size);
369 |
370 | // Response
371 | $this->responseRaw = fread($this->transport, $size[1]);
372 | if ($this->responseRaw === false) {
373 | $this->error = 'Unable to receive response';
374 | return false;
375 | }
376 | } else {
377 | throw new BananoIPCException("Invalid transport type");
378 | }
379 |
380 |
381 | // *
382 | // * Response: Banano encoding switch
383 | // *
384 |
385 | // * 1/2
386 |
387 | if ($this->bananoEncoding == 1 ||
388 | $this->bananoEncoding == 2
389 | ) {
390 | $this->response = json_decode($this->responseRaw, true);
391 |
392 | if (isset($this->response['error'])) {
393 | $this->error = $this->response['error'];
394 | $this->response = null;
395 | }
396 |
397 |
398 | // * 3
399 |
400 | } elseif ($this->bananoEncoding == 3) {
401 | $buffer = \Google\FlatBuffers\ByteBuffer::wrap($this->responseRaw);
402 | $envelope = \MikeRow\Bandano\BananoAPI\Envelope::getRootAsEnvelope($buffer);
403 |
404 | $this->responseType = \MikeRow\Bandano\BananoAPI\Message::Name($envelope->getMessageType());
405 | $this->responseTime = $envelope->getTime();
406 |
407 | if ($envelope->getCorrelationId() != $this->id) {
408 | $this->error = 'Correlation Id doesn\'t match';
409 | }
410 |
411 | if ($this->responseType == 'Error') {
412 | $this->error = $envelope->getMessage(new \MikeRow\Bandano\BananoAPI\Error())->getMessage();
413 | $this->errorCode = $envelope->getMessage(new \MikeRow\Bandano\BananoAPI\Error())->getCode();
414 | } else {
415 | $model = '\\MikeRow\\Bandano\\BananoAPI\\' . $this->responseType;
416 |
417 | $methods = get_class_methods($model);
418 | foreach ($methods as $method) {
419 | if (substr($method, 0, 3) == 'get' &&
420 | $method != 'getRootAs' . $this->responseType
421 | ) {
422 | $this->response[substr($method, 3)] = $envelope->getMessage(new $model())->$method();
423 | }
424 | }
425 | }
426 |
427 |
428 | // * 4
429 |
430 | } elseif ($this->bananoEncoding == 4) {
431 | $this->response = json_decode($this->responseRaw, true);
432 |
433 | $this->responseType = $this->response['message_type'];
434 |
435 | $this->responseTime = (int) $this->response['time'];
436 |
437 | if ($this->response['correlation_id'] != $this->id) {
438 | $this->error = 'Correlation Id doesn\'t match';
439 | }
440 |
441 | if ($this->response['message_type'] == 'Error') {
442 | $this->error = $this->response['message'];
443 | $this->errorCode = (int) $this->response['message']['code'];
444 | $this->response = null;
445 | } else {
446 | $this->response = $this->response['message'];
447 | }
448 | } else {
449 | throw new BananoIPCException("Invalid Banano encoding");
450 | }
451 |
452 |
453 | // * Return
454 |
455 | if ($this->error) {
456 | return false;
457 | } else {
458 | return $this->response;
459 | }
460 | }
461 |
462 |
463 | // *
464 | // * Listen
465 | // *
466 |
467 | public function listen()
468 | {
469 | // Check transport connection
470 | if ($this->transport == null) {
471 | throw new BananoIPCException("Transport connection is not opened");
472 | }
473 |
474 | // Check if listen is enabled
475 | if (!$this->listen) {
476 | throw new BananoIPCException("Listen is not enabled");
477 | }
478 |
479 |
480 | // *
481 | // * Response: transport switch
482 | // *
483 |
484 | if ($this->transportType == 'unix' ||
485 | $this->transportType == 'tcp'
486 | ) {
487 | // Response lenght
488 | $size = fread($this->transport, 4);
489 | if ($size === false) {
490 | $this->error = 'Unable to receive response lenght';
491 | return false;
492 | }
493 | if (strlen($size) == 0) {
494 | $this->error = 'Unable to receive response lenght';
495 | return false;
496 | }
497 |
498 | $size = unpack("N", $size);
499 |
500 | // Response
501 | $this->responseRaw = fread($this->transport, $size[1]);
502 | if ($this->responseRaw === false) {
503 | $this->error = 'Unable to receive response';
504 | return false;
505 | }
506 | } else {
507 | throw new BananoIPCException("Invalid transport type");
508 | }
509 |
510 |
511 | // *
512 | // * Response: Banano encoding switch
513 | // *
514 |
515 | // * 1/2
516 |
517 | if ($this->bananoEncoding == 1 ||
518 | $this->bananoEncoding == 2
519 | ) {
520 | return json_decode($this->responseRaw, true);
521 |
522 |
523 | // * 3
524 |
525 | } elseif ($this->bananoEncoding == 3) {
526 | return \Google\FlatBuffers\ByteBuffer::wrap($this->responseRaw);
527 |
528 |
529 | // * 4
530 |
531 | } elseif ($this->bananoEncoding == 4) {
532 | return json_decode($this->responseRaw, true);
533 | } else {
534 | throw new BananoIPCException("Invalid Banano encoding");
535 | }
536 | }
537 | }
538 |
--------------------------------------------------------------------------------
/src/BananoRPC.php:
--------------------------------------------------------------------------------
1 | protocol = $protocol;
60 | $this->hostname = $hostname;
61 | $this->port = $port;
62 | $this->url = $url;
63 | $this->bananoApi = 1;
64 |
65 | $this->options =
66 | [
67 | CURLOPT_RETURNTRANSFER => true,
68 | CURLOPT_FOLLOWLOCATION => true,
69 | CURLOPT_USERAGENT => 'Bandano/BananoRPC',
70 | CURLOPT_MAXREDIRS => 10,
71 | CURLOPT_HTTPHEADER => ['Content-type: application/json']
72 | ];
73 |
74 | if (is_array($options)) {
75 | foreach ($options as $key => $value) {
76 | $this->options[$key] = $value;
77 | }
78 | }
79 | }
80 |
81 |
82 | // *
83 | // * Set Banano API
84 | // *
85 |
86 | public function setBananoApi(int $banano_api)
87 | {
88 | if ($banano_api != 1 &&
89 | $banano_api != 2
90 | ) {
91 | throw new BananoRPCException("Invalid Banano API: $banano_api");
92 | }
93 |
94 | $this->bananoApi = $banano_api;
95 | }
96 |
97 |
98 | // *
99 | // * Set Banano API key
100 | // *
101 |
102 | public function setBananoApiKey(string $banano_api_key)
103 | {
104 | if (empty($banano_api_key)){
105 | throw new BananoRPCException("Invalid Banano API key: $banano_api_key");
106 | }
107 |
108 | $this->bananoApiKey = (string) $banano_api_key;
109 | }
110 |
111 |
112 | // *
113 | // * Call
114 | // *
115 |
116 | public function __call($method, array $params)
117 | {
118 | $this->id++;
119 | $this->response = null;
120 | $this->responseRaw = null;
121 | $this->responseType = null;
122 | $this->responseTime = null;
123 | $this->status = null;
124 | $this->error = null;
125 | $this->errorCode = null;
126 |
127 | if (!isset($params[0])) {
128 | $params[0] = [];
129 | }
130 |
131 |
132 | // *
133 | // * Request: API switch
134 | // *
135 |
136 | // * v1
137 |
138 | if ($this->bananoApi == 1) {
139 | $request = $params[0];
140 | $request['action'] = $method;
141 |
142 |
143 | // * v2
144 |
145 | } elseif ($this->bananoApi == 2) {
146 | $request = [
147 | 'correlation_id' => (string) $this->id,
148 | 'message_type' => $method,
149 | 'message' => $params[0]
150 | ];
151 |
152 | // Banano API key
153 | if ($this->bananoApiKey != null) {
154 | $request['credentials'] = $this->bananoApiKey;
155 | }
156 | } else {
157 | throw new BananoRPCException("Invalid Banano API key");
158 | }
159 |
160 | $request = json_encode($request);
161 |
162 |
163 | // * Build the cURL session
164 |
165 | $curl = curl_init("{$this->protocol}://{$this->hostname}:{$this->port}/{$this->url}");
166 |
167 | $this->options[CURLOPT_POST] = true;
168 | $this->options[CURLOPT_POSTFIELDS] = $request;
169 |
170 |
171 | // * Call
172 |
173 | // This prevents users from getting the following warning when open_basedir is set:
174 | // Warning: curl_setopt() [function.curl-setopt]: CURLOPT_FOLLOWLOCATION cannot be activated when in safe_mode or an open_basedir is set
175 | if (ini_get('open_basedir')) {
176 | unset($options[CURLOPT_FOLLOWLOCATION]);
177 | }
178 |
179 | curl_setopt_array($curl, $this->options);
180 |
181 | // Execute the request and decode to an array
182 | $this->responseRaw = curl_exec($curl);
183 | $this->response = json_decode($this->responseRaw, true);
184 |
185 |
186 | // *
187 | // * Response: API switch
188 | // *
189 |
190 | // * v1
191 |
192 | if ($this->bananoApi == 1) {
193 | if (isset($this->response['error'])) {
194 | $this->error = $this->response['error'];
195 | $this->response = null;
196 | }
197 |
198 |
199 | // * v2
200 |
201 | } elseif ($this->bananoApi == 2) {
202 | $this->responseType = $this->response['message_type'];
203 |
204 | $this->responseTime = (int) $this->response['time'];
205 |
206 | if ($this->response['correlation_id'] != $this->id) {
207 | $this->error = 'Correlation Id doesn\'t match';
208 | }
209 |
210 | if ($this->response['message_type'] == 'Error') {
211 | $this->error = $this->response['message'];
212 | $this->errorCode = (int) $this->response['message']['code'];
213 | $this->response = null;
214 | } else {
215 | $this->response = $this->response['message'];
216 | }
217 | } else {
218 | throw new BananoRPCException("Invalid Banano API key");
219 | }
220 |
221 |
222 | // * cURL errors
223 |
224 | // If the status is not 200, something is wrong
225 | $this->status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
226 |
227 | // If there was no error, this will be an empty string
228 | $curl_error = curl_error($curl);
229 |
230 | curl_close($curl);
231 |
232 | if (!empty($curl_error)) {
233 | $this->error = $curl_error;
234 | }
235 |
236 | if ($this->status != 200) {
237 | // If node didn't return a nice error message, we need to make our own
238 | switch ($this->status) {
239 | case 400:
240 | $this->error = 'HTTP_BAD_REQUEST';
241 | break;
242 |
243 | case 401:
244 | $this->error = 'HTTP_UNAUTHORIZED';
245 | break;
246 |
247 | case 403:
248 | $this->error = 'HTTP_FORBIDDEN';
249 | break;
250 |
251 | case 404:
252 | $this->error = 'HTTP_NOT_FOUND';
253 | break;
254 | }
255 | }
256 |
257 |
258 | // * Return
259 |
260 | if ($this->error) {
261 | return false;
262 | } else {
263 | return $this->response;
264 | }
265 | }
266 | }
267 |
--------------------------------------------------------------------------------
/src/BananoRPCExt.php:
--------------------------------------------------------------------------------
1 | error = 'Unable to parse Array';
20 | return false;
21 | }
22 |
23 | $wallet = $args['wallet'];
24 | $destination = $args['destination'];
25 |
26 | // Wallet ok?
27 | $wallet_info = $this->wallet_info(['wallet' => $wallet]);
28 |
29 | if ($this->error != null) {
30 | $this->error = 'Bad wallet number';
31 | return false;
32 | }
33 |
34 | // Balance ok?
35 | if (gmp_cmp($wallet_info['balance'], 1) < 0) {
36 | $this->error = 'Insufficient balance';
37 | return false;
38 | }
39 |
40 | // Destination ok?
41 | if (!BananoTool::account2public($destination, false)) {
42 | $this->error = 'Bad destination';
43 | return false;
44 | }
45 |
46 | // Any sort?
47 | $sort = isset($args['sort']) ? $args['sort'] : 'list';
48 |
49 | //
50 |
51 | $return = ['balances' => []];
52 |
53 | // Get wallet balances
54 | $args = [
55 | 'wallet' => $wallet,
56 | 'threshold' => 1
57 | ];
58 |
59 | $wallet_balances = $this->wallet_balances($args);
60 |
61 | // Sort balances
62 | if ($sort == 'asc') {
63 | uasort($wallet_balances['balances'], function ($a, $b) {
64 | return gmp_cmp($a['balance'], $b['balance']);
65 | });
66 | } elseif ($sort == 'desc') {
67 | uasort($wallet_balances['balances'], function ($a, $b) {
68 | return gmp_cmp($b['balance'], $a['balance']);
69 | });
70 | } else {
71 | // Do nothing
72 | }
73 |
74 | // Sweep wallet
75 | foreach ($wallet_balances['balances'] as $account => $balances) {
76 | if ($account == $destination) {
77 | $return['balances'][$account] = [
78 | 'notice' => 'Skipped self send',
79 | 'amount' => $balances['balance']
80 | ];
81 | continue;
82 | }
83 |
84 | $args = [
85 | 'wallet' => $wallet,
86 | 'source' => $account,
87 | 'destination' => $destination,
88 | 'amount' => $balances['balance'],
89 | 'id' => uniqid()
90 | ];
91 |
92 | $send = $this->send($args);
93 |
94 | // Send
95 | $return['balances'][$account] = [
96 | 'block' => $send['block'],
97 | 'amount' => $balances['balance']
98 | ];
99 |
100 | if ($send['block'] == BananoTool::EMPTY32_HEX) {
101 | $return['balances'][$account] = [
102 | 'error' => 'Bad send',
103 | 'amount' => $balances['balance']
104 | ];
105 | }
106 | }
107 |
108 | $this->responseRaw = json_encode($return);
109 | $this->response = $return;
110 |
111 | return $this->response;
112 | }
113 |
114 | public function wallet_wipe(array $args)
115 | {
116 | return $this->wallet_sweep($args);
117 | }
118 |
119 |
120 | // *
121 | // * Wallet send
122 | // *
123 |
124 | public function wallet_send(array $args)
125 | {
126 | // Check args
127 | if (!isset($args['wallet']) || !isset($args['destination']) || !isset($args['amount'])) {
128 | $this->error = 'Unable to parse Array';
129 | return false;
130 | }
131 |
132 | $wallet = $args['wallet'];
133 | $destination = $args['destination'];
134 | $amount = $args['amount'];
135 |
136 | // Wallet ok?
137 | $wallet_info = $this->wallet_info(['wallet' => $wallet]);
138 |
139 | if ($this->error != null) {
140 | $this->error = 'Bad wallet number';
141 | return false;
142 | }
143 |
144 | // Destination ok?
145 | if (!BananoTool::account2public($destination, false)) {
146 | $this->error = 'Bad destination';
147 | return false;
148 | }
149 |
150 | // Amount ok?
151 | if (!ctype_digit($amount)) {
152 | $this->error = 'Bad amount';
153 | return false;
154 | }
155 |
156 | if (gmp_cmp($amount, 1) < 0) {
157 | $this->error = 'Bad amount';
158 | return false;
159 | }
160 |
161 | if (gmp_cmp($wallet_info['balance'], $amount) < 0) {
162 | $this->error = 'Insufficient balance';
163 | return false;
164 | }
165 |
166 | // Any sort?
167 | $sort = isset($args['sort']) ? $args['sort'] : 'list';
168 |
169 | //
170 |
171 | $return = ['balances' => []];
172 | $selected_accounts = [];
173 | $amount_left = $amount;
174 |
175 | // Get wallet balances
176 | $args = [
177 | 'wallet' => $wallet,
178 | 'threshold' => 1
179 | ];
180 |
181 | $wallet_balances = $this->wallet_balances($args);
182 |
183 | // Sort balances
184 | if ($sort == 'asc') {
185 | uasort($wallet_balances['balances'], function ($a, $b) {
186 | return gmp_cmp($a['balance'], $b['balance']);
187 | });
188 | } elseif ($sort == 'desc') {
189 | uasort($wallet_balances['balances'], function ($a, $b) {
190 | return gmp_cmp($b['balance'], $a['balance']);
191 | });
192 | } else {
193 | // Do nothing
194 | }
195 |
196 | // Select accounts
197 | foreach ($wallet_balances['balances'] as $account => $balances) {
198 | if (gmp_cmp($balances['balance'], $amount_left) >= 0) {
199 | $selected_accounts[$account] = $amount_left;
200 | $amount_left = '0';
201 | } else {
202 | $selected_accounts[$account] = $balances['balance'];
203 | $amount_left = gmp_strval(gmp_sub($amount_left, $balances['balance']));
204 | }
205 |
206 | if (gmp_cmp($amount_left, '0') <= 0) {
207 | break; // Amount reached
208 | }
209 | }
210 |
211 | // Send from selected accounts
212 | foreach ($selected_accounts as $account => $balance) {
213 | if ($account == $destination) {
214 | $return['balances'][$account] = [
215 | 'notice' => 'Skipped self send',
216 | 'amount' => $balances['balance']
217 | ];
218 | continue;
219 | }
220 |
221 | $args = [
222 | 'wallet' => $wallet,
223 | 'source' => $account,
224 | 'destination' => $destination,
225 | 'amount' => $balance,
226 | 'id' => uniqid()
227 | ];
228 |
229 | $send = $this->send($args);
230 |
231 | // Send
232 | $return['balances'][$account] = [
233 | 'block' => $send['block'],
234 | 'amount' => $balances['balance']
235 | ];
236 |
237 | if ($send['block'] == BananoTool::EMPTY32_HEX) {
238 | $return['balances'][$account] = [
239 | 'error' => 'Bad send',
240 | 'amount' => $balances['balance']
241 | ];
242 | }
243 | }
244 |
245 | $this->responseRaw = json_encode($return);
246 | $this->response = $return;
247 |
248 | return $this->response;
249 | }
250 |
251 |
252 | // *
253 | // * Wallet weight
254 | // *
255 |
256 | public function wallet_weight(array $args)
257 | {
258 | // Check args
259 | if (!isset($args['wallet'])) {
260 | $this->error = 'Unable to parse Array';
261 | return false;
262 | }
263 |
264 | $wallet = $args['wallet'];
265 |
266 | // Wallet ok?
267 | $wallet_info = $this->wallet_info(['wallet' => $wallet]);
268 |
269 | if ($this->error != null) {
270 | $this->error = 'Bad wallet number';
271 | return false;
272 | }
273 |
274 | // Any sort?
275 |
276 | $sort = isset($args['sort']) ? $args['sort'] : 'list';
277 |
278 | //
279 |
280 | $return = ['weight' => '', 'weights' => []];
281 | $wallet_weight = '0';
282 |
283 | // Get wallet balances
284 | $args = [
285 | 'wallet' => $wallet
286 | ];
287 |
288 | $wallet_accounts = $this->account_list($args);
289 |
290 | // Check every weight and sum them
291 | foreach ($wallet_accounts['accounts'] as $account) {
292 | $account_weight = $this->account_weight(['account'=>$account]);
293 | $wallet_weight = gmp_add($wallet_weight, $account_weight['weight']);
294 | $return['weights'][$account] = gmp_strval($account_weight['weight']);
295 | }
296 |
297 | $return['weight'] = gmp_strval($wallet_weight);
298 |
299 | // Sort weights
300 | if ($sort == 'asc') {
301 | uasort($return['weights'], function ($a, $b) {
302 | return gmp_cmp($a, $b);
303 | });
304 | } elseif ($sort == 'desc') {
305 | uasort($return['weights'], function ($a, $b) {
306 | return gmp_cmp($b, $a);
307 | });
308 | } else {
309 | // Do nothing
310 | }
311 |
312 | $this->responseRaw = json_encode($return);
313 | $this->response = $return;
314 |
315 | return $this->response;
316 | }
317 | }
318 |
--------------------------------------------------------------------------------
/src/BananoTool.php:
--------------------------------------------------------------------------------
1 | '1000000000000000000000000000',
23 | 'BANANO' => '100000000000000000000000000000'
24 | ];
25 |
26 | const PREAMBLE_HEX = '0000000000000000000000000000000000000000000000000000000000000006';
27 | const EMPTY32_HEX = '0000000000000000000000000000000000000000000000000000000000000000';
28 | const HARDENED = 0x80000000;
29 |
30 |
31 | // *
32 | // * Denomination to raw
33 | // *
34 |
35 | public static function den2raw($amount, string $denomination): string
36 | {
37 | if (!array_key_exists($denomination, self::RAWS)) {
38 | throw new BananoToolException("Invalid denomination: $denomination");
39 | }
40 |
41 | $raw_to_denomination = self::RAWS[$denomination];
42 |
43 | if ($amount == 0) {
44 | return '0';
45 | }
46 |
47 | if (strpos($amount, '.')) {
48 | $dot_pos = strpos($amount, '.');
49 | $number_len = strlen($amount) - 1;
50 | $raw_to_denomination = substr($raw_to_denomination, 0, -($number_len - $dot_pos));
51 | }
52 |
53 | $amount = str_replace('.', '', $amount) . str_replace('1', '', $raw_to_denomination);
54 |
55 | // Remove useless zeros from left
56 | while (substr($amount, 0, 1) == '0') {
57 | $amount = substr($amount, 1);
58 | }
59 |
60 | return $amount;
61 | }
62 |
63 |
64 | // *
65 | // * Raw to denomination
66 | // *
67 |
68 | public static function raw2den(string $amount, string $denomination): string
69 | {
70 | if (!array_key_exists($denomination, self::RAWS)) {
71 | throw new BananoToolException("Invalid denomination: $denomination");
72 | }
73 |
74 | $raw_to_denomination = self::RAWS[$denomination];
75 |
76 | if ($amount == '0') {
77 | return 0;
78 | }
79 |
80 | $prefix_lenght = 39 - strlen($amount);
81 |
82 | $i = 0;
83 |
84 | while ($i < $prefix_lenght) {
85 | $amount = '0' . $amount;
86 | $i++;
87 | }
88 |
89 | $amount = substr_replace($amount, '.', -(strlen($raw_to_denomination)-1), 0);
90 |
91 | // Remove useless zeroes from left
92 | while (substr($amount, 0, 1) == '0' && substr($amount, 1, 1) != '.') {
93 | $amount = substr($amount, 1);
94 | }
95 |
96 | // Remove useless decimals
97 | while (substr($amount, -1) == '0') {
98 | $amount = substr($amount, 0, -1);
99 | }
100 |
101 | // Remove dot if all decimals are zeros
102 | if (substr($amount, -1) == '.') {
103 | $amount = substr($amount, 0, -1);
104 | }
105 |
106 | return $amount;
107 | }
108 |
109 |
110 | // *
111 | // * Denomination to denomination
112 | // *
113 |
114 | public static function den2den($amount, string $denomination_from, string $denomination_to): string
115 | {
116 | if (!array_key_exists($denomination_from, self::RAWS)) {
117 | throw new BananoToolException("Invalid source denomination: $denomination_from");
118 | }
119 | if (!array_key_exists($denomination_to, self::RAWS)) {
120 | throw new BananoToolException("Invalid target denomination: $denomination_to");
121 | }
122 |
123 | $raw = self::den2raw($amount, $denomination_from);
124 |
125 | return self::raw2den($raw, $denomination_to);
126 | }
127 |
128 |
129 | // *
130 | // * Account to public key
131 | // *
132 |
133 | public static function account2public(string $account, bool $get_public_key = true)
134 | {
135 | if ((strpos($account, 'ban_1') === 0 ||
136 | strpos($account, 'ban_3') === 0) &&
137 | strlen($account) == 64
138 | ) {
139 | $crop = explode('_', $account);
140 | $crop = $crop[1];
141 |
142 | if (preg_match('/^[13456789abcdefghijkmnopqrstuwxyz]+$/', $crop)) {
143 | $aux = \MikeRow\BananoPHP\Util\Uint::fromString(substr($crop, 0, 52))->toUint4()->toArray();
144 | array_shift($aux);
145 | $key_uint4 = $aux;
146 | $hash_uint8 = \MikeRow\BananoPHP\Util\Uint::fromString(substr($crop, 52, 60))->toUint8()->toArray();
147 | $key_uint8 = \MikeRow\BananoPHP\Util\Uint::fromUint4Array($key_uint4)->toUint8();
148 |
149 | if (!extension_loaded('blake2')) {
150 | $key_hash = new SplFixedArray(64);
151 | $b2b = new Blake2b();
152 | $ctx = $b2b->init(null, 5);
153 | $b2b->update($ctx, $key_uint8, 32);
154 | $b2b->finish($ctx, $key_hash);
155 | $key_hash = array_reverse(array_slice($key_hash->toArray(), 0, 5));
156 | } else {
157 | $key_uint8 = \MikeRow\BananoPHP\Util\Bin::arr2bin((array) $key_uint8);
158 | $key_hash = blake2($key_uint8, 5, null, true);
159 | $key_hash = \MikeRow\BananoPHP\Util\Bin::bin2arr(strrev($key_hash));
160 | }
161 |
162 | if ($hash_uint8 == $key_hash) {
163 | if ($get_public_key) {
164 | return \MikeRow\BananoPHP\Util\Uint::fromUint4Array($key_uint4)->toHexString();
165 | } else {
166 | return true;
167 | }
168 | }
169 | }
170 | }
171 |
172 | return false;
173 | }
174 |
175 |
176 | // *
177 | // * Public key to account
178 | // *
179 |
180 | public static function public2account(string $public_key): string
181 | {
182 | if (strlen($public_key) != 64 || !hex2bin($public_key)) {
183 | throw new BananoToolException("Invalid public key: $public_key");
184 | }
185 |
186 | if (!extension_loaded('blake2')) {
187 | $key = \MikeRow\BananoPHP\Util\Uint::fromHex($public_key);
188 | $checksum;
189 | $hash = new SplFixedArray(64);
190 |
191 | $b2b = new Blake2b();
192 | $ctx = $b2b->init(null, 5);
193 | $b2b->update($ctx, $key->toUint8(), 32);
194 | $b2b->finish($ctx, $hash);
195 | $hash = \MikeRow\BananoPHP\Util\Uint::fromUint8Array(array_slice($hash->toArray(), 0, 5))->reverse();
196 | $checksum = $hash->toString();
197 | } else {
198 | $key = \MikeRow\BananoPHP\Util\Uint::fromHex($public_key)->toUint8();
199 | $key = \MikeRow\BananoPHP\Util\Bin::arr2bin((array) $key);
200 |
201 | $hash = blake2($key, 5, null, true);
202 | $hash = \MikeRow\BananoPHP\Util\Bin::bin2arr(strrev($hash));
203 | $checksum = \MikeRow\BananoPHP\Util\Uint::fromUint8Array($hash)->toString();
204 | }
205 |
206 | $c_account = \MikeRow\BananoPHP\Util\Uint::fromHex('0' . $public_key)->toString();
207 |
208 | return 'ban_' . $c_account . $checksum;
209 | }
210 |
211 |
212 | // *
213 | // * Private key to public key
214 | // *
215 |
216 | public static function private2public(string $private_key): string
217 | {
218 | if (strlen($private_key) != 64 || !hex2bin($private_key)) {
219 | throw new BananoToolException("Invalid private key: $private_key");
220 | }
221 |
222 | $salt = NanoSalt::instance();
223 | $private_key = \MikeRow\BananoPHP\Util\Uint::fromHex($private_key)->toUint8();
224 | $public_key = $salt::crypto_sign_public_from_secret_key($private_key);
225 |
226 | return \MikeRow\BananoPHP\Util\Uint::fromUint8Array($public_key)->toHexString();
227 | }
228 |
229 |
230 | // *
231 | // * String to burn account
232 | // *
233 |
234 | public static function string2burn(string $string, string $leading_char = '1', string $filling_char = '1'): string
235 | {
236 | if (!preg_match('/^[13456789abcdefghijkmnopqrstuwxyz]+$/', $string) || strlen($string) < 1 || strlen($string) > 51) {
237 | throw new BananoToolException("Invalid string: $string");
238 | }
239 | if ($leading_char != '1' && $leading_char != '3') {
240 | throw new BananoToolException("Invalid leading character: $leading_char");
241 | }
242 | if (!preg_match('/^[13456789abcdefghijkmnopqrstuwxyz]+$/', $filling_char) || strlen($filling_char != 1)) {
243 | throw new BananoToolException("Invalid filling character: $filling_char");
244 | }
245 |
246 | $string = $leading_char . $string . str_repeat($filling_char, (51 - strlen($string)));
247 |
248 | $aux = \MikeRow\BananoPHP\Util\Uint::fromString($string)->toUint4()->toArray();
249 | array_shift($aux);
250 | $key_uint4 = $aux;
251 | $key_uint8 = \MikeRow\BananoPHP\Util\Uint::fromUint4Array($key_uint4)->toUint8();
252 |
253 | if (!extension_loaded('blake2')) {
254 | $checksum;
255 | $hash = new SplFixedArray(64);
256 |
257 | $b2b = new Blake2b();
258 | $ctx = $b2b->init(null, 5);
259 | $b2b->update($ctx, $key_uint8, 32);
260 | $b2b->finish($ctx, $hash);
261 | $hash = \MikeRow\BananoPHP\Util\Uint::fromUint8Array(array_slice($hash->toArray(), 0, 5))->reverse();
262 | $checksum = $hash->toString();
263 | } else {
264 | $key = \MikeRow\BananoPHP\Util\Bin::arr2bin((array) $key_uint8);
265 |
266 | $hash = blake2($key, 5, null, true);
267 | $hash = \MikeRow\BananoPHP\Util\Bin::bin2arr(strrev($hash));
268 | $checksum = \MikeRow\BananoPHP\Util\Uint::fromUint8Array($hash)->toString();
269 | }
270 |
271 | return 'ban_' . $string . $checksum;
272 | }
273 |
274 |
275 | // *
276 | // * Get random keypair
277 | // *
278 |
279 | public static function keys(bool $get_account = false): array
280 | {
281 | $salt = NanoSalt::instance();
282 | $keys = $salt->crypto_sign_keypair();
283 |
284 | $keys[0] = \MikeRow\BananoPHP\Util\Uint::fromUint8Array(array_slice($keys[0]->toArray(), 0, 32))->toHexString();
285 | $keys[1] = \MikeRow\BananoPHP\Util\Uint::fromUint8Array($keys[1])->toHexString();
286 |
287 | if ($get_account) {
288 | $keys[] = self::public2account($keys[1]);
289 | }
290 |
291 | return $keys;
292 | }
293 |
294 |
295 | // *
296 | // * Seed to keypair (Blake2b)
297 | // *
298 |
299 | public static function seed2keys(string $seed, int $index = 0, bool $get_account = false): array
300 | {
301 | if (strlen($seed) != 64 || !hex2bin($seed)) {
302 | throw new BananoToolException("Invalid seed: $seed");
303 | }
304 | if ($index < 0 || $index > 4294967295) {
305 | throw new BananoToolException("Invalid index: $index");
306 | }
307 |
308 | $seed = \MikeRow\BananoPHP\Util\Uint::fromHex($seed)->toUint8();
309 | $index = \MikeRow\BananoPHP\Util\Uint::fromDec($index)->toUint8()->toArray();
310 |
311 | if (count($index) < 4) {
312 | $missing_bytes = [];
313 | for ($i = 0; $i < (4 - count($index)); $i++) {
314 | $missing_bytes[] = 0;
315 | }
316 | $index = array_merge($missing_bytes, $index);
317 | }
318 |
319 | $index = \MikeRow\BananoPHP\Util\Uint::fromUint8Array($index)->toUint8();
320 | $private_key = new SplFixedArray(64);
321 |
322 | $b2b = new Blake2b();
323 | $ctx = $b2b->init(null, 32);
324 | $b2b->update($ctx, $seed, 32);
325 | $b2b->update($ctx, $index, 4);
326 | $b2b->finish($ctx, $private_key);
327 |
328 | $private_key = \MikeRow\BananoPHP\Util\Uint::fromUint8Array(array_slice($private_key->toArray(), 0, 32))->toHexString();
329 | $public_key = self::private2public($private_key);
330 |
331 | $keys = [$private_key,$public_key];
332 |
333 | if ($get_account) {
334 | $keys[] = self::public2account($public_key);
335 | }
336 |
337 | return $keys;
338 | }
339 |
340 |
341 | // *
342 | // * Mnemonic seed to hexadecimal string (BIP39)
343 | // *
344 |
345 | public static function mnem2hex(array $words): string
346 | {
347 | $mnem_count = count($words);
348 |
349 | if ($mnem_count != 12 &&
350 | $mnem_count != 15 &&
351 | $mnem_count != 18 &&
352 | $mnem_count != 21 &&
353 | $mnem_count != 24
354 | ) {
355 | throw new BananoToolException("Invalid words array count: not 12,15,18,21,24");
356 | }
357 |
358 | $bip39 = new BIP39\BIP39EnglishWordList();
359 | $bip39_words = $bip39->getWords();
360 | $bits = [];
361 | $hex = [];
362 |
363 | foreach ($words as $index => $value) {
364 | $word = array_search($value, $bip39_words);
365 | if ($word === false) {
366 | throw new BananoToolException("Invalid menmonic word: $value");
367 | }
368 |
369 | $words[$index] = decbin($word);
370 | $words[$index] = str_split(str_repeat('0', (11 - strlen($words[$index]))) . $words[$index]);
371 |
372 | foreach ($words[$index] as $bit) {
373 | $bits[] = $bit;
374 | }
375 | }
376 |
377 | for ($i = 0; $i < ceil($mnem_count*2.66666); $i++) {
378 | $hex[] = bindec(implode('', array_slice($bits, $i * 8, 8)));
379 | }
380 |
381 | $hex = \MikeRow\BananoPHP\Util\Uint::fromUint8Array($hex)->toHexString();
382 | $hex = substr($hex, 0, ceil($mnem_count*2.66666));
383 |
384 | return $hex;
385 | }
386 |
387 |
388 | // *
389 | // * Hexadecimal string to mnemonic words (BIP39)
390 | // *
391 |
392 | public static function hex2mnem(string $hex): array
393 | {
394 | $hex_lenght = strlen($hex);
395 |
396 | if (($hex_lenght != 32 &&
397 | $hex_lenght != 40 &&
398 | $hex_lenght != 48 &&
399 | $hex_lenght != 56 &&
400 | $hex_lenght != 64) ||
401 | !hex2bin($hex)
402 | ) {
403 | throw new BananoToolException("Invalid hexadecimal string: $hex");
404 | }
405 |
406 | $bip39 = new BIP39\BIP39EnglishWordList();
407 | $bip39_words = $bip39->getWords();
408 | $bits = [];
409 | $mnemonic = [];
410 |
411 | $hex = \MikeRow\BananoPHP\Util\Uint::fromHex($hex)->toUint8();
412 | $check = hash('sha256', \MikeRow\BananoPHP\Util\Bin::arr2bin((array) $hex), true);
413 | $hex = array_merge((array) $hex, \MikeRow\BananoPHP\Util\Bin::bin2arr(substr($check, 0, 1)));
414 |
415 | foreach ($hex as $byte) {
416 | $bits_raw = decbin($byte);
417 | $bits = array_merge($bits, str_split(str_repeat('0', (8 - strlen($bits_raw))) . $bits_raw));
418 | }
419 |
420 | for ($i = 0; $i < floor($hex_lenght/2.66666); $i++) {
421 | $mnemonic[] = $bip39_words[bindec(implode('', array_slice($bits, $i * 11, 11)))];
422 | }
423 |
424 | return $mnemonic;
425 | }
426 |
427 |
428 | // *
429 | // * Mnemonic words to master seed (BIP39/44)
430 | // *
431 |
432 | public static function mnem2mseed(array $words, string $passphrase = ''): string
433 | {
434 | if (count($words) < 1) {
435 | throw new BananoToolException("Invalid words array count: less than 1");
436 | }
437 |
438 | $bip39 = new BIP39\BIP39EnglishWordList();
439 | $bip39_words = $bip39->getWords();
440 |
441 | foreach ($words as $index => $value) {
442 | $word = array_search($value, $bip39_words);
443 | if ($word === false) {
444 | throw new BananoToolException("Invalid menmonic word: $value");
445 | }
446 | }
447 |
448 | return strtoupper(
449 | hash_pbkdf2('sha512', implode(' ', $words), 'mnemonic' . $passphrase, 2048, 128)
450 | );
451 | }
452 |
453 |
454 | // *
455 | // * Master seed to keypair (BIP39/44)
456 | // *
457 |
458 | public static function mseed2keys(string $mseed, int $index = 0, bool $get_account = false): array
459 | {
460 | if (strlen($mseed) != 128 || !hex2bin($mseed)) {
461 | throw new BananoToolException("Invalid master seed: $mseed");
462 | }
463 | if ($index < 0 || $index > 4294967295) {
464 | throw new BananoToolException("Invalid index: $index");
465 | }
466 |
467 | $path = ["44","198","$index"];
468 |
469 | $I = hash_hmac('sha512', hex2bin($mseed), 'ed25519 seed', true);
470 | $HDKey = [substr($I, 0, 32),substr($I, 32, 32)];
471 |
472 | foreach ($path as $entry) {
473 | $entry = intval($entry);
474 | if ($entry >= self::HARDENED) {
475 | $entry = $entry - self::HARDENED;
476 | }
477 |
478 | $data = chr(0x00) . $HDKey[0] . hex2bin(dechex(self::HARDENED + (int) $entry));
479 | $I = hash_hmac('sha512', $data, $HDKey[1], true);
480 | $HDKey = [substr($I, 0, 32),substr($I, 32, 32)];
481 | }
482 |
483 | $private_key = strtoupper(bin2hex($HDKey[0]));
484 | $keys = [$private_key,self::private2public($private_key)];
485 |
486 | if ($get_account) {
487 | $keys[] = self::public2account($keys[1]);
488 | }
489 |
490 | return $keys;
491 | }
492 |
493 |
494 | // *
495 | // * Hash array of hexadecimals
496 | // *
497 |
498 | public static function hashHexs(array $hexs, int $size = 32): string
499 | {
500 | if (count($hexs) < 1) {
501 | throw new BananoToolException("Invalid hexadecimals array count: less than 1");
502 | }
503 | if ($size < 1) {
504 | throw new BananoToolException("Invalid size: $size");
505 | }
506 |
507 | $b2b = new Blake2b();
508 |
509 | $ctx = $b2b->init(null, $size);
510 | $hash = new SplFixedArray(64);
511 |
512 | foreach ($hexs as $index => $value) {
513 | if (!hex2bin($value)) {
514 | throw new BananoToolException("Invalid hexadecimal string: $value");
515 | }
516 |
517 | $value = \MikeRow\BananoPHP\Util\Uint::fromHex($value)->toUint8();
518 | $b2b->update($ctx, $value, count($value));
519 | }
520 |
521 | $b2b->finish($ctx, $hash);
522 | $hash = $hash->toArray();
523 | $hash = array_slice($hash, 0, $size);
524 | $hash = \MikeRow\BananoPHP\Util\Uint::fromUint8Array($hash)->toHexString();
525 |
526 | return $hash;
527 | }
528 |
529 |
530 | // *
531 | // * Sign message
532 | // *
533 |
534 | public static function sign(string $msg, string $private_key): string
535 | {
536 | if (!hex2bin($msg)) {
537 | throw new BananoToolException("Invalid message: $msg");
538 | }
539 | if (strlen($private_key) != 64 || !hex2bin($private_key)) {
540 | throw new BananoToolException("Invalid private key: $private_key");
541 | }
542 |
543 | $salt = NanoSalt::instance();
544 | $private_key = FieldElement::fromArray(\MikeRow\BananoPHP\Util\Uint::fromHex($private_key)->toUint8());
545 | $public_key = NanoSalt::crypto_sign_public_from_secret_key($private_key);
546 |
547 | $private_key->setSize(64);
548 | $private_key->copy($public_key, 32, 32);
549 |
550 | $msg = \MikeRow\BananoPHP\Util\Uint::fromHex($msg)->toUint8();
551 | $sm = $salt->crypto_sign($msg, count($msg), $private_key);
552 |
553 | $signature = [];
554 | for ($i = 0; $i < 64; $i++) {
555 | $signature[$i] = $sm[$i];
556 | }
557 |
558 | return \MikeRow\BananoPHP\Util\Uint::fromUint8Array($signature)->toHexString();
559 | }
560 |
561 |
562 | // *
563 | // * Validate signature
564 | // *
565 |
566 | public static function validSign(string $msg, string $sig, string $account)
567 | {
568 | if (!hex2bin($msg)) {
569 | throw new BananoToolException("Invalid message: $msg");
570 | }
571 | if (strlen($sig) != 128 || !hex2bin($sig)) {
572 | throw new BananoToolException("Invalid signature: $sig");
573 | }
574 | $public_key = self::account2public($account);
575 | if (!$public_key) {
576 | throw new BananoToolException("Invalid account: $account");
577 | }
578 |
579 | $sig = \MikeRow\BananoPHP\Util\Uint::fromHex($sig)->toUint8();
580 | $msg = \MikeRow\BananoPHP\Util\Uint::fromHex($msg)->toUint8();
581 | $public_key = \MikeRow\BananoPHP\Util\Uint::fromHex($public_key)->toUint8();
582 |
583 | $sm = new SplFixedArray(64 + count($msg));
584 | $m = new SplFixedArray(64 + count($msg));
585 |
586 | for ($i = 0; $i < 64; $i++) {
587 | $sm[$i] = $sig[$i];
588 | }
589 | for ($i = 0; $i < count($msg); $i++) {
590 | $sm[$i+64] = $msg[$i];
591 | }
592 |
593 | $open2 = NanoSalt::crypto_sign_open2($m, $sm, count($sm), $public_key);
594 |
595 | if ($open2 == null) {
596 | return false;
597 | }
598 |
599 | $open2 = \MikeRow\BananoPHP\Util\Uint::fromUint8Array($open2)->toHexString();
600 |
601 | return $open2;
602 | }
603 |
604 |
605 | // *
606 | // * Multiplier to difficulty
607 | // *
608 |
609 | public static function mult2diff(string $difficulty, float $multiplier): string
610 | {
611 | if (strlen($difficulty) != 16 || !hex2bin($difficulty)) {
612 | throw new BananoToolException("Invalid difficulty: $difficulty");
613 | }
614 | if ($multiplier <= 0) {
615 | throw new BananoToolException("Invalid multiplier: $multiplier");
616 | }
617 |
618 | $ref = (float) 18446744073709551616;
619 | $difficulty = hexdec($difficulty);
620 |
621 | return dechex(($difficulty - $ref) / $multiplier + $ref);
622 | }
623 |
624 |
625 | // *
626 | // * Difficulty to muliplier
627 | // *
628 |
629 | public static function diff2mult(string $base_difficulty, string $difficulty): float
630 | {
631 | if (strlen($base_difficulty) != 16 || !hex2bin($base_difficulty)) {
632 | throw new BananoToolException("Invalid base difficulty: $base_difficulty");
633 | }
634 | if (strlen($difficulty) != 16 || !hex2bin($difficulty)) {
635 | throw new BananoToolException("Invalid difficulty: $difficulty");
636 | }
637 |
638 | $ref = (float) 18446744073709551616;
639 | $base_difficulty = hexdec($base_difficulty);
640 | $difficulty = hexdec($difficulty);
641 |
642 | return (float) ($ref - $base_difficulty) / (float) ($ref - $difficulty);
643 | }
644 |
645 |
646 | // *
647 | // * Generate work
648 | // *
649 |
650 | public static function work(string $hash, string $difficulty): string
651 | {
652 | if (strlen($hash) != 64 || !hex2bin($hash)) {
653 | throw new BananoToolException("Invalid hash: $hash");
654 | }
655 | if (strlen($difficulty) != 16 || !hex2bin($difficulty)) {
656 | throw new BananoToolException("Invalid difficulty: $difficulty");
657 | }
658 |
659 | $hash = \MikeRow\BananoPHP\Util\Uint::fromHex($hash)->toUint8();
660 | $difficulty = hex2bin($difficulty);
661 |
662 | if (!extension_loaded('blake2')) {
663 | $b2b = new Blake2b();
664 | $rng = random_bytes(8);
665 | $rng = \MikeRow\BananoPHP\Util\bin2arr($rng);
666 |
667 | while (true) {
668 | $output = new SplFixedArray(64);
669 |
670 | $ctx = $b2b->init(null, 8);
671 | $b2b->update($ctx, $rng, 8);
672 | $b2b->update($ctx, $hash, 32);
673 | $b2b->finish($ctx, $output);
674 |
675 | $output = $output->toArray();
676 | $output = array_slice($output, 0, 8);
677 | $output = array_reverse($output);
678 | //$output = \MikeRow\BananoPHP\Util\Uint::fromUint8Array($output)->toHexString();
679 |
680 | if (strcasecmp(\MikeRow\BananoPHP\Util\Bin::arr2bin($output), $difficulty) >= 0) {
681 | return \MikeRow\BananoPHP\Util\Uint::fromUint8Array(array_reverse($rng))->toHexString();
682 | }
683 |
684 | $rng = $output;
685 | }
686 | } else {
687 | $hash = \MikeRow\BananoPHP\Util\Bin::arr2bin((array) $hash);
688 | $rng = random_bytes(8);
689 |
690 | while (true) {
691 | $output = strrev(blake2($rng . $hash, 8, null, true));
692 |
693 | if (strcasecmp($output, $difficulty) >= 0) {
694 | return \MikeRow\BananoPHP\Util\Uint::fromUint8Array(array_reverse(\MikeRow\BananoPHP\Util\bin2arr($rng)))->toHexString();
695 | }
696 |
697 | $rng = $output;
698 | }
699 | }
700 | }
701 |
702 |
703 | // *
704 | // * Validate work
705 | // *
706 |
707 | public static function validWork(string $hash, string $difficulty, string $work): bool
708 | {
709 | if (strlen($hash) != 64 || !hex2bin($hash)) {
710 | throw new BananoToolException("Invalid hash: $hash");
711 | }
712 | if (strlen($difficulty) != 16 || !hex2bin($difficulty)) {
713 | throw new BananoToolException("Invalid difficulty: $difficulty");
714 | }
715 | if (strlen($work) != 16 || !hex2bin($work)) {
716 | throw new BananoToolException("Invalid work: $work");
717 | }
718 |
719 | $hash = \MikeRow\BananoPHP\Util\Uint::fromHex($hash)->toUint8();
720 | $work = \MikeRow\BananoPHP\Util\Uint::fromHex($work)->toUint8();
721 | $work = array_reverse($work->toArray());
722 | $work = SplFixedArray::fromArray($work);
723 |
724 | $res = new SplFixedArray(64);
725 |
726 | $blake2b = new Blake2b();
727 | $ctx = $blake2b->init(null, 8);
728 | $blake2b->update($ctx, $work, 8);
729 | $blake2b->update($ctx, $hash, 32);
730 | $blake2b->finish($ctx, $res);
731 |
732 | $res = $res->toArray();
733 | $res = array_slice($res, 0, 8);
734 | $res = array_reverse($res);
735 | $res = \MikeRow\BananoPHP\Util\Uint::fromUint8Array($res)->toHexString();
736 |
737 | if (hexdec($res) >= hexdec($difficulty)) {
738 | return true;
739 | }
740 |
741 | return false;
742 | }
743 | }
744 |
--------------------------------------------------------------------------------
/src/BananoWS.php:
--------------------------------------------------------------------------------
1 | options = [];
48 |
49 | // Timeout
50 | if (isset($options['timeout'])) {
51 | $this->options['timeout'] = (float) $options['timeout'];
52 | }
53 |
54 | // Fragment size
55 | if (isset($options['fragment_size'])) {
56 | $this->options['fragment_size'] = (int) $options['fragment_size'];
57 | }
58 |
59 | // Context
60 | if (isset($options['context']) && is_array($options['context'])) {
61 | $this->options['context'] = stream_context_create($options['context']);
62 | }
63 |
64 | // Headers
65 | if (isset($options['headers']) && is_array($options['headers'])) {
66 | $this->options['headers'] = $options['headers'];
67 | }
68 |
69 | $this->protocol = $protocol;
70 | $this->hostname = $hostname;
71 | $this->port = $port;
72 | $this->url = $url;
73 | }
74 |
75 |
76 | // *
77 | // * Open connection
78 | // *
79 |
80 | public function open(): bool
81 | {
82 | try {
83 | $this->websocket = new \WebSocket\Client("{$this->protocol}://{$this->hostname}:{$this->port}/{$this->url}", $this->options);
84 | return true;
85 | } catch (\WebSocket\ConnectionException $e) {
86 | return false;
87 | }
88 | }
89 |
90 |
91 | // *
92 | // * Close connection
93 | // *
94 |
95 | public function close()
96 | {
97 | if ($this->websocket != null) {
98 | $this->websocket->close();
99 | $this->weboscket = null;
100 | }
101 | }
102 |
103 |
104 | // *
105 | // * Subscribe to topic
106 | // *
107 |
108 | public function subscribe(string $topic, array $options = null, bool $ack = false): int
109 | {
110 | // Check WebSocket connection
111 | if ($this->websocket == null) {
112 | throw new BananoWSException("WebSocket connection is not opened");
113 | }
114 |
115 | // Check inputs
116 | if (empty($topic)){
117 | throw new BananoWSException("Invalid topic: $topic");
118 | }
119 |
120 | $this->id++;
121 |
122 | $subscribe = [
123 | 'action' => 'subscribe',
124 | 'topic' => $topic,
125 | 'id' => $this->id
126 | ];
127 |
128 | if (!empty($options)) {
129 | $subscribe['options'] = $options;
130 | }
131 |
132 | if ($ack) {
133 | $subscribe['ack'] = true;
134 | }
135 |
136 | $subscribe = json_encode($subscribe);
137 | $this->websocket->send($subscribe);
138 |
139 | return $this->id;
140 | }
141 |
142 |
143 | // *
144 | // * Update subscription
145 | // *
146 |
147 | public function update(string $topic, array $options, bool $ack = false): int
148 | {
149 | // Check WebSocket connection
150 | if ($this->websocket == null) {
151 | throw new BananoWSException("WebSocket connection is not opened");
152 | }
153 |
154 | // Check inputs
155 | if (empty($topic)){
156 | throw new BananoWSException("Invalid topic: $topic");
157 | }
158 |
159 | $this->id++;
160 |
161 | $update = [
162 | 'action' => 'update',
163 | 'topic' => $topic,
164 | 'id' => $this->id,
165 | 'options' => $options
166 | ];
167 |
168 | if ($ack) {
169 | $update['ack'] = true;
170 | }
171 |
172 | $update = json_encode($update);
173 | $this->websocket->send($update);
174 |
175 | return $this->id;
176 | }
177 |
178 |
179 | // *
180 | // * Unsubscribe to topic
181 | // *
182 |
183 | public function unsubscribe(string $topic, bool $ack = false): int
184 | {
185 | // Check WebSocket connection
186 | if ($this->websocket == null) {
187 | throw new BananoWSException("WebSocket connection is not opened");
188 | }
189 |
190 | // Check inputs
191 | if (empty($topic)){
192 | throw new BananoWSException("Invalid topic: $topic");
193 | }
194 |
195 | $this->id++;
196 |
197 | $unsubscribe = [
198 | 'action' => 'unsubscribe',
199 | 'topic' => $topic,
200 | 'id' => $this->id
201 | ];
202 |
203 | if ($ack) {
204 | $unsubscribe['ack'] = true;
205 | }
206 |
207 | $unsubscribe = json_encode($unsubscribe);
208 | $this->websocket->send($unsubscribe);
209 |
210 | return $this->id;
211 | }
212 |
213 |
214 | // *
215 | // * Keepalive
216 | // *
217 |
218 | public function keepalive(): int
219 | {
220 | // Check WebSocket connection
221 | if ($this->websocket == null) {
222 | throw new BananoWSException("WebSocket connection is not opened");
223 | }
224 |
225 | $this->id++;
226 |
227 | $keepalive = [
228 | 'action' => 'ping',
229 | 'id' => $this->id
230 | ];
231 |
232 | $keepalive = json_encode($keepalive);
233 | $this->websocket->send($keepalive);
234 |
235 | return $this->id;
236 | }
237 |
238 |
239 | // *
240 | // * Listen
241 | // *
242 |
243 | public function listen()
244 | {
245 | // Check WebSocket connection
246 | if ($this->websocket == null) {
247 | throw new BananoWSException("WebSocket connection is not opened");
248 | }
249 |
250 | while (true) {
251 | try {
252 | return json_decode($this->websocket->receive(), true);
253 | } catch (\WebSocket\ConnectionException $e) {
254 | //
255 | }
256 | }
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/src/Util/Base.php:
--------------------------------------------------------------------------------
1 | http://www.danvk.org/hex2dec.html
4 |
5 | /**
6 | * A function for converting hex <-> dec w/o loss of precision.
7 | *
8 | * The problem is that parseInt("0x12345...") isn't precise enough to convert
9 | * 64-bit integers correctly.
10 | *
11 | * Internally, this uses arrays to encode decimal digits starting with the least
12 | * significant:
13 | * 8 = [8]
14 | * 16 = [6, 1]
15 | * 1024 = [4, 2, 0, 1]
16 | */
17 |
18 | namespace MikeRow\BananoPHP\Util;
19 |
20 | class Base
21 | {
22 | public static function add($x, $y, $base)
23 | {
24 | $z = [];
25 | $n = max(count($x), count($y));
26 | $carry = 0;
27 | $i = 0;
28 | while ($i < $n || $carry)
29 | {
30 | $xi = $i < count($x) ? $x[$i] : 0;
31 | $yi = $i < count($y) ? $y[$i] : 0;
32 | $zi = $carry + $xi + $yi;
33 | $z[] = $zi % $base;
34 | $carry = floor($zi / $base);
35 | $i++;
36 | }
37 | return $z;
38 | }
39 |
40 | public static function multiplyByNumber($num, $x, $base) {
41 | if ($num < 0) return null;
42 | if ($num == 0) return [];
43 |
44 | $result = [];
45 | $power = $x;
46 | while (true)
47 | {
48 | if ($num & 1)
49 | {
50 | $result = self::add($result, $power, $base);
51 | }
52 | $num = $num >> 1;
53 | if ($num === 0)
54 | break;
55 | $power = self::add($power, $power, $base);
56 | }
57 |
58 | return $result;
59 | }
60 |
61 | public static function parseToDigitsArray($str, $base) {
62 | $digits = str_split($str);
63 | $ary = [];
64 | for ($i = count($digits) - 1; $i >= 0; $i--)
65 | {
66 | $n = intval($digits[$i], $base);
67 | if (!is_numeric($n)) return null;
68 | $ary[] = $n;
69 | }
70 | return $ary;
71 | }
72 |
73 | public static function convertBase($str, $fromBase, $toBase) {
74 | $digits = self::parseToDigitsArray($str, $fromBase);
75 | if ($digits === null) return null;
76 |
77 | $outArray = [];
78 | $power = [1];
79 | for ($i = 0; $i < count($digits); $i++) {
80 | // invariant: at this point, fromBase^i = power
81 | if ($digits[$i])
82 | {
83 | $outArray = self::add($outArray, self::multiplyByNumber($digits[$i], $power, $toBase), $toBase);
84 | }
85 | $power = self::multiplyByNumber($fromBase, $power, $toBase);
86 | }
87 |
88 | $out = '';
89 | for ($i = count($outArray) - 1; $i >= 0; $i--) {
90 | $out .= base_convert($outArray[$i], $fromBase, $toBase);
91 | }
92 | return $out;
93 | }
94 |
95 | public static function decToHex($decStr) {
96 | $hex = self::convertBase($decStr, 10, 16);
97 | if(strlen($hex) % 2 != 0)
98 | $hex = '0' . $hex;
99 | return $hex ? $hex : null;
100 | }
101 |
102 | public static function hexToDec($hexStr) {
103 | if (substr($hexStr, 0, 2) === '0x') $hexStr = substr($hexStr, 2);
104 | $hexStr = strtolower($hexStr);
105 | return self::convertBase($hexStr, 16, 10);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Util/Bin.php:
--------------------------------------------------------------------------------
1 | 255) {
14 | return false;
15 | }
16 | }
17 |
18 | return implode(array_map('chr', $array));
19 | }
20 |
21 | public static function bin2arr(string $string): array
22 | {
23 | return array_map('ord', str_split($string));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Util/Uint.php:
--------------------------------------------------------------------------------
1 | u8 = $this->u4 = $this->u5 = $this->hex = $this->string = false;
23 | }
24 |
25 | public static function fromHex($hex)
26 | {
27 | $ret = new Uint();
28 | $ret->hex = strtoupper($hex);
29 | $ret->u8 = $ret->hexU8($hex);
30 | $ret->u4 = $ret->hexU4($hex);
31 | return $ret;
32 | }
33 |
34 | public static function fromUint8Array($u8)
35 | {
36 | $ret = new Uint();
37 | $ret->u8 = $u8;
38 | $ret->hex = $ret->u8Hex($u8);
39 | return $ret;
40 | }
41 |
42 | public static function fromUint4Array($u4)
43 | {
44 | $ret = new Uint();
45 | $ret->u4 = $u4;
46 | $ret->hex = $ret->u4Hex($u4);
47 | $ret->u8 = $ret->u4U8($u4);
48 | return $ret;
49 | }
50 |
51 | public static function fromString($str)
52 | {
53 | $ret = new Uint();
54 | $ret->u5 = $ret->stringU5($str);
55 | $ret->string = $str;
56 | $ret->u4 = $ret->u5U4($ret->u5);
57 | $ret->u8 = $ret->u4U8($ret->u4);
58 | return $ret;
59 | }
60 |
61 | public static function fromDec($dec)
62 | {
63 | $ret = new Uint();
64 | $ret->dec = $dec;
65 | $ret->hex = Base::decToHex($dec);
66 | $ret->u8 = $ret->hexU8($ret->hex);
67 | return $ret;
68 | }
69 |
70 | public function toHexString()
71 | {
72 | if($this->hex)
73 | return $this->hex;
74 | else
75 | return $this->u8Hex($this->u8);
76 | }
77 |
78 | public function toString()
79 | {
80 | if($this->string)
81 | return $this->string;
82 | if($this->u5)
83 | return $this->u5String($this->u5);
84 | if($this->u4)
85 | return $this->u5String($this->u4U5($this->u4));
86 | if($this->u8)
87 | return $this->u5String($this->u4U5($this->u8U4($this->u8)));
88 | return false;
89 | }
90 |
91 | public function toUint8()
92 | {
93 | return $this->u8;
94 | }
95 |
96 | public function toUint4()
97 | {
98 | if($this->u4)
99 | return $this->u4;
100 | if($this->u8)
101 | return $this->u8U4($this->u8);
102 | return false;
103 | }
104 |
105 | public function hexU8($hex)
106 | {
107 | if(strlen($hex) % 2 != 0)
108 | $hex = '0' . $hex;
109 | $arr = new SplFixedArray(strlen($hex) / 2);
110 | for($i = 0; $i < strlen($hex); $i+=2)
111 | {
112 | $arr[$i/2] = base_convert($hex[$i] . $hex[$i+1], 16, 10);
113 | }
114 | return $arr;
115 | }
116 |
117 | public function hexU4($hex)
118 | {
119 | $arr = new SplFixedArray(strlen($hex));
120 | for($i = 0; $i < strlen($hex); $i++)
121 | {
122 | $arr[$i] = base_convert($hex[$i], 16, 10);
123 | }
124 | return $arr;
125 | }
126 |
127 | public function u8Hex($bytes)
128 | {
129 | $hex = '';
130 | foreach($bytes as $byte)
131 | {
132 | $aux = base_convert($byte, 10, 16);
133 | if(strlen($aux) == 1)
134 | $aux = '0' . $aux;
135 | $hex .= $aux;
136 | }
137 | return strtoupper($hex);
138 | }
139 |
140 | public function u4Hex($bytes)
141 | {
142 | $hex = '';
143 | foreach($bytes as $byte)
144 | $hex .= base_convert($byte, 10, 16);
145 | return strtoupper($hex);
146 | }
147 |
148 | public function u8U4($u8)
149 | {
150 | $u4 = new SplFixedArray(count($u8) * 2);
151 | for($i = 0; $i < count($u8); $i++)
152 | {
153 | $u4[$i*2] = $u8[$i] / 16 | 0;
154 | $u4[$i*2 + 1] = $u8[$i] % 16;
155 | }
156 | return $u4;
157 | }
158 |
159 | public function u4U8($u4)
160 | {
161 | $u8 = new SplFixedArray(count($u4) / 2);
162 | for($i = 0; $i < count($u8); $i++)
163 | $u8[$i] = $u4[$i*2] * 16 + $u4[$i*2 + 1];
164 | return $u8;
165 | }
166 |
167 | public function u4U5($u4)
168 | {
169 | $u5 = new SplFixedArray(count($u4) / 5 * 4);
170 | for($i = 1; $i <= count($u5); $i++)
171 | {
172 | $n = $i - 1;
173 | $m = $i % 4;
174 | $z = $n + (($i - $m) / 4);
175 | $right = $u4[$z] << $m;
176 | if((count($u5) - $i) % 4 == 0)
177 | $left = $u4[$z - 1] << 4;
178 | else
179 | $left = $u4[$z + 1] >> (4 - $m);
180 | $u5[$n] = ($left + $right) % 32;
181 | }
182 | return $u5;
183 | }
184 |
185 | public function u5U4($u5)
186 | {
187 | $u4 = new SplFixedArray(count($u5) / 4 * 5);
188 | for($i = 1; $i <= count($u4); $i++)
189 | {
190 | $n = $i - 1;
191 | $m = $i % 5;
192 | $z = $n - (($i - $m) / 5);
193 | if($i > 1)
194 | $right = $u5[$z - 1] << (5 - $m);
195 | else $right = 0;
196 | $left = $u5[$z] >> $m;
197 | $u4[$n] = ($left + $right) % 16;
198 | }
199 | return $u4;
200 | }
201 |
202 | public function stringU5($str)
203 | {
204 | $letters = '13456789abcdefghijkmnopqrstuwxyz';
205 | $len = strlen($str);
206 | $arr = str_split($str);
207 | $u5 = new SplFixedArray($len);
208 | for($i = 0; $i < $len; $i++)
209 | $u5[$i] = strpos($letters, $arr[$i]);
210 | return $u5;
211 | }
212 |
213 | public function u5String($u5)
214 | {
215 | $letters = str_split('13456789abcdefghijkmnopqrstuwxyz');
216 | $str = "";
217 | for($i = 0; $i < count($u5); $i++)
218 | $str .= $letters[$u5[$i]];
219 | return $str;
220 | }
221 |
222 | public function dec2hex($str, $bytes = null)
223 | {
224 | $dec = str_split($hex);
225 | $sum = [];
226 | $hex = [];
227 | $i = $s = 0;
228 | while(count($dec))
229 | {
230 | $s = 1 * array_shift($dec);
231 | for($i = 0; $s || $i < count($sum); $i++)
232 | {
233 | $s += ($sum[$i] || 0) * 10;
234 | $sum[$i] = $s % 16;
235 | $s = ($s - $sum[$i]) / 16;
236 | }
237 | }
238 | while(count($sum))
239 | {
240 | $hex[] = base_convert(array_shift($sum), 10, 16);
241 | }
242 |
243 | $hex = implode('', $hex);
244 |
245 | if(strlen($hex) % 2 != 0)
246 | $hex = "0" . $hex;
247 |
248 | if($bytes > strlen($hex) / 2)
249 | {
250 | $diff = $bytes - strlen($hex) / 2;
251 | for($i = 0; $i < $diff; $i++)
252 | $hex = "00" . $hex;
253 | }
254 | return $hex;
255 | }
256 |
257 | public function reverse()
258 | {
259 | if($this->u8)
260 | {
261 | $len = count($this->u8);
262 | for($i = 0; $i != $len - 1 - $i; $i++)
263 | {
264 | $aux = $this->u8[$i];
265 | $this->u8[$i] = $this->u8[$len - 1 - $i];
266 | $this->u8[$len - 1 - $i] = $aux;
267 | }
268 | }
269 | return self::fromUint8Array($this->toUint8());
270 | }
271 |
272 | public static function expandSize($size, $uint)
273 | {
274 | if(get_class($uint) != 'Uint')
275 | return false;
276 | if(count($uint->toUint8()) < $size)
277 | {
278 | $u8 = $uint->toUint8();
279 | $new = new SplFixedArray($size);
280 | $diff = $size - count($u8);
281 | for($i = 0; $i < $size; $i++)
282 | {
283 | if($i < $diff)
284 | $new[$i] = 0;
285 | else
286 | $new[$i] = $u8[$i - $diff];
287 | }
288 | return self::fromUint8Array($new);
289 | }
290 | return $uint;
291 | }
292 | }
293 |
--------------------------------------------------------------------------------
/test/BananoBlock/change.php:
--------------------------------------------------------------------------------
1 | account_info(['account' => $account]);
22 | $block_info = $bananorpc->block_info([
23 | 'json_block' => true,
24 | 'hash' => $account_info['frontier']
25 | ]);
26 |
27 | // Generate work
28 | $work = BananoTool::work($account_info['frontier'], $change_difficulty);
29 |
30 | // Build block
31 | $bananoblock->setPrev($account_info['frontier'], $block_info['contents']);
32 | $bananoblock->setWork($work);
33 | $bananoblock->change($representative);
34 |
35 | // Publish block
36 | $process = $bananorpc->process([
37 | 'json_block' => 'true',
38 | 'block' => $bananoblock->block
39 | ]);
40 |
41 | // Results and debug
42 | if ($bananorpc->error) {
43 | echo $bananorpc->error . PHP_EOL;
44 | }
45 |
46 | var_dump($process);
47 |
--------------------------------------------------------------------------------
/test/BananoBlock/open.php:
--------------------------------------------------------------------------------
1 | setWork($work);
27 | $bananoblock->open($pairing_block_id, $received_amount, $representative);
28 |
29 | // Publish block
30 | $process = $bananorpc->process([
31 | 'json_block' => 'true',
32 | 'block' => $bananoblock->block
33 | ]);
34 |
35 | // Results and debug
36 | if ($bananorpc->error) {
37 | echo $bananorpc->error . PHP_EOL;
38 | }
39 |
40 | var_dump($process);
41 |
--------------------------------------------------------------------------------
/test/BananoBlock/receive.php:
--------------------------------------------------------------------------------
1 | account_info(['account' => $account]);
24 | $block_info = $bananorpc->block_info([
25 | 'json_block' => true,
26 | 'hash' => $account_info['frontier']
27 | ]);
28 |
29 | // Generate work
30 | $work = BananoTool::work($account_info['frontier'], $receive_difficulty);
31 |
32 | // Build block
33 | $bananoblock->setPrev($account_info['frontier'], $block_info['contents']);
34 | $bananoblock->setWork($work);
35 | $bananoblock->receive($pairing_block_id, $received_amount, $representative);
36 |
37 | // Publish block
38 | $process = $bananorpc->process([
39 | 'json_block' => 'true',
40 | 'block' => $bananoblock->block
41 | ]);
42 |
43 | // Results and debug
44 | if ($bananorpc->error) {
45 | echo $bananorpc->error . PHP_EOL;
46 | }
47 |
48 | var_dump($process);
49 |
--------------------------------------------------------------------------------
/test/BananoBlock/send.php:
--------------------------------------------------------------------------------
1 | account_info(['account' => $account]);
24 | $block_info = $bananorpc->block_info([
25 | 'json_block' => true,
26 | 'hash' => $account_info['frontier']
27 | ]);
28 |
29 | // Generate work
30 | $work = BananoTool::work($account_info['frontier'], $send_difficulty);
31 |
32 | // Build block
33 | $bananoblock->setPrev($account_info['frontier'], $block_info['contents']);
34 | $bananoblock->setWork($work);
35 | $bananoblock->send($destination, $sending_amount, $representative);
36 |
37 | // Publish block
38 | $process = $bananorpc->process([
39 | 'json_block' => 'true',
40 | 'block' => $bananoblock->block
41 | ]);
42 |
43 | // Results and debug
44 | if ($bananorpc->error) {
45 | echo $bananorpc->error . PHP_EOL;
46 | }
47 |
48 | var_dump($process);
49 |
--------------------------------------------------------------------------------
/test/BananoCLI.php:
--------------------------------------------------------------------------------
1 | account_key(['account' => $account]);
10 |
11 | var_dump($bananocli);
12 |
--------------------------------------------------------------------------------
/test/BananoIPC.php:
--------------------------------------------------------------------------------
1 | setBananoEncoding(1);
13 |
14 | $bananoipc_unix->open();
15 |
16 | $bananoipc_unix->account_weight(['account' => $account]);
17 |
18 | var_dump($bananoipc_unix);
19 |
20 | $bananoipc_unix->close();
21 |
22 |
23 | // * Unix domain socket encoding 2
24 |
25 | $bananoipc_unix = new MikeRow\BananoPHP\BananoIPC('unix', ['/tmp/nano']);
26 |
27 | $bananoipc_unix->setBananoEncoding(2);
28 |
29 | $bananoipc_unix->open();
30 |
31 | $bananoipc_unix->account_weight(['account' => $account]);
32 |
33 | var_dump($bananoipc_unix);
34 |
35 | $bananoipc_unix->close();
36 |
37 |
38 | // * Unix domain socket encoding 3
39 |
40 | $bananoipc_unix = new MikeRow\BananoPHP\BananoIPC('unix', ['/tmp/nano']);
41 |
42 | $bananoipc_unix->setBananoEncoding(3);
43 |
44 | $bananoipc_unix->open();
45 |
46 | $bananoipc_unix->AccountWeight(['Account' => $account]);
47 |
48 | var_dump($bananoipc_unix);
49 |
50 | $bananoipc_unix->close();
51 |
52 |
53 | // * Unix domain socket encoding 4
54 |
55 | $bananoipc_unix = new MikeRow\BananoPHP\BananoIPC('unix', ['/tmp/nano']);
56 |
57 | $bananoipc_unix->setBananoEncoding(4);
58 |
59 | $bananoipc_unix->open();
60 |
61 | $bananoipc_unix->AccountWeight(['account' => $account]);
62 |
63 | var_dump($bananoipc_unix);
64 |
65 | $bananoipc_unix->close();
66 |
67 |
68 | // * TCP encoding 1
69 |
70 | $bananoipc_tcp = new MikeRow\BananoPHP\BananoIPC('tcp', ['localhost', 7077]);
71 |
72 | $bananoipc_tcp->setBananoEncoding(1);
73 |
74 | $bananoipc_tcp->open();
75 |
76 | $bananoipc_tcp->account_weight(['account' => $account]);
77 |
78 | var_dump($bananoipc_tcp);
79 |
80 | $bananoipc_tcp->close();
81 |
82 |
83 | // * TCP encoding 2
84 |
85 | $bananoipc_tcp = new MikeRow\BananoPHP\BananoIPC('tcp', ['localhost', 7077]);
86 |
87 | $bananoipc_tcp->setBananoEncoding(2);
88 |
89 | $bananoipc_tcp->open();
90 |
91 | $bananoipc_tcp->account_weight(['account' => $account]);
92 |
93 | var_dump($bananoipc_tcp);
94 |
95 | $bananoipc_tcp->close();
96 |
97 |
98 | // * TCP encoding 3
99 |
100 | $bananoipc_tcp = new MikeRow\BananoPHP\BananoIPC('tcp', ['localhost', 7077]);
101 |
102 | $bananoipc_tcp->setBananoEncoding(3);
103 |
104 | $bananoipc_tcp->open();
105 |
106 | $bananoipc_tcp->AccountWeight(['Account' => $account]);
107 |
108 | var_dump($bananoipc_tcp);
109 |
110 | $bananoipc_tcp->close();
111 |
112 |
113 | // * TCP encoding 4
114 |
115 | $bananoipc_tcp = new MikeRow\BananoPHP\BananoIPC('tcp', ['localhost', 7077]);
116 |
117 | $bananoipc_tcp->setBananoEncoding(4);
118 |
119 | $bananoipc_tcp->open();
120 |
121 | $bananoipc_tcp->AccountWeight(['account' => $account]);
122 |
123 | var_dump($bananoipc_tcp);
124 |
125 | $bananoipc_tcp->close();
126 |
--------------------------------------------------------------------------------
/test/BananoIPCListen.php:
--------------------------------------------------------------------------------
1 | setBananoEncoding(3);
13 |
14 | $bananoipc_unix->setListen(true);
15 |
16 | $bananoipc_unix->open();
17 |
18 | $args = [
19 | 'ServiceName' => 'TopicConfirmation'
20 | ];
21 |
22 | $bananoipc_unix->ServiceRegister($args);
23 |
24 | $i = 0;
25 | while ($i<5) {
26 | print_r($bananoipc_unix->listen());
27 | $i++;
28 | }
29 |
30 | $bananoipc_unix->close();
31 |
--------------------------------------------------------------------------------
/test/BananoRPC.php:
--------------------------------------------------------------------------------
1 | setBananoApi(1);
13 |
14 | $bananorpc->account_weight(['account' => $account]);
15 |
16 | var_dump($bananorpc);
17 |
18 |
19 | // * API v2
20 |
21 | $bananorpc = new MikeRow\BananoPHP\BananoRPC('http', 'localhost', 7076, 'api/v2');
22 |
23 | $bananorpc->setBananoApi(2);
24 |
25 | $bananorpc->AccountWeight(['account' => $account]);
26 |
27 | var_dump($bananorpc);
28 |
--------------------------------------------------------------------------------
/test/BananoRPCExt.php:
--------------------------------------------------------------------------------
1 | account_weight(['account' => $account]);
10 |
11 | var_dump($bananorpcext);
12 |
--------------------------------------------------------------------------------
/test/BananoTool/account2public.php:
--------------------------------------------------------------------------------
1 | open();
8 |
9 | $bananows->subscribe('confirmation', null, true);
10 |
11 | $i = 0;
12 | while ($i<5) {
13 | print_r($bananows->listen());
14 | $i++;
15 | }
16 |
17 | $bananows->unsubscribe('confirmation');
18 |
19 | $bananows->close();
20 |
--------------------------------------------------------------------------------
/test/autoload.php:
--------------------------------------------------------------------------------
1 | setBananoEncoding(1);
15 |
16 | $bananoipc_unix->open();
17 |
18 | $t0 = microtime(true);
19 |
20 | for ($i = 0; $i < $cycles; $i++) {
21 | $bananoipc_unix->account_weight(['account' => $account]);
22 | }
23 |
24 | echo 'Time unix enc 1: ' . (microtime(true) - $t0) . PHP_EOL;
25 |
26 | $bananoipc_unix->close();
27 |
28 |
29 | // * Unix domain socket encoding 2
30 |
31 | $bananoipc_unix = new MikeRow\BananoPHP\BananoIPC('unix', ['/tmp/nano']);
32 |
33 | $bananoipc_unix->setBananoEncoding(2);
34 |
35 | $bananoipc_unix->open();
36 |
37 | $t0 = microtime(true);
38 |
39 | for ($i = 0; $i < $cycles; $i++) {
40 | $bananoipc_unix->account_weight(['account' => $account]);
41 | }
42 |
43 | echo 'Time unix enc 2: ' . (microtime(true) - $t0) . PHP_EOL;
44 |
45 | $bananoipc_unix->close();
46 |
47 |
48 | // * Unix domain socket encoding 3
49 |
50 | $bananoipc_unix = new MikeRow\BananoPHP\BananoIPC('unix', ['/tmp/nano']);
51 |
52 | $bananoipc_unix->setBananoEncoding(3);
53 |
54 | $bananoipc_unix->open();
55 |
56 | $t0 = microtime(true);
57 |
58 | for ($i = 0; $i < $cycles; $i++) {
59 | $bananoipc_unix->AccountWeight(['Account' => $account]);
60 | }
61 |
62 | echo 'Time unix enc 3: ' . (microtime(true) - $t0) . PHP_EOL;
63 |
64 | $bananoipc_unix->close();
65 |
66 |
67 | // * Unix domain socket encoding 4
68 |
69 | $bananoipc_unix = new MikeRow\BananoPHP\BananoIPC('unix', ['/tmp/nano']);
70 |
71 | $bananoipc_unix->setBananoEncoding(4);
72 |
73 | $bananoipc_unix->open();
74 |
75 | $t0 = microtime(true);
76 |
77 | for ($i = 0; $i < $cycles; $i++) {
78 | $bananoipc_unix->AccountWeight(['account' => $account]);
79 | }
80 |
81 | echo 'Time unix enc 4: ' . (microtime(true) - $t0) . PHP_EOL;
82 |
83 | $bananoipc_unix->close();
84 |
85 |
86 | // * TCP encoding 1
87 |
88 | $bananoipc_tcp = new MikeRow\BananoPHP\BananoIPC('tcp', ['localhost', 7077]);
89 |
90 | $bananoipc_tcp->setBananoEncoding(1);
91 |
92 | $bananoipc_tcp->open();
93 |
94 | $t0 = microtime(true);
95 |
96 | for ($i = 0; $i < $cycles; $i++) {
97 | $bananoipc_tcp->account_weight(['account' => $account]);
98 | }
99 |
100 | echo 'Time TCP enc 1: ' . (microtime(true) - $t0) . PHP_EOL;
101 |
102 | $bananoipc_tcp->close();
103 |
104 |
105 | // * TCP encoding 2
106 |
107 | $bananoipc_tcp = new MikeRow\BananoPHP\BananoIPC('tcp', ['localhost', 7077]);
108 |
109 | $bananoipc_tcp->setBananoEncoding(2);
110 |
111 | $bananoipc_tcp->open();
112 |
113 | $t0 = microtime(true);
114 |
115 | for ($i = 0; $i < $cycles; $i++) {
116 | $bananoipc_tcp->account_weight(['account' => $account]);
117 | }
118 |
119 | echo 'Time TCP enc 2: ' . (microtime(true) - $t0) . PHP_EOL;
120 |
121 | $bananoipc_tcp->close();
122 |
123 |
124 | // * TCP encoding 3
125 |
126 | $bananoipc_tcp = new MikeRow\BananoPHP\BananoIPC('tcp', ['localhost', 7077]);
127 |
128 | $bananoipc_tcp->setBananoEncoding(3);
129 |
130 | $bananoipc_tcp->open();
131 |
132 | $t0 = microtime(true);
133 |
134 | for ($i = 0; $i < $cycles; $i++) {
135 | $bananoipc_tcp->AccountWeight(['Account' => $account]);
136 | }
137 |
138 | echo 'Time TCP enc 3: ' . (microtime(true) - $t0) . PHP_EOL;
139 |
140 | $bananoipc_tcp->close();
141 |
142 |
143 | // * TCP encoding 4
144 |
145 | $bananoipc_tcp = new MikeRow\BananoPHP\BananoIPC('tcp', ['localhost', 7077]);
146 |
147 | $bananoipc_tcp->setBananoEncoding(4);
148 |
149 | $bananoipc_tcp->open();
150 |
151 | $t0 = microtime(true);
152 |
153 | for ($i = 0; $i < $cycles; $i++) {
154 | $bananoipc_tcp->AccountWeight(['account' => $account]);
155 | }
156 |
157 | echo 'Time TCP enc 4: ' . (microtime(true) - $t0) . PHP_EOL;
158 |
159 | $bananoipc_tcp->close();
160 |
--------------------------------------------------------------------------------
/test/speed/BananoRPC.php:
--------------------------------------------------------------------------------
1 | setBananoApi(1);
15 |
16 | $t0 = microtime(true);
17 |
18 | for ($i = 0; $i < $cycles; $i++) {
19 | $bananorpc->account_weight(['account' => $account]);
20 | }
21 |
22 | echo 'Time v1: ' . (microtime(true) - $t0) . PHP_EOL;
23 |
24 |
25 | // * API v2
26 |
27 | $bananorpc = new MikeRow\BananoPHP\BananoRPC('http', 'localhost', 7076, 'api/v2');
28 |
29 | $bananorpc->setBananoApi(2);
30 |
31 | $t0 = microtime(true);
32 |
33 | for ($i = 0; $i < $cycles; $i++) {
34 | $bananorpc->AccountWeight(['account' => $account]);
35 | }
36 |
37 | echo 'Time v2: ' . (microtime(true) - $t0) . PHP_EOL;
38 |
--------------------------------------------------------------------------------