package lighthouse.protocol;
import com.google.common.base.*;
import com.google.common.util.concurrent.*;
import org.bitcoinj.core.*;
import org.slf4j.*;
import javax.annotation.*;
import java.net.*;
import java.nio.file.*;
import java.time.*;
import java.time.format.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.function.Function;
import java.util.stream.*;
public class LHUtils {
public static final String PROJECT_MIME_TYPE = "application/vnd.vinumeris.lighthouse-project";
public static final String PLEDGE_MIME_TYPE = "application/vnd.vinumeris.lighthouse-pledge";
private static final Logger log = LoggerFactory.getLogger(LHUtils.class);
public static List<Path> listDir(Path dir) {
List<Path> contents = new LinkedList<>();
try (Stream<Path> list = unchecked(() -> Files.list(dir))) {
list.forEach(contents::add);
}
return contents;
}
public static String nowAsString() {
return Instant.now().atZone(ZoneId.systemDefault()).format(DateTimeFormatter.RFC_1123_DATE_TIME);
}
public static boolean compareOutputsStructurally(Transaction tx, Project project) {
List<TransactionOutput> outs1 = mapList(tx.getOutputs(), TransactionOutput::duplicateDetached);
List<TransactionOutput> outs2 = mapList(project.getOutputs(), TransactionOutput::duplicateDetached);
return outs1.equals(outs2);
}
public static boolean pledgeAppearsInClaim(Project forProject, LHProtos.Pledge pledge, Transaction claim) {
Transaction tx = forProject.fastSanityCheck(pledge);
List<TransactionInput> pledgeInputs = mapList(tx.getInputs(), TransactionInput::duplicateDetached);
List<TransactionInput> claimInputs = mapList(claim.getInputs(), TransactionInput::duplicateDetached);
claimInputs.retainAll(pledgeInputs);
return claimInputs.size() == pledgeInputs.size();
}
public static String titleToUrlString(String s) {
return s.replaceAll("[\"!@#$%^&*()_+\\\\';:?=/.,\\[\\] \\n]", "-").replaceAll("-+", "-").replaceAll("(^-+|-+$)", "").toLowerCase();
}
public static boolean isUnix() {
return System.getProperty("os.name").toLowerCase().contains("linux") || System.getProperty("os.name").toLowerCase().contains("freebsd");
}
public static Transaction pledgeToTx(NetworkParameters params, LHProtos.Pledge pledge) {
return new Transaction(params, pledge.getTransactions(pledge.getTransactionsCount() - 1).toByteArray());
}
public static boolean isMac() {
return System.getProperty("os.name").toLowerCase().contains("mac");
}
//region Generic Java 8 enhancements
public interface UncheckedRun<T> {
T run() throws Throwable;
}
public interface UncheckedRunnable {
void run() throws Throwable;
}
public static <T> T unchecked(UncheckedRun<T> run) {
try {
return run.run();
} catch (Throwable throwable) {
Throwables.propagate(throwable);
throw new AssertionError(); // Unreachable.
}
}
public static void uncheck(UncheckedRunnable run) {
try {
run.run();
} catch (Throwable throwable) {
Throwables.propagate(throwable);
}
}
public static void ignoreAndLog(UncheckedRunnable runnable) {
try {
runnable.run();
} catch (Throwable t) {
log.error("Ignoring error", t);
}
}
public static <T> T ignoredAndLogged(UncheckedRun<T> runnable) {
try {
return runnable.run();
} catch (Throwable t) {
log.error("Ignoring error", t);
return null;
}
}
@SuppressWarnings("unchecked")
public static <T, E extends Throwable> T checkedGet(Future<T> future) throws E {
try {
return future.get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw (E) e.getCause();
}
}
public static boolean didThrow(UncheckedRun run) {
try {
run.run();
return false;
} catch (Throwable throwable) {
return true;
}
}
public static boolean didThrow(UncheckedRunnable run) {
try {
run.run();
return false;
} catch (Throwable throwable) {
return true;
}
}
public static <T> T stopwatched(String description, UncheckedRun<T> run) {
long now = System.currentTimeMillis();
T result = unchecked(run::run);
log.info("{}: {}ms", description, System.currentTimeMillis() - now);
return result;
}
public static void stopwatch(String description, UncheckedRunnable run) {
long now = System.currentTimeMillis();
uncheck(run::run);
log.info("{}: {}ms", description, System.currentTimeMillis() - now);
}
//endregion
/** Given an HTTP[S] URL, verifies that it matches the LH protocol layout and returns the hostname if so. */
@Nullable
public static String validateServerPath(String path) {
try {
if (path.isEmpty())
return null;
URI uri = new URI(path);
boolean validScheme = uri.getScheme().equals("https") || (uri.getScheme().equals("http") && uri.getHost().equals("localhost"));
if (validScheme && uri.getPath().startsWith(HTTP_PATH_PREFIX + HTTP_PROJECT_PATH))
return uri.getHost();
else
return null;
} catch (URISyntaxException e) {
return null;
}
}
public static final String HTTP_PATH_PREFIX = "/_lighthouse/crowdfund";
public static final String HTTP_PROJECT_PATH = "/project/";
public static final int HTTP_LOCAL_TEST_PORT = 13765;
public static String makeServerPath(String name, String projectID) {
return "https://" + name + HTTP_PATH_PREFIX + HTTP_PROJECT_PATH + projectID;
}
/** Convert from Guava futures to Java 8 futures */
public static <T> CompletableFuture<T> convertFuture(ListenableFuture<T> future) {
CompletableFuture<T> cf = new CompletableFuture<>();
Futures.addCallback(future, new FutureCallback<T>() {
@Override
public void onSuccess(@Nullable T result) {
cf.complete(result);
}
@Override
public void onFailure(Throwable t) {
cf.completeExceptionally(t);
}
});
return cf;
}
/** Just simpler syntax than the stupid streams library */
public static <T, R> List<R> mapList(List<T> inputs, Function<T, R> transformer) {
List<R> results = new ArrayList<>(inputs.size());
for (T input : inputs) results.add(transformer.apply(input));
return results;
}
public static <T> CompletableFuture<T> futureOfFutures(List<CompletableFuture<T>> futures) {
// It's dumb that the CF API is so huge and doesn't have this.
CompletableFuture<T> result = new CompletableFuture<>();
AtomicInteger numPending = new AtomicInteger(futures.size());
for (CompletableFuture<T> future : futures) {
future.handle((v, ex) -> {
if (numPending.decrementAndGet() == 0)
result.complete(v);
return null;
});
}
return result;
}
/** Either hashes the given pledge protobuf or returns the hash it claims to have been originally. */
public static Sha256Hash hashFromPledge(LHProtos.Pledge pledge) {
if (pledge.getPledgeDetails().hasOrigHash())
return Sha256Hash.wrap(pledge.getPledgeDetails().getOrigHash().toByteArray());
else
return Sha256Hash.of(pledge.toByteArray());
}
}