| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * @author Russell Michell 2018 <[email protected]> |
||
| 5 | * @package silverstripe-verifiable |
||
| 6 | */ |
||
| 7 | |||
| 8 | namespace PhpTek\Verifiable\Backend\Chainpoint; |
||
| 9 | |||
| 10 | use SilverStripe\Core\Config\Configurable; |
||
| 11 | use GuzzleHttp\Client; |
||
| 12 | use GuzzleHttp\Exception\RequestException; |
||
| 13 | use PhpTek\Verifiable\Exception\VerifiableBackendException; |
||
| 14 | use PhpTek\Verifiable\Exception\VerifiableValidationException; |
||
| 15 | use PhpTek\Verifiable\Backend\GatewayProvider; |
||
| 16 | |||
| 17 | /** |
||
| 18 | * Calls the endpoints of the Tierion network's ChainPoint service. |
||
| 19 | * |
||
| 20 | * @see https://app.swaggerhub.com/apis/chainpoint/node/1.0.0 |
||
| 21 | * @see https://chainpoint.org |
||
| 22 | */ |
||
| 23 | class Gateway implements GatewayProvider |
||
| 24 | { |
||
| 25 | use Configurable; |
||
| 26 | |||
| 27 | /** |
||
| 28 | * An array of nodes for submitting hashes to. |
||
| 29 | * |
||
| 30 | * @var array |
||
| 31 | */ |
||
| 32 | protected static $discovered_nodes = []; |
||
| 33 | |||
| 34 | /** |
||
| 35 | * @return string |
||
| 36 | */ |
||
| 37 | public function name() : string |
||
| 38 | { |
||
| 39 | return 'chainpoint'; |
||
| 40 | } |
||
| 41 | |||
| 42 | /** |
||
| 43 | * @return string |
||
| 44 | */ |
||
| 45 | public function hashFunc() : string |
||
| 46 | { |
||
| 47 | return 'sha256'; |
||
| 48 | } |
||
| 49 | |||
| 50 | /** |
||
| 51 | * Send a single hash_id_node to retrieve a proof in binary format from the |
||
| 52 | * Tierion network. |
||
| 53 | * |
||
| 54 | * GETs to the: "/proofs" REST API endpoint. |
||
| 55 | * |
||
| 56 | * @param string $hashIdNode |
||
| 57 | * @return string A v3 ChainpointProof |
||
| 58 | */ |
||
| 59 | public function proofs(string $hashIdNode) : string |
||
| 60 | { |
||
| 61 | $response = $this->client("/proofs/$hashIdNode", 'GET'); |
||
| 62 | |||
| 63 | return $response->getBody()->getContents() ?? '[]'; |
||
| 64 | } |
||
| 65 | |||
| 66 | /** |
||
| 67 | * Send an array of hashes for anchoring. |
||
| 68 | * |
||
| 69 | * POSTs to the: "/hashes" REST API endpoint. |
||
| 70 | * |
||
| 71 | * @param array $hashes |
||
| 72 | * @return string (From GuzzleHttp\Stream::getContents() |
||
| 73 | */ |
||
| 74 | public function hashes(array $hashes) : string |
||
| 75 | { |
||
| 76 | $response = $this->client('/hashes', 'POST', ['hashes' => $hashes]); |
||
| 77 | |||
| 78 | return $response->getBody()->getContents() ?? '[]'; |
||
| 79 | } |
||
| 80 | |||
| 81 | /** |
||
| 82 | * Submit a chainpoint proof to the backend for verification. |
||
| 83 | * |
||
| 84 | * @param string $proof A partial or full JSON string, originally received from, |
||
| 85 | * or generated on behalf of a backend. |
||
| 86 | * @return string |
||
| 87 | * @todo See the returned proof's "uris" key, to be able to call a specific URI for proof verification. |
||
| 88 | */ |
||
| 89 | public function verify(string $proof) : string |
||
| 90 | { |
||
| 91 | $response = $this->client('/verify', 'POST', ['proofs' => [$proof]]); |
||
| 92 | |||
| 93 | return $response->getBody()->getContents() ?? '[]'; |
||
| 94 | } |
||
| 95 | |||
| 96 | /** |
||
| 97 | * Return a client to use for all RPC traffic to this backend. |
||
| 98 | * |
||
| 99 | * @param string $url The absolute or relative URL to make a request to. |
||
| 100 | * @param string $verb The HTTP verb to use e.g. GET or POST. |
||
| 101 | * @param array $payload The payload to be sent along in GET/POST requests. |
||
| 102 | * @param bool $rel Is $url relative? If so, pass "base_uri" to {@link Client}. |
||
| 103 | * @return Response Guzzle Response object |
||
|
0 ignored issues
–
show
|
|||
| 104 | * @throws VerifiableBackendException |
||
| 105 | */ |
||
| 106 | protected function client(string $url, string $verb, array $payload = [], bool $rel = true) |
||
| 107 | { |
||
| 108 | if ($rel && !$this->getDiscoveredNodes()) { |
||
| 109 | $this->setDiscoveredNodes(); |
||
| 110 | |||
| 111 | if (!$this->getDiscoveredNodes()) { |
||
| 112 | // This should _never_ happen.. |
||
| 113 | throw new VerifiableValidationException('No chainpoint nodes discovered!'); |
||
| 114 | } |
||
| 115 | } |
||
| 116 | |||
| 117 | $verb = strtoupper($verb); |
||
| 118 | $method = strtolower($verb); |
||
| 119 | $config = $this->config()->get('client_config'); |
||
| 120 | $client = new Client([ |
||
| 121 | 'base_uri' => $rel ? $this->getDiscoveredNodes()[0] : '', // Use a single address only |
||
| 122 | 'verify' => true, |
||
| 123 | 'timeout' => $config['timeout'], |
||
| 124 | 'connect_timeout' => $config['connect_timeout'], |
||
| 125 | 'allow_redirects' => false, |
||
| 126 | ]); |
||
| 127 | |||
| 128 | try { |
||
| 129 | // json_encodes POSTed data and sends correct Content-Type header |
||
| 130 | if ($payload && $verb === 'POST') { |
||
|
0 ignored issues
–
show
The expression
$payload of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
|
|||
| 131 | $payload['json'] = $payload; |
||
| 132 | } |
||
| 133 | |||
| 134 | if ($rel) { |
||
| 135 | $url = ($rel ? $this->getDiscoveredNodes()[0] : '') . $url; |
||
|
0 ignored issues
–
show
|
|||
| 136 | } |
||
| 137 | |||
| 138 | return $client->$method($url, $payload); |
||
| 139 | } catch (RequestException $e) { |
||
| 140 | throw new VerifiableValidationException(sprintf('Upstream network problem: %s', $e->getMessage())); |
||
| 141 | } |
||
| 142 | } |
||
| 143 | |||
| 144 | /** |
||
| 145 | * The Tierion network comprises many nodes, some of which may or may not be |
||
| 146 | * online. We therefore randomly select a source of curated (audited) node-IPs |
||
| 147 | * and for each IP, we ping it until we receive a 200 OK response. For each such |
||
| 148 | * node, it is then set to $discovered_nodes. |
||
| 149 | * |
||
| 150 | * @param array $usedNodes Optionally pass some "pre-known" chainpoint nodes |
||
| 151 | * @return mixed void | null |
||
| 152 | * @throws VerifiableBackendException |
||
| 153 | */ |
||
| 154 | public function setDiscoveredNodes($usedNodes = null) |
||
| 155 | { |
||
| 156 | if ($usedNodes) { |
||
| 157 | static::$discovered_nodes = $usedNodes; |
||
| 158 | |||
| 159 | return; |
||
| 160 | } |
||
| 161 | |||
| 162 | if (count(static::$discovered_nodes)) { |
||
| 163 | return; |
||
| 164 | } |
||
| 165 | |||
| 166 | $limit = (int) $this->config()->get('discover_node_count') ?: 1; |
||
| 167 | $chainpointUrls = $this->config()->get('chainpoint_urls'); |
||
| 168 | $url = $chainpointUrls[rand(0, 2)]; |
||
| 169 | $response = $this->client($url, 'GET', [], false); |
||
| 170 | |||
| 171 | if ($response->getStatusCode() !== 200) { |
||
| 172 | throw new VerifiableBackendException('Bad response from node source URL'); |
||
| 173 | } |
||
| 174 | |||
| 175 | $i = 0; |
||
| 176 | |||
| 177 | foreach (json_decode($response->getBody(), true) as $candidate) { |
||
| 178 | $response = $this->client($candidate['public_uri'], 'GET', [], false); |
||
| 179 | |||
| 180 | if ($response->getStatusCode() !== 200) { |
||
| 181 | continue; |
||
| 182 | } |
||
| 183 | |||
| 184 | ++$i; // Only increment with succesful requests |
||
| 185 | |||
| 186 | static::$discovered_nodes[] = $candidate['public_uri']; |
||
| 187 | |||
| 188 | if ($i === $limit) { |
||
| 189 | break; |
||
| 190 | } |
||
| 191 | } |
||
| 192 | } |
||
| 193 | |||
| 194 | /** |
||
| 195 | * @return array |
||
| 196 | */ |
||
| 197 | public function getDiscoveredNodes() |
||
| 198 | { |
||
| 199 | return static::$discovered_nodes; |
||
| 200 | } |
||
| 201 | } |
||
| 202 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths