protected function updateChannel(Channel $chan)
{
// The original Merkle Tree (and root) from the last-known-good state.
$originalTree = $this->getMerkleTree($chan);
$originalRoot = $originalTree->getRoot();
foreach ($chan->getAllURLs() as $url) {
try {
/**
* Fetch all the alleged updates from the server, which are to
* be treated with severe skepticism. The updates we see here
* are, at least, authenticated by the server API's signing key
* which means we're commmunicating with the correct channel.
*
* @var TreeUpdate[]
*/
$updates = $this->fetchTreeUpdates($chan, $url, $originalRoot);
if (empty($updates)) {
// Nothing to do.
return;
}
/**
* We'll keep retrying until we're in a good state or we have
* no more candidate updates left to add to our tree.
*/
while (!empty($updates)) {
$merkleTree = $originalTree->getExpandedTree();
// Verify these updates with our Peers.
try {
if ($this->verifyResponseWithPeers($chan, $merkleTree, ...$updates)) {
// Apply these updates:
$this->processTreeUpdates($chan, ...$updates);
return;
}
// If we're here, verification failed
} catch (CouldNotUpdate $ex) {
/**
* Something bad happened. Possibilities:
*
* 1. One of our peers disagreed with the Merkle root
* that we saw, so we aborted for safety.
* 2. Too many network failures occurred, so we aborted
* to prevent a DoS to create a false consensus.
*
* Log the details, then the loop will continue.
*/
$this->log($ex->getMessage(), LogLevel::ALERT, \Airship\throwableToArray($ex));
$subsequent = [];
foreach ($updates as $up) {
if ($up instanceof TreeUpdate) {
$subsequent[] = $this->getLogData($up);
}
}
self::$continuumLogger->store(LogLevel::ALERT, $ex->getMessage(), ['action' => 'KEYGGDRASIL', 'baseRoot' => $originalRoot, 'subsequent' => $subsequent]);
}
// If verification fails, pop off the last update and try again
\array_pop($updates);
}
// Received a successful API response.
return;
} catch (ChannelSignatureFailed $ex) {
/**
* We can't even trust the channel's API response. An error
* occurred. We aborte entirely at this step.
*
* This may mean a MITM attacker with a valid CA certificate.
* This may mean a server-side error.
*
* Log and abort; don't try to automate any decisions based
* on a strange network state.
*/
$this->log('Invalid Channel Signature for ' . $chan->getName(), LogLevel::ALERT, \Airship\throwableToArray($ex));
self::$continuumLogger->store(LogLevel::ALERT, 'Invalid Channel Signature for ' . $chan->getName(), ['action' => 'KEYGGDRASIL']);
} catch (TransferException $ex) {
/**
* Typical network error. Maybe an HTTP 5xx response code?
*
* Either way: log and abort.
*/
$this->log('Channel update error', LogLevel::NOTICE, \Airship\throwableToArray($ex));
}
}
// IF we get HERE, we've run out of updates to try.
$this->log('Channel update concluded with no changes', LogLevel::ALERT);
}