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 # Project specific files
admins.conf
clients.conf
*list.conf *list.conf
config.properties config.properties
/*.db /*.db

View File

@ -44,6 +44,7 @@ dependencies {
compile 'ch.dissem.jabit:jabit-networking:0.2.1-SNAPSHOT' 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-repositories:0.2.1-SNAPSHOT'
compile 'ch.dissem.jabit:jabit-security-bouncy: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' compile 'com.h2database:h2:1.4.187'

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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; package ch.dissem.bitmessage.server;
import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.BitmessageContext;
@ -7,7 +23,7 @@ import org.slf4j.LoggerFactory;
import java.util.TimerTask; import java.util.TimerTask;
/** /**
* Created by chrigu on 04.10.15. * @author Christian Basler
*/ */
public class CleanupJob extends TimerTask { public class CleanupJob extends TimerTask {
private static final Logger LOG = LoggerFactory.getLogger(CleanupJob.class); 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; package ch.dissem.bitmessage.server;
import ch.dissem.bitmessage.entity.BitmessageAddress; 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; package ch.dissem.bitmessage.server;
import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler; import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.ports.MemoryNodeRegistry; import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.repository.JdbcAddressRepository; import ch.dissem.bitmessage.repository.*;
import ch.dissem.bitmessage.repository.JdbcConfig;
import ch.dissem.bitmessage.repository.JdbcInventory;
import ch.dissem.bitmessage.repository.JdbcMessageRepository;
import ch.dissem.bitmessage.security.bc.BouncySecurity; 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.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -16,6 +32,9 @@ import springfox.documentation.spring.web.plugins.Docket;
import java.util.Set; import java.util.Set;
import static ch.dissem.bitmessage.server.Constants.*;
import static java.util.stream.Collectors.toSet;
@Configuration @Configuration
public class JabitServerConfig { public class JabitServerConfig {
public static final int SHORTLIST_SIZE = 5; public static final int SHORTLIST_SIZE = 5;
@ -27,28 +46,102 @@ public class JabitServerConfig {
@Value("${bitmessage.connection.limit}") @Value("${bitmessage.connection.limit}")
private int connectionLimit; 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 @Bean
public BitmessageContext bitmessageContext() { public BitmessageContext bitmessageContext() {
JdbcConfig config = new JdbcConfig("jdbc:h2:file:./jabit;AUTO_SERVER=TRUE", "sa", null);
return new BitmessageContext.Builder() return new BitmessageContext.Builder()
.addressRepo(new JdbcAddressRepository(config)) .addressRepo(addressRepo())
.inventory(new JdbcInventory(config)) .inventory(inventory())
.messageRepo(new JdbcMessageRepository(config)) .messageRepo(messageRepo())
.nodeRegistry(new MemoryNodeRegistry()) .nodeRegistry(nodeRegistry())
.networkHandler(new DefaultNetworkHandler()) .powRepo(proofOfWorkRepo())
.security(new BouncySecurity()) .networkHandler(networkHandler())
.listener(serverListener())
.customCommandHandler(commandHandler())
.security(security())
.port(port) .port(port)
.connectionLimit(connectionLimit) .connectionLimit(connectionLimit)
.connectionTTL(connectionTTL) .connectionTTL(connectionTTL)
.listener(plaintext -> {
})
.build(); .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 @Bean
public Set<String> whitelist() { public Set<String> whitelist() {
return Utils.readOrCreateList( return Utils.readOrCreateList(
"whitelist.conf", WHITELIST,
"# If there are any Bitmessage addresses in the whitelist, only those will be shown.\n" + "# 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" "# blacklist.conf will be ignored, but shortlist.conf will be applied to whitelisted addresses.\n"
); );
@ -57,7 +150,7 @@ public class JabitServerConfig {
@Bean @Bean
public Set<String> shortlist() { public Set<String> shortlist() {
return Utils.readOrCreateList( return Utils.readOrCreateList(
"shortlist.conf", SHORTLIST,
"# Broadcasts of these addresses will be restricted to the last " + SHORTLIST_SIZE + " entries.\n\n" + "# Broadcasts of these addresses will be restricted to the last " + SHORTLIST_SIZE + " entries.\n\n" +
"# Time Service:\n" + "# Time Service:\n" +
"BM-BcbRqcFFSQUUmXFKsPJgVQPSiFA3Xash\n\n" + "BM-BcbRqcFFSQUUmXFKsPJgVQPSiFA3Xash\n\n" +
@ -69,7 +162,7 @@ public class JabitServerConfig {
@Bean @Bean
public Set<String> blacklist() { public Set<String> blacklist() {
return Utils.readOrCreateList( return Utils.readOrCreateList(
"blacklist.conf", BLACKLIST,
"# Bitmessage addresses in this file are being ignored and their broadcasts won't be returned.\n" "# 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; package ch.dissem.bitmessage.server;
import ch.dissem.bitmessage.BitmessageContext; 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; package ch.dissem.bitmessage.server;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; 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 class Utils {
public static Set<String> readOrCreateList(String filename, String content) { 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) { public static Set<String> readList(File file) {
Set<String> result = new HashSet<>(); Set<String> result = new HashSet<>();
try { try {
@ -38,4 +75,11 @@ public class Utils {
} }
return result; 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,
);