Merge branch 'feature/server-pow' into develop

This commit is contained in:
Christian Basler 2016-01-11 10:52:12 +01:00
commit fa766cb6f1
14 changed files with 695 additions and 20 deletions

2
.gitignore vendored
View File

@ -1,4 +1,6 @@
# Project specific files
admins.conf
clients.conf
*list.conf
config.properties
/*.db

View File

@ -44,6 +44,7 @@ dependencies {
compile 'ch.dissem.jabit:jabit-networking:0.2.1-SNAPSHOT'
compile 'ch.dissem.jabit:jabit-repositories:0.2.1-SNAPSHOT'
compile 'ch.dissem.jabit:jabit-security-bouncy:0.2.1-SNAPSHOT'
compile 'ch.dissem.jabit:jabit-extensions:0.2.1-SNAPSHOT'
compile 'com.h2database:h2:1.4.187'

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip

View File

@ -1,3 +1,19 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.server;
import ch.dissem.bitmessage.BitmessageContext;
@ -7,7 +23,7 @@ import org.slf4j.LoggerFactory;
import java.util.TimerTask;
/**
* Created by chrigu on 04.10.15.
* @author Christian Basler
*/
public class CleanupJob extends TimerTask {
private static final Logger LOG = LoggerFactory.getLogger(CleanupJob.class);

View File

@ -0,0 +1,13 @@
package ch.dissem.bitmessage.server;
/**
* Created by chrigu on 22.11.15.
*/
public interface Constants {
String ADMIN_LIST = "admins.conf";
String CLIENT_LIST = "clients.conf";
String WHITELIST = "whitelist.conf";
String SHORTLIST = "shortlist.conf";
String BLACKLIST = "blacklist.conf";
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.server;
import ch.dissem.bitmessage.entity.BitmessageAddress;

View File

@ -1,13 +1,29 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.server;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.ports.MemoryNodeRegistry;
import ch.dissem.bitmessage.repository.JdbcAddressRepository;
import ch.dissem.bitmessage.repository.JdbcConfig;
import ch.dissem.bitmessage.repository.JdbcInventory;
import ch.dissem.bitmessage.repository.JdbcMessageRepository;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.repository.*;
import ch.dissem.bitmessage.security.bc.BouncySecurity;
import ch.dissem.bitmessage.server.repository.ServerProofOfWorkRepository;
import ch.dissem.bitmessage.utils.Singleton;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -16,6 +32,9 @@ import springfox.documentation.spring.web.plugins.Docket;
import java.util.Set;
import static ch.dissem.bitmessage.server.Constants.*;
import static java.util.stream.Collectors.toSet;
@Configuration
public class JabitServerConfig {
public static final int SHORTLIST_SIZE = 5;
@ -27,28 +46,102 @@ public class JabitServerConfig {
@Value("${bitmessage.connection.limit}")
private int connectionLimit;
@Bean
public JdbcConfig jdbcConfig() {
return new JdbcConfig("jdbc:h2:file:./jabit;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=10", "sa", null);
}
@Bean
public AddressRepository addressRepo() {
return new JdbcAddressRepository(jdbcConfig());
}
@Bean
public Inventory inventory() {
return new JdbcInventory(jdbcConfig());
}
@Bean
public MessageRepository messageRepo() {
return new JdbcMessageRepository(jdbcConfig());
}
@Bean
public ProofOfWorkRepository proofOfWorkRepo() {
return new JdbcProofOfWorkRepository(jdbcConfig());
}
@Bean
public NodeRegistry nodeRegistry() {
return new MemoryNodeRegistry();
}
@Bean
public NetworkHandler networkHandler() {
return new DefaultNetworkHandler();
}
@Bean
public Security security() {
BouncySecurity security = new BouncySecurity();
Singleton.initialize(security); // needed for admins and clients
return security;
}
@Bean
public BitmessageContext.Listener serverListener() {
return new ServerListener(admins(), clients(), whitelist(), shortlist(), blacklist());
}
@Bean
public ServerProofOfWorkRepository serverProofOfWorkRepository() {
return new ServerProofOfWorkRepository(jdbcConfig());
}
@Bean
public CustomCommandHandler commandHandler() {
return new ProofOfWorkRequestHandler(serverProofOfWorkRepository(), clients());
}
@Bean
public BitmessageContext bitmessageContext() {
JdbcConfig config = new JdbcConfig("jdbc:h2:file:./jabit;AUTO_SERVER=TRUE", "sa", null);
return new BitmessageContext.Builder()
.addressRepo(new JdbcAddressRepository(config))
.inventory(new JdbcInventory(config))
.messageRepo(new JdbcMessageRepository(config))
.nodeRegistry(new MemoryNodeRegistry())
.networkHandler(new DefaultNetworkHandler())
.security(new BouncySecurity())
.addressRepo(addressRepo())
.inventory(inventory())
.messageRepo(messageRepo())
.nodeRegistry(nodeRegistry())
.powRepo(proofOfWorkRepo())
.networkHandler(networkHandler())
.listener(serverListener())
.customCommandHandler(commandHandler())
.security(security())
.port(port)
.connectionLimit(connectionLimit)
.connectionTTL(connectionTTL)
.listener(plaintext -> {
})
.build();
}
@Bean
public Set<BitmessageAddress> admins() {
security();
return Utils.readOrCreateList(
ADMIN_LIST,
"# Admins can send commands to the server.\n"
).stream().map(BitmessageAddress::new).collect(toSet());
}
@Bean
public Set<BitmessageAddress> clients() {
security();
return Utils.readOrCreateList(
CLIENT_LIST,
"# Clients may send incomplete objects for proof of work.\n"
).stream().map(BitmessageAddress::new).collect(toSet());
}
@Bean
public Set<String> whitelist() {
return Utils.readOrCreateList(
"whitelist.conf",
WHITELIST,
"# If there are any Bitmessage addresses in the whitelist, only those will be shown.\n" +
"# blacklist.conf will be ignored, but shortlist.conf will be applied to whitelisted addresses.\n"
);
@ -57,7 +150,7 @@ public class JabitServerConfig {
@Bean
public Set<String> shortlist() {
return Utils.readOrCreateList(
"shortlist.conf",
SHORTLIST,
"# Broadcasts of these addresses will be restricted to the last " + SHORTLIST_SIZE + " entries.\n\n" +
"# Time Service:\n" +
"BM-BcbRqcFFSQUUmXFKsPJgVQPSiFA3Xash\n\n" +
@ -69,7 +162,7 @@ public class JabitServerConfig {
@Bean
public Set<String> blacklist() {
return Utils.readOrCreateList(
"blacklist.conf",
BLACKLIST,
"# Bitmessage addresses in this file are being ignored and their broadcasts won't be returned.\n"
);
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.server;
import ch.dissem.bitmessage.BitmessageContext;

View File

@ -0,0 +1,163 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.server;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.CustomMessage;
import ch.dissem.bitmessage.entity.MessagePayload;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.extensions.CryptoCustomMessage;
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest;
import ch.dissem.bitmessage.ports.CustomCommandHandler;
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
import ch.dissem.bitmessage.server.repository.ServerProofOfWorkRepository;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.stream.Collectors;
import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATING;
import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.COMPLETE;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
/**
* @author Christian Basler
*/
public class ProofOfWorkRequestHandler implements CustomCommandHandler, InternalContext.ContextHolder {
private final static Logger LOG = LoggerFactory.getLogger(ProofOfWorkRequestHandler.class);
private final List<byte[]> decryptionKeys;
private final ServerProofOfWorkRepository repo;
private BitmessageAddress serverIdentity;
private ProofOfWorkEngine engine;
private InternalContext context;
public ProofOfWorkRequestHandler(ServerProofOfWorkRepository repo, Collection<BitmessageAddress> clients) {
this.repo = repo;
decryptionKeys = clients.stream().map(BitmessageAddress::getPublicDecryptionKey).collect(Collectors.toList());
new Timer().schedule(new TimerTask() {
@Override
public void run() {
doMissingProofOfWork();
}
}, 15_000); // After 15 seconds
new Timer().schedule(new TimerTask() {
@Override
public void run() {
repo.cleanupTasks(7 * DAY);
}
}, 60_000, DAY * 1000); // First time after 1 minute, then daily
}
public void doMissingProofOfWork() {
List<ServerProofOfWorkRepository.Task> incompleteTasks = repo.getIncompleteTasks();
LOG.info("Doing POW for " + incompleteTasks.size() + " tasks.");
for (ServerProofOfWorkRepository.Task task : incompleteTasks) {
engine.calculateNonce(task.initialHash, task.target, repo::updateTask);
}
}
@Override
public MessagePayload handle(CustomMessage message) {
try {
CryptoCustomMessage<ProofOfWorkRequest> cryptoMessage = CryptoCustomMessage.read(message,
ProofOfWorkRequest::read);
ProofOfWorkRequest request = decrypt(cryptoMessage);
if (request == null) {
return CustomMessage.error(
"Unknown sender. Please ask the server's administrator to add you as a client. " +
"For this he'll need your identity."
);
}
switch (request.getRequest()) {
case CALCULATE:
if (!repo.hasTask(request.getInitialHash())) {
repo.storeTask(request);
// TODO: This is probably the place to do some book-keeping
// if we want to bill our customers.
engine.calculateNonce(request.getInitialHash(), request.getData(), repo::updateTask);
return new CryptoCustomMessage<>(
new ProofOfWorkRequest(getIdentity(), request.getInitialHash(), CALCULATING, new byte[0])
);
} else {
byte[] nonce = repo.getNonce(request);
CryptoCustomMessage<ProofOfWorkRequest> response;
if (nonce != null) {
response = new CryptoCustomMessage<>(
new ProofOfWorkRequest(getIdentity(), request.getInitialHash(), COMPLETE, nonce)
);
} else {
response = new CryptoCustomMessage<>(
new ProofOfWorkRequest(getIdentity(), request.getInitialHash(), CALCULATING, new byte[0])
);
}
response.signAndEncrypt(serverIdentity, request.getSender().getPubkey().getEncryptionKey());
return response;
}
}
return null;
} catch (IOException e) {
return CustomMessage.error(e.getMessage());
}
}
private BitmessageAddress getIdentity() {
if (serverIdentity == null) {
synchronized (this) {
if (serverIdentity == null) {
serverIdentity = context.getAddressRepository().getIdentities().stream().findFirst().orElseGet(() -> {
final BitmessageAddress identity = new BitmessageAddress(new PrivateKey(
false,
context.getStreams()[0],
context.getNetworkNonceTrialsPerByte(),
context.getNetworkExtraBytes()
));
context.getAddressRepository().save(identity);
return identity;
});
}
}
}
return serverIdentity;
}
private ProofOfWorkRequest decrypt(CryptoCustomMessage<ProofOfWorkRequest> cryptoMessage) {
for (byte[] key : decryptionKeys) {
try {
return cryptoMessage.decrypt(key);
} catch (DecryptionFailedException ignore) {
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return null;
}
@Override
public void setContext(InternalContext context) {
this.context = context;
this.engine = context.getProofOfWorkEngine();
}
}

View File

@ -0,0 +1,137 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.server;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Scanner;
import static ch.dissem.bitmessage.server.Constants.*;
/**
* @author Christian Basler
*/
public class ServerListener implements BitmessageContext.Listener {
private final static Logger LOG = LoggerFactory.getLogger(ServerListener.class);
private final Collection<BitmessageAddress> admins;
private final Collection<BitmessageAddress> clients;
private final Collection<String> whitelist;
private final Collection<String> shortlist;
private final Collection<String> blacklist;
public ServerListener(Collection<BitmessageAddress> admins,
Collection<BitmessageAddress> clients,
Collection<String> whitelist,
Collection<String> shortlist,
Collection<String> blacklist) {
this.admins = admins;
this.clients = clients;
this.whitelist = whitelist;
this.shortlist = shortlist;
this.blacklist = blacklist;
}
@Override
public void receive(Plaintext message) {
if (admins.contains(message.getFrom())) {
String[] command = message.getSubject().trim().toLowerCase().split("\\s+");
String data = message.getText();
if (command.length == 2) {
switch (command[1]) {
case "client":
case "clients":
updateUserList(CLIENT_LIST, clients, command[0], data);
break;
case "admin":
case "admins":
case "administrator":
case "administrators":
updateUserList(ADMIN_LIST, admins, command[0], data);
break;
case "whitelist":
updateList(WHITELIST, whitelist, command[0], data);
break;
case "shortlist":
updateList(SHORTLIST, shortlist, command[0], data);
break;
case "blacklist":
updateList(BLACKLIST, blacklist, command[0], data);
break;
default:
LOG.trace("ignoring unknown command " + message.getSubject());
}
}
}
}
private void updateUserList(String file, Collection<BitmessageAddress> list, String command, String data) {
switch (command) {
case "set":
list.clear();
case "add":
Scanner scanner = new Scanner(data);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
try {
list.add(new BitmessageAddress(line));
} catch (Exception e) {
LOG.info(command + " " + file + ": ignoring line: " + line);
}
}
Utils.saveList(file, list.stream().map(BitmessageAddress::getAddress));
break;
case "remove":
list.removeIf(address -> data.contains(address.getAddress()));
Utils.saveList(file, list.stream().map(BitmessageAddress::getAddress));
break;
default:
LOG.info("unknown command " + command + " on list " + file);
}
}
private void updateList(String file, Collection<String> list, String command, String data) {
switch (command) {
case "set":
list.clear();
case "add":
Scanner scanner = new Scanner(data);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
try {
list.add(new BitmessageAddress(line).getAddress());
} catch (Exception e) {
LOG.info(command + " " + file + ": ignoring line: " + line);
}
}
Utils.saveList(file, list.stream());
break;
case "remove":
list.removeIf(data::contains);
Utils.saveList(file, list.stream());
break;
default:
LOG.info("unknown command " + command + " on list " + file);
}
}
}

View File

@ -1,10 +1,29 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.server;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
import java.util.stream.Stream;
public class Utils {
public static Set<String> readOrCreateList(String filename, String content) {
@ -23,6 +42,24 @@ public class Utils {
}
}
public static void saveList(String filename, Stream<String> content) {
try {
File file = new File(filename);
try (FileWriter fw = new FileWriter(file)) {
content.forEach(l -> {
try {
fw.write(l);
fw.write(System.lineSeparator());
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static Set<String> readList(File file) {
Set<String> result = new HashSet<>();
try {
@ -38,4 +75,11 @@ public class Utils {
}
return result;
}
public static boolean zero(byte[] nonce) {
for (byte b : nonce) {
if (b != 0) return false;
}
return true;
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.server.entities;
/**
* @author Christian Basler
*/
public class Update<T> {
public final T oldValue;
public final T newValue;
public Update(T oldValue, T newValue) {
this.oldValue = oldValue;
this.newValue = newValue;
}
}

View File

@ -0,0 +1,137 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.server.repository;
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest;
import ch.dissem.bitmessage.repository.JdbcConfig;
import ch.dissem.bitmessage.repository.JdbcHelper;
import ch.dissem.bitmessage.utils.UnixTime;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
/**
* @author Christian Basler
*/
public class ServerProofOfWorkRepository extends JdbcHelper {
public ServerProofOfWorkRepository(JdbcConfig config) {
super(config);
}
/**
* client (can be removed once the new IV is returned)
* IV (without nonce)
* IV (with nonce, can be removed once the new IV is returned)
* status: calculating, finished, confirmed
* data (can be removed once POW calculation is done)
*/
public void storeTask(ProofOfWorkRequest request) {
try (Connection connection = config.getConnection()) {
PreparedStatement ps = connection.prepareStatement(
"INSERT INTO ProofOfWorkTask (initial_hash, client, target, timestamp) VALUES (?, ?, ?, ?)");
ps.setBytes(1, request.getInitialHash());
ps.setString(2, request.getSender().getAddress());
ps.setBytes(3, request.getData());
ps.setLong(4, UnixTime.now());
ps.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public void updateTask(byte[] initalHash, byte[] nonce) {
try (Connection connection = config.getConnection()) {
PreparedStatement ps = connection.prepareStatement(
"UPDATE ProofOfWorkTask SET nonce = ? WHERE initial_hash = ?");
ps.setBytes(1, nonce);
ps.setBytes(2, initalHash);
ps.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public byte[] getNonce(ProofOfWorkRequest request) {
try (Connection connection = config.getConnection()) {
PreparedStatement ps = connection.prepareStatement("SELECT nonce FROM ProofOfWorkTask WHERE initial_hash = ?");
ps.setBytes(1, request.getInitialHash());
ResultSet rs = ps.executeQuery();
if (rs.next()) {
return rs.getBytes(1);
} else {
return null;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public boolean hasTask(byte[] initialHash) {
try (Connection connection = config.getConnection()) {
PreparedStatement ps = connection.prepareStatement("SELECT count(1) FROM ProofOfWorkTask WHERE initial_hash = ?");
ps.setBytes(1, initialHash);
ResultSet rs = ps.executeQuery();
rs.next();
return rs.getInt(1) > 0;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public List<Task> getIncompleteTasks() {
try (Connection connection = config.getConnection()) {
List<Task> result = new LinkedList<>();
ResultSet rs = connection.createStatement().executeQuery(
"SELECT initial_hash, target FROM ProofOfWorkTask WHERE nonce IS NULL");
while (rs.next()) {
result.add(new Task(
rs.getBytes(1),
rs.getBytes(2)
));
}
return result;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public void cleanupTasks(long ageInSeconds) {
try (Connection connection = config.getConnection()) {
PreparedStatement ps = connection.prepareStatement(
"DELETE FROM ProofOfWorkTask WHERE timestamp < ?");
ps.setLong(1, UnixTime.now(-ageInSeconds));
ps.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static class Task {
public final byte[] initialHash;
public final byte[] target;
private Task(byte[] initialHash, byte[] target) {
this.initialHash = initialHash;
this.target = target;
}
}
}

View File

@ -0,0 +1,7 @@
CREATE TABLE ProofOfWorkTask (
initial_hash BINARY(64) NOT NULL PRIMARY KEY,
client VARCHAR(40) NOT NULL,
target BINARY(32),
nonce BINARY(8),
timestamp BIGINT NOT NULL,
);