use of com.hedera.mirror.importer.exception.SignatureVerificationException in project hedera-mirror-node by hashgraph.
the class Downloader method verifySigsAndDownloadDataFiles.
/**
* For each group of signature files with the same file name: (1) verify that the signature files are signed by
* corresponding node's PublicKey; (2) For valid signature files, we compare their Hashes to see if at least 1/3 of
* hashes match. If they do, we download the corresponding data file from a node folder which has valid signature
* file. (3) compare the hash of data file with Hash which has been agreed on by valid signatures, if match, move
* the data file into `valid` directory; else download the data file from other valid node folder and compare the
* hash until we find a match.
*
* @param sigFilesMap signature files grouped by filename
*/
private void verifySigsAndDownloadDataFiles(Multimap<String, FileStreamSignature> sigFilesMap) {
Instant endDate = mirrorProperties.getEndDate();
for (var sigFilenameIter = sigFilesMap.keySet().iterator(); sigFilenameIter.hasNext(); ) {
if (ShutdownHelper.isStopping()) {
return;
}
Instant startTime = Instant.now();
String sigFilename = sigFilenameIter.next();
Collection<FileStreamSignature> signatures = sigFilesMap.get(sigFilename);
boolean valid = false;
try {
nodeSignatureVerifier.verify(signatures);
} catch (SignatureVerificationException ex) {
if (sigFilenameIter.hasNext()) {
log.warn("Signature verification failed but still have files in the batch, try to process the " + "next group: {}", ex.getMessage());
continue;
}
throw ex;
}
for (FileStreamSignature signature : signatures) {
if (ShutdownHelper.isStopping()) {
return;
}
// Ignore signatures that didn't validate or weren't in the majority
if (signature.getStatus() != FileStreamSignature.SignatureStatus.CONSENSUS_REACHED) {
continue;
}
try {
PendingDownload pendingDownload = downloadSignedDataFile(signature);
if (!pendingDownload.waitForCompletion()) {
continue;
}
StreamFilename dataFilename = pendingDownload.getStreamFilename();
StreamFileData streamFileData = new StreamFileData(dataFilename, pendingDownload.getBytes());
T streamFile = streamFileReader.read(streamFileData);
streamFile.setNodeAccountId(signature.getNodeAccountId());
verify(streamFile, signature);
if (downloaderProperties.isWriteFiles()) {
Utility.archiveFile(streamFile.getName(), streamFile.getBytes(), downloaderProperties.getNodeStreamPath(signature.getNodeAccountIdString()));
}
if (downloaderProperties.isWriteSignatures()) {
signatures.forEach(s -> {
Path destination = downloaderProperties.getNodeStreamPath(s.getNodeAccountIdString());
Utility.archiveFile(s.getFilename(), s.getBytes(), destination);
});
}
if (!downloaderProperties.isPersistBytes()) {
streamFile.setBytes(null);
}
if (dataFilename.getInstant().isAfter(endDate)) {
downloaderProperties.setEnabled(false);
log.warn("Disabled polling after downloading all files <= endDate ({})", endDate);
return;
}
onVerified(pendingDownload, streamFile);
valid = true;
break;
} catch (HashMismatchException e) {
log.warn("Failed to verify data file from node {} corresponding to {}. Will retry another node", signature.getNodeAccountIdString(), sigFilename, e);
} catch (InterruptedException e) {
log.warn("Failed to download data file from node {} corresponding to {}", signature.getNodeAccountIdString(), sigFilename, e);
Thread.currentThread().interrupt();
} catch (Exception e) {
log.error("Error downloading data file from node {} corresponding to {}. Will retry another node", signature.getNodeAccountIdString(), sigFilename, e);
}
}
if (!valid) {
log.error("None of the data files could be verified, signatures: {}", signatures);
}
streamVerificationMetric.tag("success", String.valueOf(valid)).register(meterRegistry).record(Duration.between(startTime, Instant.now()));
}
}
use of com.hedera.mirror.importer.exception.SignatureVerificationException in project hedera-mirror-node by hashgraph.
the class Downloader method downloadNextBatch.
protected void downloadNextBatch() {
if (!downloaderProperties.isEnabled()) {
return;
}
if (ShutdownHelper.isStopping()) {
return;
}
try {
AddressBook addressBook = addressBookService.getCurrent();
var sigFilesMap = downloadAndParseSigFiles(addressBook);
// Following is a cost optimization to not unnecessarily list the public demo bucket once complete
if (sigFilesMap.isEmpty() && mirrorProperties.getNetwork() == MirrorProperties.HederaNetwork.DEMO) {
downloaderProperties.setEnabled(false);
log.warn("Disabled polling after downloading all files in demo bucket");
}
// Verify signature files and download corresponding files of valid signature files
verifySigsAndDownloadDataFiles(sigFilesMap);
} catch (SignatureVerificationException e) {
log.warn(e.getMessage());
} catch (InterruptedException e) {
log.error("Error downloading files", e);
Thread.currentThread().interrupt();
} catch (Exception e) {
log.error("Error downloading files", e);
}
}
use of com.hedera.mirror.importer.exception.SignatureVerificationException in project hedera-mirror-node by hashgraph.
the class NodeSignatureVerifier method verify.
/**
* Verifies that the signature files satisfy the consensus requirement:
* <ol>
* <li>At least 1/3 signature files are present</li>
* <li>For a signature file, we validate it by checking if it's signed by corresponding node's PublicKey. For valid
* signature files, we compare their hashes to see if at least 1/3 have hashes that match. If a signature is
* valid, we put the hash in its content and its file to the map, to see if at least 1/3 valid signatures have
* the same hash</li>
* </ol>
*
* @param signatures a list of signature files which have the same filename
* @throws SignatureVerificationException
*/
public void verify(Collection<FileStreamSignature> signatures) throws SignatureVerificationException {
AddressBook currentAddressBook = addressBookService.getCurrent();
Map<String, PublicKey> nodeAccountIDPubKeyMap = currentAddressBook.getNodeAccountIDPubKeyMap();
Multimap<String, FileStreamSignature> signatureHashMap = HashMultimap.create();
String filename = signatures.stream().map(FileStreamSignature::getFilename).findFirst().orElse("unknown");
int consensusCount = 0;
long sigFileCount = signatures.size();
long nodeCount = nodeAccountIDPubKeyMap.size();
if (!canReachConsensus(sigFileCount, nodeCount)) {
throw new SignatureVerificationException(String.format("Insufficient downloaded signature file count, requires at least %.03f to reach consensus, got %d" + " out of %d for file %s: %s", commonDownloaderProperties.getConsensusRatio(), sigFileCount, nodeCount, filename, statusMap(signatures, nodeAccountIDPubKeyMap)));
}
for (FileStreamSignature fileStreamSignature : signatures) {
if (verifySignature(fileStreamSignature, nodeAccountIDPubKeyMap)) {
fileStreamSignature.setStatus(SignatureStatus.VERIFIED);
signatureHashMap.put(fileStreamSignature.getFileHashAsHex(), fileStreamSignature);
}
}
if (commonDownloaderProperties.getConsensusRatio() == 0 && signatureHashMap.size() > 0) {
log.debug("Signature file {} does not require consensus, skipping consensus check", filename);
return;
}
for (String key : signatureHashMap.keySet()) {
Collection<FileStreamSignature> validatedSignatures = signatureHashMap.get(key);
if (canReachConsensus(validatedSignatures.size(), nodeCount)) {
consensusCount += validatedSignatures.size();
validatedSignatures.forEach(s -> s.setStatus(SignatureStatus.CONSENSUS_REACHED));
}
}
if (consensusCount == nodeCount) {
log.debug("Verified signature file {} reached consensus", filename);
return;
} else if (consensusCount > 0) {
log.warn("Verified signature file {} reached consensus but with some errors: {}", filename, statusMap(signatures, nodeAccountIDPubKeyMap));
return;
}
throw new SignatureVerificationException("Signature verification failed for file " + filename + ": " + statusMap(signatures, nodeAccountIDPubKeyMap));
}
Aggregations