From 5c4b976417acaf2f675bfb816fcc7807df943e0e Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Sun, 22 Nov 2015 12:31:51 +0100 Subject: [PATCH 1/5] Server POW and some admin functions (needs some testing) --- gradle/wrapper/gradle-wrapper.properties | 2 +- .../dissem/bitmessage/server/Constants.java | 13 ++ .../bitmessage/server/JabitServerConfig.java | 29 +++- .../server/ServerObjectListener.java | 160 ++++++++++++++++++ .../ch/dissem/bitmessage/server/Utils.java | 30 +++- 5 files changed, 227 insertions(+), 7 deletions(-) create mode 100644 src/main/java/ch/dissem/bitmessage/server/Constants.java create mode 100644 src/main/java/ch/dissem/bitmessage/server/ServerObjectListener.java diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 66e6c70..94f382d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/src/main/java/ch/dissem/bitmessage/server/Constants.java b/src/main/java/ch/dissem/bitmessage/server/Constants.java new file mode 100644 index 0000000..a25f019 --- /dev/null +++ b/src/main/java/ch/dissem/bitmessage/server/Constants.java @@ -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"; +} diff --git a/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java b/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java index 6dcd8f6..82ee924 100644 --- a/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java +++ b/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java @@ -1,6 +1,7 @@ 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; @@ -16,6 +17,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; @@ -36,19 +40,34 @@ public class JabitServerConfig { .messageRepo(new JdbcMessageRepository(config)) .nodeRegistry(new MemoryNodeRegistry()) .networkHandler(new DefaultNetworkHandler()) + .objectListener(new ServerObjectListener(admins(), clients(), whitelist(), shortlist(), blacklist())) .security(new BouncySecurity()) .port(port) .connectionLimit(connectionLimit) .connectionTTL(connectionTTL) - .listener(plaintext -> { - }) .build(); } + @Bean + public Set admins() { + return Utils.readOrCreateList( + ADMIN_LIST, + "# Admins can send commands to the server.\n" + ).stream().map(BitmessageAddress::new).collect(toSet()); + } + + @Bean + public Set clients() { + return Utils.readOrCreateList( + CLIENT_LIST, + "# Clients may send incomplete objects for proof of work.\n" + ).stream().map(BitmessageAddress::new).collect(toSet()); + } + @Bean public Set 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 +76,7 @@ public class JabitServerConfig { @Bean public Set 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 +88,7 @@ public class JabitServerConfig { @Bean public Set blacklist() { return Utils.readOrCreateList( - "blacklist.conf", + BLACKLIST, "# Bitmessage addresses in this file are being ignored and their broadcasts won't be returned.\n" ); } diff --git a/src/main/java/ch/dissem/bitmessage/server/ServerObjectListener.java b/src/main/java/ch/dissem/bitmessage/server/ServerObjectListener.java new file mode 100644 index 0000000..83a0c99 --- /dev/null +++ b/src/main/java/ch/dissem/bitmessage/server/ServerObjectListener.java @@ -0,0 +1,160 @@ +package ch.dissem.bitmessage.server; + +import ch.dissem.bitmessage.DefaultObjectListener; +import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.entity.ObjectMessage; +import ch.dissem.bitmessage.entity.Plaintext; +import ch.dissem.bitmessage.entity.payload.Broadcast; +import ch.dissem.bitmessage.exception.DecryptionFailedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.Scanner; + +import static ch.dissem.bitmessage.factory.Factory.getObjectMessage; +import static ch.dissem.bitmessage.server.Constants.*; +import static ch.dissem.bitmessage.server.Utils.zero; +import static ch.dissem.bitmessage.utils.Singleton.security; + +/** + * @author Christian Basler + */ +public class ServerObjectListener extends DefaultObjectListener { + private final static Logger LOG = LoggerFactory.getLogger(ServerObjectListener.class); + + private final Collection admins; + private final Collection clients; + + private final Collection whitelist; + private final Collection shortlist; + private final Collection blacklist; + + public ServerObjectListener(Collection admins, Collection clients, Collection whitelist, Collection shortlist, Collection blacklist) { + super(p -> { + }); + this.admins = admins; + this.clients = clients; + this.whitelist = whitelist; + this.shortlist = shortlist; + this.blacklist = blacklist; + } + + @Override + protected void receive(ObjectMessage object, Broadcast broadcast) throws IOException { + processCommands(broadcast); + if (zero(object.getNonce())) { + calculateNonceForClient(object, broadcast); + } else { + super.receive(object, broadcast); + } + } + + private void processCommands(Broadcast broadcast) throws IOException { + for (BitmessageAddress admin : admins) { + try { + broadcast.decrypt(admin); + Plaintext message = broadcast.getPlaintext(); + 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(WHITELIST, shortlist, command[0], data); + break; + case "blacklist": + updateList(WHITELIST, blacklist, command[0], data); + break; + default: + LOG.trace("ignoring unknown command " + message.getSubject()); + } + } + } catch (DecryptionFailedException ignore) { + } + } + } + + private void calculateNonceForClient(ObjectMessage object, Broadcast broadcast) throws IOException { + for (BitmessageAddress client : clients) { + try { + broadcast.decrypt(client); + byte[] message = broadcast.getPlaintext().getMessage(); + final ObjectMessage toRelay = getObjectMessage(3, new ByteArrayInputStream(message), message.length); + security().doProofOfWork(toRelay, + ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes(), + (nonce) -> { + toRelay.setNonce(nonce); + ctx.getInventory().storeObject(object); + ctx.getNetworkHandler().offer(object.getInventoryVector()); + } + ); + } catch (DecryptionFailedException ignore) { + } + } + } + + private void updateUserList(String file, Collection 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 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); + } + } +} diff --git a/src/main/java/ch/dissem/bitmessage/server/Utils.java b/src/main/java/ch/dissem/bitmessage/server/Utils.java index 2f9a8d5..d9bcc49 100644 --- a/src/main/java/ch/dissem/bitmessage/server/Utils.java +++ b/src/main/java/ch/dissem/bitmessage/server/Utils.java @@ -4,7 +4,10 @@ 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 readOrCreateList(String filename, String content) { @@ -23,6 +26,24 @@ public class Utils { } } + public static void saveList(String filename, Stream 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 readList(File file) { Set result = new HashSet<>(); try { @@ -38,4 +59,11 @@ public class Utils { } return result; } + + public static boolean zero(byte[] nonce) { + for (byte b : nonce) { + if (b != 0) return false; + } + return true; + } } From 48ddfbbbd0bc2e46966a9f6e9a76959c62790718 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Mon, 23 Nov 2015 17:40:47 +0100 Subject: [PATCH 2/5] Added license information --- .../dissem/bitmessage/server/CleanupJob.java | 18 +++++++++++++++++- .../ch/dissem/bitmessage/server/Converter.java | 16 ++++++++++++++++ .../bitmessage/server/JabitServerConfig.java | 16 ++++++++++++++++ .../server/JabitServerController.java | 16 ++++++++++++++++ .../server/ServerObjectListener.java | 17 +++++++++++++++++ .../ch/dissem/bitmessage/server/Utils.java | 16 ++++++++++++++++ 6 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/dissem/bitmessage/server/CleanupJob.java b/src/main/java/ch/dissem/bitmessage/server/CleanupJob.java index 8b93cea..2294d42 100644 --- a/src/main/java/ch/dissem/bitmessage/server/CleanupJob.java +++ b/src/main/java/ch/dissem/bitmessage/server/CleanupJob.java @@ -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); diff --git a/src/main/java/ch/dissem/bitmessage/server/Converter.java b/src/main/java/ch/dissem/bitmessage/server/Converter.java index 0e387c4..364831a 100644 --- a/src/main/java/ch/dissem/bitmessage/server/Converter.java +++ b/src/main/java/ch/dissem/bitmessage/server/Converter.java @@ -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; diff --git a/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java b/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java index 82ee924..d0959f2 100644 --- a/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java +++ b/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java @@ -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; diff --git a/src/main/java/ch/dissem/bitmessage/server/JabitServerController.java b/src/main/java/ch/dissem/bitmessage/server/JabitServerController.java index 9b458a9..6607955 100644 --- a/src/main/java/ch/dissem/bitmessage/server/JabitServerController.java +++ b/src/main/java/ch/dissem/bitmessage/server/JabitServerController.java @@ -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; diff --git a/src/main/java/ch/dissem/bitmessage/server/ServerObjectListener.java b/src/main/java/ch/dissem/bitmessage/server/ServerObjectListener.java index 83a0c99..656a8f5 100644 --- a/src/main/java/ch/dissem/bitmessage/server/ServerObjectListener.java +++ b/src/main/java/ch/dissem/bitmessage/server/ServerObjectListener.java @@ -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.DefaultObjectListener; @@ -90,6 +106,7 @@ public class ServerObjectListener extends DefaultObjectListener { } private void calculateNonceForClient(ObjectMessage object, Broadcast broadcast) throws IOException { + // TODO: prevent doing calculation twice for (BitmessageAddress client : clients) { try { broadcast.decrypt(client); diff --git a/src/main/java/ch/dissem/bitmessage/server/Utils.java b/src/main/java/ch/dissem/bitmessage/server/Utils.java index d9bcc49..50d6a5f 100644 --- a/src/main/java/ch/dissem/bitmessage/server/Utils.java +++ b/src/main/java/ch/dissem/bitmessage/server/Utils.java @@ -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 java.io.File; From 891294f267d82881152915081a3973f579eb1d88 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Sat, 28 Nov 2015 20:26:13 +0100 Subject: [PATCH 3/5] Server POW (work in progress, major refactoring) --- .gitignore | 2 + build.gradle | 1 + .../bitmessage/server/JabitServerConfig.java | 2 +- .../server/ProofOfWorkRequestHandler.java | 84 +++++++++ .../bitmessage/server/ServerListener.java | 137 ++++++++++++++ .../server/ServerObjectListener.java | 177 ------------------ .../bitmessage/server/entities/Update.java | 30 +++ .../repository/ProofOfWorkRepository.java | 124 ++++++++++++ .../db/migration/V1.0_ProofOfWorkTask.sql | 7 + 9 files changed, 386 insertions(+), 178 deletions(-) create mode 100644 src/main/java/ch/dissem/bitmessage/server/ProofOfWorkRequestHandler.java create mode 100644 src/main/java/ch/dissem/bitmessage/server/ServerListener.java delete mode 100644 src/main/java/ch/dissem/bitmessage/server/ServerObjectListener.java create mode 100644 src/main/java/ch/dissem/bitmessage/server/entities/Update.java create mode 100644 src/main/java/ch/dissem/bitmessage/server/repository/ProofOfWorkRepository.java create mode 100644 src/main/resources/db/migration/V1.0_ProofOfWorkTask.sql diff --git a/.gitignore b/.gitignore index 57462fb..0d42f5d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ # Project specific files +admins.conf +clients.conf *list.conf config.properties /*.db diff --git a/build.gradle b/build.gradle index 7f6b199..0fe15a1 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java b/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java index d0959f2..b3b314e 100644 --- a/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java +++ b/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java @@ -56,7 +56,7 @@ public class JabitServerConfig { .messageRepo(new JdbcMessageRepository(config)) .nodeRegistry(new MemoryNodeRegistry()) .networkHandler(new DefaultNetworkHandler()) - .objectListener(new ServerObjectListener(admins(), clients(), whitelist(), shortlist(), blacklist())) + .listener(new ServerListener(admins(), clients(), whitelist(), shortlist(), blacklist())) .security(new BouncySecurity()) .port(port) .connectionLimit(connectionLimit) diff --git a/src/main/java/ch/dissem/bitmessage/server/ProofOfWorkRequestHandler.java b/src/main/java/ch/dissem/bitmessage/server/ProofOfWorkRequestHandler.java new file mode 100644 index 0000000..d5d95bd --- /dev/null +++ b/src/main/java/ch/dissem/bitmessage/server/ProofOfWorkRequestHandler.java @@ -0,0 +1,84 @@ +/* + * 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.CustomMessage; +import ch.dissem.bitmessage.entity.MessagePayload; +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.ProofOfWorkRepository; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Christian Basler + */ +public class ProofOfWorkRequestHandler implements CustomCommandHandler { + private final List decryptionKeys = new ArrayList<>(); + private ProofOfWorkRepository repo; + private ProofOfWorkEngine engine; + + @Override + public MessagePayload handle(CustomMessage message) { + try { + CryptoCustomMessage cryptoMessage = CryptoCustomMessage.read(message.getData(), + (sender, in) -> ProofOfWorkRequest.read(sender, in)); + ProofOfWorkRequest request = decrypt(cryptoMessage); + if (request == null) return error("Unknown encryption key."); + switch (request.getRequest()) { + case CALCULATE: + repo.storeTask(request); // FIXME + engine.calculateNonce(request.getInitialHash(), request.getData(), nonce -> { + + }); + } + return null; + } catch (IOException e) { + return error(e.getMessage()); + } + } + + private MessagePayload error(String message) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write("ERROR\n".getBytes("UTF-8")); + out.write(message.getBytes("UTF-8")); + return new CustomMessage(out.toByteArray()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private ProofOfWorkRequest decrypt(CryptoCustomMessage cryptoMessage) { + for (byte[] key : decryptionKeys) { + try { + return cryptoMessage.decrypt(key); + } catch (DecryptionFailedException ignore) { + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return null; + } + +} diff --git a/src/main/java/ch/dissem/bitmessage/server/ServerListener.java b/src/main/java/ch/dissem/bitmessage/server/ServerListener.java new file mode 100644 index 0000000..228c6bc --- /dev/null +++ b/src/main/java/ch/dissem/bitmessage/server/ServerListener.java @@ -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 admins; + private final Collection clients; + + private final Collection whitelist; + private final Collection shortlist; + private final Collection blacklist; + + public ServerListener(Collection admins, + Collection clients, + Collection whitelist, + Collection shortlist, + Collection 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 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 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); + } + } +} diff --git a/src/main/java/ch/dissem/bitmessage/server/ServerObjectListener.java b/src/main/java/ch/dissem/bitmessage/server/ServerObjectListener.java deleted file mode 100644 index 656a8f5..0000000 --- a/src/main/java/ch/dissem/bitmessage/server/ServerObjectListener.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * 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.DefaultObjectListener; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.payload.Broadcast; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.Collection; -import java.util.Scanner; - -import static ch.dissem.bitmessage.factory.Factory.getObjectMessage; -import static ch.dissem.bitmessage.server.Constants.*; -import static ch.dissem.bitmessage.server.Utils.zero; -import static ch.dissem.bitmessage.utils.Singleton.security; - -/** - * @author Christian Basler - */ -public class ServerObjectListener extends DefaultObjectListener { - private final static Logger LOG = LoggerFactory.getLogger(ServerObjectListener.class); - - private final Collection admins; - private final Collection clients; - - private final Collection whitelist; - private final Collection shortlist; - private final Collection blacklist; - - public ServerObjectListener(Collection admins, Collection clients, Collection whitelist, Collection shortlist, Collection blacklist) { - super(p -> { - }); - this.admins = admins; - this.clients = clients; - this.whitelist = whitelist; - this.shortlist = shortlist; - this.blacklist = blacklist; - } - - @Override - protected void receive(ObjectMessage object, Broadcast broadcast) throws IOException { - processCommands(broadcast); - if (zero(object.getNonce())) { - calculateNonceForClient(object, broadcast); - } else { - super.receive(object, broadcast); - } - } - - private void processCommands(Broadcast broadcast) throws IOException { - for (BitmessageAddress admin : admins) { - try { - broadcast.decrypt(admin); - Plaintext message = broadcast.getPlaintext(); - 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(WHITELIST, shortlist, command[0], data); - break; - case "blacklist": - updateList(WHITELIST, blacklist, command[0], data); - break; - default: - LOG.trace("ignoring unknown command " + message.getSubject()); - } - } - } catch (DecryptionFailedException ignore) { - } - } - } - - private void calculateNonceForClient(ObjectMessage object, Broadcast broadcast) throws IOException { - // TODO: prevent doing calculation twice - for (BitmessageAddress client : clients) { - try { - broadcast.decrypt(client); - byte[] message = broadcast.getPlaintext().getMessage(); - final ObjectMessage toRelay = getObjectMessage(3, new ByteArrayInputStream(message), message.length); - security().doProofOfWork(toRelay, - ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes(), - (nonce) -> { - toRelay.setNonce(nonce); - ctx.getInventory().storeObject(object); - ctx.getNetworkHandler().offer(object.getInventoryVector()); - } - ); - } catch (DecryptionFailedException ignore) { - } - } - } - - private void updateUserList(String file, Collection 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 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); - } - } -} diff --git a/src/main/java/ch/dissem/bitmessage/server/entities/Update.java b/src/main/java/ch/dissem/bitmessage/server/entities/Update.java new file mode 100644 index 0000000..3aebada --- /dev/null +++ b/src/main/java/ch/dissem/bitmessage/server/entities/Update.java @@ -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 { + public final T oldValue; + public final T newValue; + + public Update(T oldValue, T newValue) { + this.oldValue = oldValue; + this.newValue = newValue; + } +} diff --git a/src/main/java/ch/dissem/bitmessage/server/repository/ProofOfWorkRepository.java b/src/main/java/ch/dissem/bitmessage/server/repository/ProofOfWorkRepository.java new file mode 100644 index 0000000..5db2215 --- /dev/null +++ b/src/main/java/ch/dissem/bitmessage/server/repository/ProofOfWorkRepository.java @@ -0,0 +1,124 @@ +/* + * 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.InternalContext; +import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.entity.valueobject.InventoryVector; +import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest; +import ch.dissem.bitmessage.repository.JdbcConfig; +import ch.dissem.bitmessage.repository.JdbcHelper; +import ch.dissem.bitmessage.server.entities.Update; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Stream; + +import static ch.dissem.bitmessage.server.repository.ProofOfWorkRepository.Status.*; + +/** + * @author Christian Basler + */ +public class ProofOfWorkRepository extends JdbcHelper implements InternalContext.ContextHolder { + private static final Logger LOG = LoggerFactory.getLogger(ProofOfWorkRepository.class); + + private InternalContext context; + + protected ProofOfWorkRepository(JdbcConfig config) { + super(config); + } + + @Override + public void setContext(InternalContext context) { + this.context = context; + } + + /** + * 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, status) VALUES (?, ?, ?, ?)"); + ps.setBytes(1, request.getInitialHash()); + ps.setString(2, request.getClient().getAddress()); + ps.setBytes(3, request.getData()); + ps.setString(4, CALCULATING.name()); + ps.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public void updateTask(InventoryVector temporaryIV, InventoryVector newIV) { + try (Connection connection = config.getConnection()) { + PreparedStatement ps = connection.prepareStatement( + "UPDATE ProofOfWorkTask SET IV = ?, status = ?, data = NULL WHERE temporaryIV = ?"); + ps.setBytes(1, newIV.getHash()); + ps.setString(2, FINISHED.name()); + ps.setBytes(3, temporaryIV.getHash()); + ps.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public Collection> getUnconfirmed(BitmessageAddress client) { + List> result = new LinkedList<>(); + try (Connection connection = config.getConnection()) { + PreparedStatement ps = connection.prepareStatement("SELECT temporaryIV, IV FROM ProofOfWorkTask WHERE client = ? AND status = ?"); + ps.setString(1, client.getAddress()); + ps.setString(2, FINISHED.name()); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + InventoryVector temporaryIV = new InventoryVector(rs.getBytes(1)); + InventoryVector iv = new InventoryVector(rs.getBytes(2)); + result.add(new Update<>(temporaryIV, iv)); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return result; + } + + public void confirm(Stream unconfirmed) { + try (Connection connection = config.getConnection()) { + PreparedStatement ps = connection.prepareStatement( + "UPDATE ProofOfWorkTask SET status = ?, IV = NULL, client = NULL WHERE IV = ANY(?)"); + ps.setString(1, CONFIRMED.name()); + ps.setArray(2, connection.createArrayOf("BINARY", unconfirmed.map(InventoryVector::getHash).toArray())); + ps.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public enum Status { + CALCULATING, FINISHED, CONFIRMED + } +} diff --git a/src/main/resources/db/migration/V1.0_ProofOfWorkTask.sql b/src/main/resources/db/migration/V1.0_ProofOfWorkTask.sql new file mode 100644 index 0000000..40d3135 --- /dev/null +++ b/src/main/resources/db/migration/V1.0_ProofOfWorkTask.sql @@ -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), + status VARCHAR(20), +); \ No newline at end of file From aafa4a4a1145c6ca20195005ab3d02084535c055 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Mon, 21 Dec 2015 15:21:30 +0100 Subject: [PATCH 4/5] Server POW should now work --- .../bitmessage/server/JabitServerConfig.java | 83 +++++++++++-- .../server/ProofOfWorkRequestHandler.java | 114 ++++++++++++++---- ....java => ServerProofOfWorkRepository.java} | 92 +++++++------- ...rkTask.sql => V1.2.1__ProofOfWorkTask.sql} | 0 4 files changed, 208 insertions(+), 81 deletions(-) rename src/main/java/ch/dissem/bitmessage/server/repository/{ProofOfWorkRepository.java => ServerProofOfWorkRepository.java} (51%) rename src/main/resources/db/migration/{V1.0_ProofOfWorkTask.sql => V1.2.1__ProofOfWorkTask.sql} (100%) diff --git a/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java b/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java index b3b314e..205154c 100644 --- a/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java +++ b/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java @@ -19,12 +19,11 @@ 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; @@ -47,17 +46,74 @@ 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()) - .listener(new ServerListener(admins(), clients(), whitelist(), shortlist(), blacklist())) - .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) @@ -74,6 +130,7 @@ public class JabitServerConfig { @Bean public Set clients() { + security(); return Utils.readOrCreateList( CLIENT_LIST, "# Clients may send incomplete objects for proof of work.\n" diff --git a/src/main/java/ch/dissem/bitmessage/server/ProofOfWorkRequestHandler.java b/src/main/java/ch/dissem/bitmessage/server/ProofOfWorkRequestHandler.java index d5d95bd..eafe85c 100644 --- a/src/main/java/ch/dissem/bitmessage/server/ProofOfWorkRequestHandler.java +++ b/src/main/java/ch/dissem/bitmessage/server/ProofOfWorkRequestHandler.java @@ -16,57 +16,122 @@ 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.ProofOfWorkRepository; +import ch.dissem.bitmessage.server.repository.ServerProofOfWorkRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.util.ArrayList; +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; /** * @author Christian Basler */ -public class ProofOfWorkRequestHandler implements CustomCommandHandler { - private final List decryptionKeys = new ArrayList<>(); - private ProofOfWorkRepository repo; +public class ProofOfWorkRequestHandler implements CustomCommandHandler, InternalContext.ContextHolder { + private final static Logger LOG = LoggerFactory.getLogger(ProofOfWorkRequestHandler.class); + + private final List decryptionKeys; + private final ServerProofOfWorkRepository repo; + private BitmessageAddress serverIdentity; private ProofOfWorkEngine engine; + private InternalContext context; + + public ProofOfWorkRequestHandler(ServerProofOfWorkRepository repo, Collection 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 + } + + public void doMissingProofOfWork() { + List 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 cryptoMessage = CryptoCustomMessage.read(message.getData(), - (sender, in) -> ProofOfWorkRequest.read(sender, in)); + CryptoCustomMessage cryptoMessage = CryptoCustomMessage.read(message, + ProofOfWorkRequest::read); ProofOfWorkRequest request = decrypt(cryptoMessage); - if (request == null) return error("Unknown encryption key."); + 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: - repo.storeTask(request); // FIXME - engine.calculateNonce(request.getInitialHash(), request.getData(), nonce -> { - - }); + 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 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 error(e.getMessage()); + return CustomMessage.error(e.getMessage()); } } - private MessagePayload error(String message) { - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - out.write("ERROR\n".getBytes("UTF-8")); - out.write(message.getBytes("UTF-8")); - return new CustomMessage(out.toByteArray()); - } catch (Exception e) { - throw new RuntimeException(e); + 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 cryptoMessage) { @@ -81,4 +146,9 @@ public class ProofOfWorkRequestHandler implements CustomCommandHandler { return null; } + @Override + public void setContext(InternalContext context) { + this.context = context; + this.engine = context.getProofOfWorkEngine(); + } } diff --git a/src/main/java/ch/dissem/bitmessage/server/repository/ProofOfWorkRepository.java b/src/main/java/ch/dissem/bitmessage/server/repository/ServerProofOfWorkRepository.java similarity index 51% rename from src/main/java/ch/dissem/bitmessage/server/repository/ProofOfWorkRepository.java rename to src/main/java/ch/dissem/bitmessage/server/repository/ServerProofOfWorkRepository.java index 5db2215..e37e200 100644 --- a/src/main/java/ch/dissem/bitmessage/server/repository/ProofOfWorkRepository.java +++ b/src/main/java/ch/dissem/bitmessage/server/repository/ServerProofOfWorkRepository.java @@ -16,44 +16,26 @@ package ch.dissem.bitmessage.server.repository; -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest; import ch.dissem.bitmessage.repository.JdbcConfig; import ch.dissem.bitmessage.repository.JdbcHelper; -import ch.dissem.bitmessage.server.entities.Update; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.Collection; import java.util.LinkedList; import java.util.List; -import java.util.stream.Stream; - -import static ch.dissem.bitmessage.server.repository.ProofOfWorkRepository.Status.*; /** * @author Christian Basler */ -public class ProofOfWorkRepository extends JdbcHelper implements InternalContext.ContextHolder { - private static final Logger LOG = LoggerFactory.getLogger(ProofOfWorkRepository.class); +public class ServerProofOfWorkRepository extends JdbcHelper { - private InternalContext context; - - protected ProofOfWorkRepository(JdbcConfig config) { + public ServerProofOfWorkRepository(JdbcConfig config) { super(config); } - @Override - public void setContext(InternalContext context) { - this.context = context; - } - /** * client (can be removed once the new IV is returned) * IV (without nonce) @@ -64,61 +46,79 @@ public class ProofOfWorkRepository extends JdbcHelper implements InternalContext public void storeTask(ProofOfWorkRequest request) { try (Connection connection = config.getConnection()) { PreparedStatement ps = connection.prepareStatement( - "INSERT INTO ProofOfWorkTask (initial_hash, client, target, status) VALUES (?, ?, ?, ?)"); + "INSERT INTO ProofOfWorkTask (initial_hash, client, target) VALUES (?, ?, ?)"); ps.setBytes(1, request.getInitialHash()); - ps.setString(2, request.getClient().getAddress()); + ps.setString(2, request.getSender().getAddress()); ps.setBytes(3, request.getData()); - ps.setString(4, CALCULATING.name()); ps.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); } } - public void updateTask(InventoryVector temporaryIV, InventoryVector newIV) { + public void updateTask(byte[] initalHash, byte[] nonce) { try (Connection connection = config.getConnection()) { PreparedStatement ps = connection.prepareStatement( - "UPDATE ProofOfWorkTask SET IV = ?, status = ?, data = NULL WHERE temporaryIV = ?"); - ps.setBytes(1, newIV.getHash()); - ps.setString(2, FINISHED.name()); - ps.setBytes(3, temporaryIV.getHash()); + "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 Collection> getUnconfirmed(BitmessageAddress client) { - List> result = new LinkedList<>(); + public byte[] getNonce(ProofOfWorkRequest request) { try (Connection connection = config.getConnection()) { - PreparedStatement ps = connection.prepareStatement("SELECT temporaryIV, IV FROM ProofOfWorkTask WHERE client = ? AND status = ?"); - ps.setString(1, client.getAddress()); - ps.setString(2, FINISHED.name()); + PreparedStatement ps = connection.prepareStatement("SELECT nonce FROM ProofOfWorkTask WHERE initial_hash = ?"); + ps.setBytes(1, request.getInitialHash()); ResultSet rs = ps.executeQuery(); - while (rs.next()) { - InventoryVector temporaryIV = new InventoryVector(rs.getBytes(1)); - InventoryVector iv = new InventoryVector(rs.getBytes(2)); - result.add(new Update<>(temporaryIV, iv)); + if (rs.next()) { + return rs.getBytes(1); + } else { + return null; } } catch (SQLException e) { throw new RuntimeException(e); } - return result; } - public void confirm(Stream unconfirmed) { + public boolean hasTask(byte[] initialHash) { try (Connection connection = config.getConnection()) { - PreparedStatement ps = connection.prepareStatement( - "UPDATE ProofOfWorkTask SET status = ?, IV = NULL, client = NULL WHERE IV = ANY(?)"); - ps.setString(1, CONFIRMED.name()); - ps.setArray(2, connection.createArrayOf("BINARY", unconfirmed.map(InventoryVector::getHash).toArray())); - ps.executeUpdate(); + 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 enum Status { - CALCULATING, FINISHED, CONFIRMED + public List getIncompleteTasks() { + try (Connection connection = config.getConnection()) { + List 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 static class Task { + public final byte[] initialHash; + public final byte[] target; + + private Task(byte[] initialHash, byte[] target) { + this.initialHash = initialHash; + this.target = target; + } } } diff --git a/src/main/resources/db/migration/V1.0_ProofOfWorkTask.sql b/src/main/resources/db/migration/V1.2.1__ProofOfWorkTask.sql similarity index 100% rename from src/main/resources/db/migration/V1.0_ProofOfWorkTask.sql rename to src/main/resources/db/migration/V1.2.1__ProofOfWorkTask.sql From 634bcffa9dc95e10c837dc2dbb64917dbd306191 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Tue, 29 Dec 2015 13:16:50 +0100 Subject: [PATCH 5/5] Some fixes, server POW is working now --- .../bitmessage/server/JabitServerConfig.java | 1 + .../server/ProofOfWorkRequestHandler.java | 11 ++++++++++- .../repository/ServerProofOfWorkRepository.java | 15 ++++++++++++++- .../db/migration/V1.2.1__ProofOfWorkTask.sql | 6 +++--- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java b/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java index 205154c..7237e79 100644 --- a/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java +++ b/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java @@ -122,6 +122,7 @@ public class JabitServerConfig { @Bean public Set admins() { + security(); return Utils.readOrCreateList( ADMIN_LIST, "# Admins can send commands to the server.\n" diff --git a/src/main/java/ch/dissem/bitmessage/server/ProofOfWorkRequestHandler.java b/src/main/java/ch/dissem/bitmessage/server/ProofOfWorkRequestHandler.java index eafe85c..ab98b10 100644 --- a/src/main/java/ch/dissem/bitmessage/server/ProofOfWorkRequestHandler.java +++ b/src/main/java/ch/dissem/bitmessage/server/ProofOfWorkRequestHandler.java @@ -27,6 +27,7 @@ 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; @@ -39,6 +40,7 @@ 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 @@ -61,6 +63,12 @@ public class ProofOfWorkRequestHandler implements CustomCommandHandler, Internal 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() { @@ -77,11 +85,12 @@ public class ProofOfWorkRequestHandler implements CustomCommandHandler, Internal CryptoCustomMessage cryptoMessage = CryptoCustomMessage.read(message, ProofOfWorkRequest::read); ProofOfWorkRequest request = decrypt(cryptoMessage); - if (request == null) + 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())) { diff --git a/src/main/java/ch/dissem/bitmessage/server/repository/ServerProofOfWorkRepository.java b/src/main/java/ch/dissem/bitmessage/server/repository/ServerProofOfWorkRepository.java index e37e200..c4af9b2 100644 --- a/src/main/java/ch/dissem/bitmessage/server/repository/ServerProofOfWorkRepository.java +++ b/src/main/java/ch/dissem/bitmessage/server/repository/ServerProofOfWorkRepository.java @@ -19,6 +19,7 @@ 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; @@ -46,10 +47,11 @@ public class ServerProofOfWorkRepository extends JdbcHelper { public void storeTask(ProofOfWorkRequest request) { try (Connection connection = config.getConnection()) { PreparedStatement ps = connection.prepareStatement( - "INSERT INTO ProofOfWorkTask (initial_hash, client, target) VALUES (?, ?, ?)"); + "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); @@ -112,6 +114,17 @@ public class ServerProofOfWorkRepository extends JdbcHelper { } } + 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; diff --git a/src/main/resources/db/migration/V1.2.1__ProofOfWorkTask.sql b/src/main/resources/db/migration/V1.2.1__ProofOfWorkTask.sql index 40d3135..0561d9d 100644 --- a/src/main/resources/db/migration/V1.2.1__ProofOfWorkTask.sql +++ b/src/main/resources/db/migration/V1.2.1__ProofOfWorkTask.sql @@ -1,7 +1,7 @@ CREATE TABLE ProofOfWorkTask ( - initial_hash BINARY(64) NOT NULL PRIMARY KEY, - client VARCHAR(40) NOT NULL, + initial_hash BINARY(64) NOT NULL PRIMARY KEY, + client VARCHAR(40) NOT NULL, target BINARY(32), nonce BINARY(8), - status VARCHAR(20), + timestamp BIGINT NOT NULL, ); \ No newline at end of file