Server POW should now work
This commit is contained in:
parent
891294f267
commit
aafa4a4a11
@ -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<BitmessageAddress> clients() {
|
||||
security();
|
||||
return Utils.readOrCreateList(
|
||||
CLIENT_LIST,
|
||||
"# Clients may send incomplete objects for proof of work.\n"
|
||||
|
@ -16,58 +16,123 @@
|
||||
|
||||
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<byte[]> 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<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
|
||||
}
|
||||
|
||||
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.getData(),
|
||||
(sender, in) -> ProofOfWorkRequest.read(sender, in));
|
||||
CryptoCustomMessage<ProofOfWorkRequest> 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<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 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<ProofOfWorkRequest> cryptoMessage) {
|
||||
for (byte[] key : decryptionKeys) {
|
||||
@ -81,4 +146,9 @@ public class ProofOfWorkRequestHandler implements CustomCommandHandler {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContext(InternalContext context) {
|
||||
this.context = context;
|
||||
this.engine = context.getProofOfWorkEngine();
|
||||
}
|
||||
}
|
||||
|
@ -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<Update<InventoryVector>> getUnconfirmed(BitmessageAddress client) {
|
||||
List<Update<InventoryVector>> 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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public void confirm(Stream<InventoryVector> 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
|
||||
public static class Task {
|
||||
public final byte[] initialHash;
|
||||
public final byte[] target;
|
||||
|
||||
private Task(byte[] initialHash, byte[] target) {
|
||||
this.initialHash = initialHash;
|
||||
this.target = target;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user