Generalizes the interface for block explorers in OTS
This commit is contained in:
@@ -24,6 +24,7 @@ import android.util.Log
|
|||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.quartz.encoders.HexKey
|
import com.vitorpamplona.quartz.encoders.HexKey
|
||||||
import com.vitorpamplona.quartz.encoders.hexToByteArray
|
import com.vitorpamplona.quartz.encoders.hexToByteArray
|
||||||
|
import com.vitorpamplona.quartz.ots.BlockstreamExplorer
|
||||||
import com.vitorpamplona.quartz.ots.DetachedTimestampFile
|
import com.vitorpamplona.quartz.ots.DetachedTimestampFile
|
||||||
import com.vitorpamplona.quartz.ots.Hash
|
import com.vitorpamplona.quartz.ots.Hash
|
||||||
import com.vitorpamplona.quartz.ots.OpenTimestamps
|
import com.vitorpamplona.quartz.ots.OpenTimestamps
|
||||||
@@ -57,7 +58,7 @@ class OtsEvent(
|
|||||||
|
|
||||||
val detachedOts = DetachedTimestampFile.deserialize(otsByteArray())
|
val detachedOts = DetachedTimestampFile.deserialize(otsByteArray())
|
||||||
|
|
||||||
val result = OpenTimestamps.verify(detachedOts, digest)
|
val result = OpenTimestamps(BlockstreamExplorer()).verify(detachedOts, digest)
|
||||||
if (result == null || result.isEmpty()) {
|
if (result == null || result.isEmpty()) {
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
@@ -72,7 +73,7 @@ class OtsEvent(
|
|||||||
|
|
||||||
fun info(): String {
|
fun info(): String {
|
||||||
val detachedOts = DetachedTimestampFile.deserialize(otsByteArray())
|
val detachedOts = DetachedTimestampFile.deserialize(otsByteArray())
|
||||||
return OpenTimestamps.info(detachedOts)
|
return OpenTimestamps(BlockstreamExplorer()).info(detachedOts)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -82,7 +83,7 @@ class OtsEvent(
|
|||||||
fun stamp(eventId: HexKey): ByteArray {
|
fun stamp(eventId: HexKey): ByteArray {
|
||||||
val hash = Hash(eventId.hexToByteArray(), OpSHA256._TAG)
|
val hash = Hash(eventId.hexToByteArray(), OpSHA256._TAG)
|
||||||
val file = DetachedTimestampFile.from(hash)
|
val file = DetachedTimestampFile.from(hash)
|
||||||
val timestamp = OpenTimestamps.stamp(file)
|
val timestamp = OpenTimestamps(BlockstreamExplorer()).stamp(file)
|
||||||
val detachedToSerialize = DetachedTimestampFile(hash.getOp(), timestamp)
|
val detachedToSerialize = DetachedTimestampFile(hash.getOp(), timestamp)
|
||||||
return detachedToSerialize.serialize()
|
return detachedToSerialize.serialize()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.vitorpamplona.quartz.ots;
|
||||||
|
|
||||||
|
public interface BitcoinExplorer {
|
||||||
|
/**
|
||||||
|
* Retrieve the block information from the block hash.
|
||||||
|
*
|
||||||
|
* @param hash Hash of the block.
|
||||||
|
* @return the blockheader of the hash
|
||||||
|
* @throws Exception desc
|
||||||
|
*/
|
||||||
|
public BlockHeader block(final String hash) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the block hash from the block height.
|
||||||
|
*
|
||||||
|
* @param height Height of the block.
|
||||||
|
* @return the hash of the block at height height
|
||||||
|
* @throws Exception desc
|
||||||
|
*/
|
||||||
|
public String blockHash(final Integer height) throws Exception;
|
||||||
|
}
|
||||||
+11
-13
@@ -1,17 +1,15 @@
|
|||||||
package com.vitorpamplona.quartz.ots;
|
package com.vitorpamplona.quartz.ots;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.vitorpamplona.quartz.ots.http.Request;
|
import com.vitorpamplona.quartz.ots.http.Request;
|
||||||
import com.vitorpamplona.quartz.ots.http.Response;
|
import com.vitorpamplona.quartz.ots.http.Response;
|
||||||
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
public class Esplora {
|
public class BlockstreamExplorer implements BitcoinExplorer {
|
||||||
|
|
||||||
private static final Logger log = Utils.getLogger(Esplora.class.getName());
|
|
||||||
private static final String esploraUrl = "https://blockstream.info/api";
|
private static final String esploraUrl = "https://blockstream.info/api";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,7 +19,7 @@ public class Esplora {
|
|||||||
* @return the blockheader of the hash
|
* @return the blockheader of the hash
|
||||||
* @throws Exception desc
|
* @throws Exception desc
|
||||||
*/
|
*/
|
||||||
public static BlockHeader block(final String hash) throws Exception {
|
public BlockHeader block(final String hash) throws Exception {
|
||||||
final URL url = new URL(esploraUrl + "/block/" + hash);
|
final URL url = new URL(esploraUrl + "/block/" + hash);
|
||||||
final Request task = new Request(url);
|
final Request task = new Request(url);
|
||||||
final ExecutorService executor = Executors.newSingleThreadExecutor();
|
final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
@@ -31,14 +29,14 @@ public class Esplora {
|
|||||||
if (!take.isOk())
|
if (!take.isOk())
|
||||||
throw new Exception();
|
throw new Exception();
|
||||||
|
|
||||||
final JSONObject jsonObject = take.getJson();
|
final JsonNode jsonObject = take.getJson();
|
||||||
final String merkleroot = jsonObject.getString("merkle_root");
|
final String merkleroot = jsonObject.get("merkle_root").asText();
|
||||||
final String time = String.valueOf(jsonObject.getInt("timestamp"));
|
final String time = String.valueOf(jsonObject.get("timestamp").asInt());
|
||||||
final BlockHeader blockHeader = new BlockHeader();
|
final BlockHeader blockHeader = new BlockHeader();
|
||||||
blockHeader.setMerkleroot(merkleroot);
|
blockHeader.setMerkleroot(merkleroot);
|
||||||
blockHeader.setTime(time);
|
blockHeader.setTime(time);
|
||||||
blockHeader.setBlockHash(hash);
|
blockHeader.setBlockHash(hash);
|
||||||
log.info(take.getFromUrl() + " " + blockHeader);
|
Log.i("BlockstreamExplorer", take.getFromUrl() + " " + blockHeader);
|
||||||
return blockHeader;
|
return blockHeader;
|
||||||
//log.warning("Cannot parse merkleroot from body: " + jsonObject + ": " + e.getMessage());
|
//log.warning("Cannot parse merkleroot from body: " + jsonObject + ": " + e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -50,7 +48,7 @@ public class Esplora {
|
|||||||
* @return the hash of the block at height height
|
* @return the hash of the block at height height
|
||||||
* @throws Exception desc
|
* @throws Exception desc
|
||||||
*/
|
*/
|
||||||
public static String blockHash(final Integer height) throws Exception {
|
public String blockHash(final Integer height) throws Exception {
|
||||||
final URL url = new URL(esploraUrl + "/block-height/" + height);
|
final URL url = new URL(esploraUrl + "/block-height/" + height);
|
||||||
final Request task = new Request(url);
|
final Request task = new Request(url);
|
||||||
final ExecutorService executor = Executors.newSingleThreadExecutor();
|
final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
@@ -60,7 +58,7 @@ public class Esplora {
|
|||||||
if (!take.isOk())
|
if (!take.isOk())
|
||||||
throw new Exception();
|
throw new Exception();
|
||||||
final String blockHash = take.getString();
|
final String blockHash = take.getString();
|
||||||
log.info(take.getFromUrl() + " " + blockHash);
|
Log.i("BlockstreamExplorer", take.getFromUrl() + " " + blockHash);
|
||||||
return blockHash;
|
return blockHash;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,13 +32,19 @@ import java.util.concurrent.Executors;
|
|||||||
*/
|
*/
|
||||||
public class OpenTimestamps {
|
public class OpenTimestamps {
|
||||||
|
|
||||||
|
BitcoinExplorer explorer;
|
||||||
|
|
||||||
|
public OpenTimestamps(BitcoinExplorer explorer) {
|
||||||
|
this.explorer = explorer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show information on a detached timestamp.
|
* Show information on a detached timestamp.
|
||||||
*
|
*
|
||||||
* @param detachedTimestampFile The DetachedTimestampFile ots.
|
* @param detachedTimestampFile The DetachedTimestampFile ots.
|
||||||
* @return the string representation of the timestamp.
|
* @return the string representation of the timestamp.
|
||||||
*/
|
*/
|
||||||
public static String info(DetachedTimestampFile detachedTimestampFile) {
|
public String info(DetachedTimestampFile detachedTimestampFile) {
|
||||||
return info(detachedTimestampFile, false);
|
return info(detachedTimestampFile, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +55,7 @@ public class OpenTimestamps {
|
|||||||
* @param verbose Show verbose output.
|
* @param verbose Show verbose output.
|
||||||
* @return the string representation of the timestamp.
|
* @return the string representation of the timestamp.
|
||||||
*/
|
*/
|
||||||
public static String info(DetachedTimestampFile detachedTimestampFile, boolean verbose) {
|
public String info(DetachedTimestampFile detachedTimestampFile, boolean verbose) {
|
||||||
if (detachedTimestampFile == null) {
|
if (detachedTimestampFile == null) {
|
||||||
return "No ots file";
|
return "No ots file";
|
||||||
}
|
}
|
||||||
@@ -68,7 +74,7 @@ public class OpenTimestamps {
|
|||||||
* @param timestamp The timestamp buffered.
|
* @param timestamp The timestamp buffered.
|
||||||
* @return the string representation of the timestamp.
|
* @return the string representation of the timestamp.
|
||||||
*/
|
*/
|
||||||
public static String info(Timestamp timestamp) {
|
public String info(Timestamp timestamp) {
|
||||||
if (timestamp == null) {
|
if (timestamp == null) {
|
||||||
return "No timestamp";
|
return "No timestamp";
|
||||||
}
|
}
|
||||||
@@ -86,8 +92,8 @@ public class OpenTimestamps {
|
|||||||
* @return The plain array buffer of stamped.
|
* @return The plain array buffer of stamped.
|
||||||
* @throws IOException if fileTimestamp is not valid, or the stamp procedure fails.
|
* @throws IOException if fileTimestamp is not valid, or the stamp procedure fails.
|
||||||
*/
|
*/
|
||||||
public static Timestamp stamp(DetachedTimestampFile fileTimestamp) throws IOException {
|
public Timestamp stamp(DetachedTimestampFile fileTimestamp) throws IOException {
|
||||||
return OpenTimestamps.stamp(fileTimestamp, null, 0);
|
return stamp(fileTimestamp, null, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -99,11 +105,11 @@ public class OpenTimestamps {
|
|||||||
* @return The plain array buffer of stamped.
|
* @return The plain array buffer of stamped.
|
||||||
* @throws IOException if fileTimestamp is not valid, or the stamp procedure fails.
|
* @throws IOException if fileTimestamp is not valid, or the stamp procedure fails.
|
||||||
*/
|
*/
|
||||||
public static Timestamp stamp(DetachedTimestampFile fileTimestamp, List<String> calendarsUrl, Integer m) throws IOException {
|
public Timestamp stamp(DetachedTimestampFile fileTimestamp, List<String> calendarsUrl, Integer m) throws IOException {
|
||||||
List<DetachedTimestampFile> fileTimestamps = new ArrayList<DetachedTimestampFile>();
|
List<DetachedTimestampFile> fileTimestamps = new ArrayList<DetachedTimestampFile>();
|
||||||
fileTimestamps.add(fileTimestamp);
|
fileTimestamps.add(fileTimestamp);
|
||||||
|
|
||||||
return OpenTimestamps.stamp(fileTimestamps, calendarsUrl, m);
|
return stamp(fileTimestamps, calendarsUrl, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,7 +121,7 @@ public class OpenTimestamps {
|
|||||||
* @return The plain array buffer of stamped.
|
* @return The plain array buffer of stamped.
|
||||||
* @throws IOException if fileTimestamp is not valid, or the stamp procedure fails.
|
* @throws IOException if fileTimestamp is not valid, or the stamp procedure fails.
|
||||||
*/
|
*/
|
||||||
public static Timestamp stamp(List<DetachedTimestampFile> fileTimestamps, List<String> calendarsUrl, Integer m) throws IOException {
|
public Timestamp stamp(List<DetachedTimestampFile> fileTimestamps, List<String> calendarsUrl, Integer m) throws IOException {
|
||||||
// Parse parameters
|
// Parse parameters
|
||||||
if (fileTimestamps == null || fileTimestamps.size() == 0) {
|
if (fileTimestamps == null || fileTimestamps.size() == 0) {
|
||||||
throw new IOException();
|
throw new IOException();
|
||||||
@@ -144,14 +150,14 @@ public class OpenTimestamps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build markle tree
|
// Build markle tree
|
||||||
Timestamp merkleTip = OpenTimestamps.makeMerkleTree(fileTimestamps);
|
Timestamp merkleTip = makeMerkleTree(fileTimestamps);
|
||||||
|
|
||||||
if (merkleTip == null) {
|
if (merkleTip == null) {
|
||||||
throw new IOException();
|
throw new IOException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stamping
|
// Stamping
|
||||||
Timestamp resultTimestamp = OpenTimestamps.create(merkleTip, calendarsUrl, m);
|
Timestamp resultTimestamp = create(merkleTip, calendarsUrl, m);
|
||||||
|
|
||||||
if (resultTimestamp == null) {
|
if (resultTimestamp == null) {
|
||||||
throw new IOException();
|
throw new IOException();
|
||||||
@@ -173,7 +179,7 @@ public class OpenTimestamps {
|
|||||||
* @param m Number of calendars to use.
|
* @param m Number of calendars to use.
|
||||||
* @return The created timestamp.
|
* @return The created timestamp.
|
||||||
*/
|
*/
|
||||||
private static Timestamp create(Timestamp timestamp, List<String> calendarUrls, Integer m) {
|
private Timestamp create(Timestamp timestamp, List<String> calendarUrls, Integer m) {
|
||||||
int capacity = calendarUrls.size();
|
int capacity = calendarUrls.size();
|
||||||
ExecutorService executor = Executors.newFixedThreadPool(4);
|
ExecutorService executor = Executors.newFixedThreadPool(4);
|
||||||
ArrayBlockingQueue<Optional<Timestamp>> queue = new ArrayBlockingQueue<>(capacity);
|
ArrayBlockingQueue<Optional<Timestamp>> queue = new ArrayBlockingQueue<>(capacity);
|
||||||
@@ -227,7 +233,7 @@ public class OpenTimestamps {
|
|||||||
* @param fileTimestamps The list of DetachedTimestampFile.
|
* @param fileTimestamps The list of DetachedTimestampFile.
|
||||||
* @return merkle tip timestamp.
|
* @return merkle tip timestamp.
|
||||||
*/
|
*/
|
||||||
public static Timestamp makeMerkleTree(List<DetachedTimestampFile> fileTimestamps) {
|
public Timestamp makeMerkleTree(List<DetachedTimestampFile> fileTimestamps) {
|
||||||
List<Timestamp> merkleRoots = new ArrayList<>();
|
List<Timestamp> merkleRoots = new ArrayList<>();
|
||||||
|
|
||||||
for (DetachedTimestampFile fileTimestamp : fileTimestamps) {
|
for (DetachedTimestampFile fileTimestamp : fileTimestamps) {
|
||||||
@@ -259,14 +265,14 @@ public class OpenTimestamps {
|
|||||||
* @throws Exception if the verification procedure fails.
|
* @throws Exception if the verification procedure fails.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public static HashMap<VerifyResult.Chains, VerifyResult> verify(DetachedTimestampFile ots, byte[] diggest) throws Exception {
|
public HashMap<VerifyResult.Chains, VerifyResult> verify(DetachedTimestampFile ots, byte[] diggest) throws Exception {
|
||||||
if (!Arrays.equals(ots.fileDigest(), diggest)) {
|
if (!Arrays.equals(ots.fileDigest(), diggest)) {
|
||||||
Log.e("OpenTimestamp", "Expected digest " + Hex.encode(ots.fileDigest()).toLowerCase());
|
Log.e("OpenTimestamp", "Expected digest " + Hex.encode(ots.fileDigest()).toLowerCase());
|
||||||
Log.e("OpenTimestamp", "File does not match original!");
|
Log.e("OpenTimestamp", "File does not match original!");
|
||||||
throw new Exception("File does not match original!");
|
throw new Exception("File does not match original!");
|
||||||
}
|
}
|
||||||
|
|
||||||
return OpenTimestamps.verify(ots.timestamp);
|
return verify(ots.timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -276,7 +282,7 @@ public class OpenTimestamps {
|
|||||||
* @return HashMap of block heights and timestamps indexed by chain: timestamp in seconds from 1 January 1970.
|
* @return HashMap of block heights and timestamps indexed by chain: timestamp in seconds from 1 January 1970.
|
||||||
* @throws Exception if the verification procedure fails.
|
* @throws Exception if the verification procedure fails.
|
||||||
*/
|
*/
|
||||||
public static HashMap<VerifyResult.Chains, VerifyResult> verify(Timestamp timestamp) throws Exception {
|
public HashMap<VerifyResult.Chains, VerifyResult> verify(Timestamp timestamp) throws Exception {
|
||||||
HashMap<VerifyResult.Chains, VerifyResult> verifyResults = new HashMap<>();
|
HashMap<VerifyResult.Chains, VerifyResult> verifyResults = new HashMap<>();
|
||||||
|
|
||||||
for (Map.Entry<byte[], TimeAttestation> item : timestamp.allAttestations().entrySet()) {
|
for (Map.Entry<byte[], TimeAttestation> item : timestamp.allAttestations().entrySet()) {
|
||||||
@@ -339,13 +345,13 @@ public class OpenTimestamps {
|
|||||||
* @throws VerificationException if it doesn't check the merkle root of the block.
|
* @throws VerificationException if it doesn't check the merkle root of the block.
|
||||||
* @throws Exception if the verification procedure fails.
|
* @throws Exception if the verification procedure fails.
|
||||||
*/
|
*/
|
||||||
public static Long verify(BitcoinBlockHeaderAttestation attestation, byte[] msg) throws VerificationException, Exception {
|
public Long verify(BitcoinBlockHeaderAttestation attestation, byte[] msg) throws VerificationException, Exception {
|
||||||
Integer height = attestation.getHeight();
|
Integer height = attestation.getHeight();
|
||||||
BlockHeader blockInfo;
|
BlockHeader blockInfo;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String blockHash = Esplora.blockHash(height);
|
String blockHash = explorer.blockHash(height);
|
||||||
blockInfo = Esplora.block(blockHash);
|
blockInfo = explorer.block(blockHash);
|
||||||
Log.i("OpenTimestamps", "Lite-client verification, assuming block " + blockHash + " is valid");
|
Log.i("OpenTimestamps", "Lite-client verification, assuming block " + blockHash + " is valid");
|
||||||
} catch (Exception e2) {
|
} catch (Exception e2) {
|
||||||
e2.printStackTrace();
|
e2.printStackTrace();
|
||||||
@@ -364,13 +370,13 @@ public class OpenTimestamps {
|
|||||||
* @throws VerificationException if it doesn't check the merkle root of the block.
|
* @throws VerificationException if it doesn't check the merkle root of the block.
|
||||||
* @throws Exception if the verification procedure fails.
|
* @throws Exception if the verification procedure fails.
|
||||||
*/
|
*/
|
||||||
public static Long verify(LitecoinBlockHeaderAttestation attestation, byte[] msg) throws VerificationException, Exception {
|
public Long verify(LitecoinBlockHeaderAttestation attestation, byte[] msg) throws VerificationException, Exception {
|
||||||
Integer height = attestation.getHeight();
|
Integer height = attestation.getHeight();
|
||||||
BlockHeader blockInfo;
|
BlockHeader blockInfo;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String blockHash = Esplora.blockHash(height);
|
String blockHash = explorer.blockHash(height);
|
||||||
blockInfo = Esplora.block(blockHash);
|
blockInfo = explorer.block(blockHash);
|
||||||
Log.i("OpenTimestamps", "Lite-client verification, assuming block " + blockHash + " is valid");
|
Log.i("OpenTimestamps", "Lite-client verification, assuming block " + blockHash + " is valid");
|
||||||
} catch (Exception e2) {
|
} catch (Exception e2) {
|
||||||
e2.printStackTrace();
|
e2.printStackTrace();
|
||||||
@@ -387,9 +393,9 @@ public class OpenTimestamps {
|
|||||||
* @return a boolean representing if the timestamp has changed.
|
* @return a boolean representing if the timestamp has changed.
|
||||||
* @throws Exception if the upgrading procedure fails.
|
* @throws Exception if the upgrading procedure fails.
|
||||||
*/
|
*/
|
||||||
public static boolean upgrade(DetachedTimestampFile detachedTimestamp) throws Exception {
|
public boolean upgrade(DetachedTimestampFile detachedTimestamp) throws Exception {
|
||||||
// Upgrade timestamp
|
// Upgrade timestamp
|
||||||
boolean changed = OpenTimestamps.upgrade(detachedTimestamp.timestamp);
|
boolean changed = upgrade(detachedTimestamp.timestamp);
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,7 +407,7 @@ public class OpenTimestamps {
|
|||||||
* @return a boolean representing if the timestamp has changed.
|
* @return a boolean representing if the timestamp has changed.
|
||||||
* @throws Exception if the upgrading procedure fails.
|
* @throws Exception if the upgrading procedure fails.
|
||||||
*/
|
*/
|
||||||
public static boolean upgrade(Timestamp timestamp) throws Exception {
|
public boolean upgrade(Timestamp timestamp) throws Exception {
|
||||||
// Check remote calendars for upgrades.
|
// Check remote calendars for upgrades.
|
||||||
// This time we only check PendingAttestations - we can't be as agressive.
|
// This time we only check PendingAttestations - we can't be as agressive.
|
||||||
|
|
||||||
@@ -417,7 +423,7 @@ public class OpenTimestamps {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Calendar calendar = new Calendar(calendarUrl);
|
Calendar calendar = new Calendar(calendarUrl);
|
||||||
Timestamp upgradedStamp = OpenTimestamps.upgrade(subStamp, calendar, commitment, existingAttestations);
|
Timestamp upgradedStamp = upgrade(subStamp, calendar, commitment, existingAttestations);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
subStamp.merge(upgradedStamp);
|
subStamp.merge(upgradedStamp);
|
||||||
@@ -436,7 +442,7 @@ public class OpenTimestamps {
|
|||||||
return upgraded;
|
return upgraded;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Timestamp upgrade(Timestamp subStamp, Calendar calendar, byte[] commitment, Set<TimeAttestation> existingAttestations) throws Exception {
|
private Timestamp upgrade(Timestamp subStamp, Calendar calendar, byte[] commitment, Set<TimeAttestation> existingAttestations) throws Exception {
|
||||||
Timestamp upgradedStamp;
|
Timestamp upgradedStamp;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user