Skip to content
Permalink
Browse files

pplns: Send payments via wallet RPC

  • Loading branch information
00-matt committed Feb 11, 2020
1 parent 3df20ba commit 9e1a7d222878a68911fdfd679b117451dc307efe
@@ -57,7 +57,7 @@
<dependency>
<groupId>uk.offtopica</groupId>
<artifactId>monerorpc</artifactId>
<version>0.1.0</version>
<version>0.2.1</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
@@ -9,6 +9,7 @@
import org.springframework.context.annotation.PropertySource;
import uk.offtopica.moneropool.pplns.service.PplnsService;
import uk.offtopica.monerorpc.daemon.MoneroDaemonRpcClient;
import uk.offtopica.monerorpc.wallet.MoneroWalletRpcClient;

import java.net.URI;

@@ -28,4 +29,9 @@ public static void main(String[] args) {
MoneroDaemonRpcClient daemonRpcClient(@Value("${daemon.address}") URI uri) {
return new MoneroDaemonRpcClient(uri);
}

@Bean
MoneroWalletRpcClient walletRpcClient(@Value("${wallet.address}") URI uri) {
return new MoneroWalletRpcClient(uri);
}
}
@@ -26,4 +26,20 @@ public BlockRepository(Sql2o sql2o) {
.executeAndFetch(Block.class);
}
}

public void markAsOrphan(Block block) {
try (Connection conn = sql2o.open()) {
conn.createQuery("UPDATE block SET orphan = TRUE WHERE block.id = :id;")
.addParameter("id", block.getId())
.executeUpdate();
}
}

public void markAsPaid(Block block) {
try (Connection conn = sql2o.open()) {
conn.createQuery("UPDATE block SET paid = TRUE WHERE block.id = :id;")
.addParameter("id", block.getId())
.executeUpdate();
}
}
}
@@ -0,0 +1,26 @@
package uk.offtopica.moneropool.pplns.repository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.sql2o.Connection;
import org.sql2o.Sql2o;
import uk.offtopica.moneropool.pplns.model.Block;

@Component
public class TransactionRepository {
private final Sql2o sql2o;

@Autowired
private TransactionRepository(Sql2o sql2o) {
this.sql2o = sql2o;
}

public void createTransaction(Block block, String hash) {
try (Connection conn = sql2o.open()) {
conn.createQuery("INSERT INTO transaction (block_id, hash) VALUES (:block, :hash);")
.addParameter("block", block.getId())
.addParameter("hash", hash)
.executeUpdate();
}
}
}
@@ -6,6 +6,8 @@
import uk.offtopica.moneropool.pplns.model.Block;
import uk.offtopica.monerorpc.daemon.MoneroDaemonRpcClient;

import java.util.concurrent.ExecutionException;

@Component
@Slf4j
public class DaemonService {
@@ -19,7 +21,7 @@ public DaemonService(MoneroDaemonRpcClient daemonRpc) {
public long getBlockHeight() {
try {
return rpc.getBlockCount().get();
} catch (Exception e) {
} catch (InterruptedException | ExecutionException e) {
log.error("Failed to get block height", e);
throw new RuntimeException(e);
}
@@ -8,7 +8,11 @@
import uk.offtopica.moneropool.pplns.model.MinerHashes;
import uk.offtopica.moneropool.pplns.repository.BlockRepository;
import uk.offtopica.moneropool.pplns.repository.ShareRepository;
import uk.offtopica.moneropool.pplns.repository.TransactionRepository;
import uk.offtopica.monerorpc.wallet.TransferDestination;
import uk.offtopica.monerorpc.wallet.TransferResult;

import java.util.ArrayList;
import java.util.List;

@Component
@@ -17,21 +21,29 @@
private final BlockRepository blockRepository;
private final DaemonService daemonService;
private final ShareRepository shareRepository;
private final TransactionRepository transactionRepository;
private final WalletService walletService;
private final int unlockTime;
private final int windowSize;
private final long feeDivisor;

@Autowired
public PplnsService(BlockRepository blockRepository,
DaemonService daemonService,
ShareRepository shareRepository,
TransactionRepository transactionRepository,
WalletService walletService,
@Value("${pplns.unlock}") int unlockTime,
@Value("${pplns.window}") int windowSize) {
@Value("${pplns.window}") int windowSize,
@Value("${pplns.feeDivisor}") long feeDivisor) {
this.blockRepository = blockRepository;
this.daemonService = daemonService;
this.shareRepository = shareRepository;
// this.unlockTime = unlockTime;
this.unlockTime = 0;
this.transactionRepository = transactionRepository;
this.walletService = walletService;
this.unlockTime = unlockTime;
this.windowSize = windowSize;
this.feeDivisor = feeDivisor;
}

public void processAllPayments() {
@@ -46,10 +58,14 @@ public void processPaymentsForBlock(Block block) {

if (daemonService.isOrphan(block)) {
log.warn("Block #{} is an orphan", block.getId());
// TODO: Mark as orphan in db.
blockRepository.markAsOrphan(block);
return;
}

final long fee = block.getExpectedReward() / 100L;
final long reward = block.getExpectedReward() - fee;

log.info("Taking fee of {} moneroj from block reward", fee / 1e12);

List<MinerHashes> minerHashesList =
shareRepository.findMinerHashes(block.getDifficulty() * windowSize,
@@ -60,10 +76,29 @@ public void processPaymentsForBlock(Block block) {
totalHashes += minerHashes.getHashes();
}

List<TransferDestination> transferDestinations = new ArrayList<>(minerHashesList.size());

for (MinerHashes minerHashes : minerHashesList) {
final long thisPayout =
Math.round((((double) minerHashes.getHashes()) / totalHashes) * block.getExpectedReward());
log.info("Paying {} {} moneroj", minerHashes.getWalletAddress(), (thisPayout / 1e12D));
Math.round((((double) minerHashes.getHashes()) / totalHashes) * reward);

transferDestinations.add(new TransferDestination(minerHashes.getWalletAddress(), thisPayout));
}

// Mark as paid before actually sending the transaction. We can always review the logs to resend a payment,
// but we can't ask for coins back if we send them twice.
blockRepository.markAsPaid(block);

List<TransferResult> transferResults = walletService.sendPayment(transferDestinations);

for (TransferResult transferResult : transferResults) {
log.info("Transaction hash={}", transferResult.getHash());
// TODO: Extract to library.
StringBuilder sb = new StringBuilder();
for (byte b : transferResult.getHash()) {
sb.append(String.format("%02x", b));
}
transactionRepository.createTransaction(block, sb.toString());
}
}
}
@@ -0,0 +1,35 @@
package uk.offtopica.moneropool.pplns.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import uk.offtopica.monerorpc.wallet.MoneroWalletRpcClient;
import uk.offtopica.monerorpc.wallet.TransferDestination;
import uk.offtopica.monerorpc.wallet.TransferResult;

import java.util.List;
import java.util.concurrent.ExecutionException;

@Component
@Slf4j
public class WalletService {
private final MoneroWalletRpcClient rpc;

@Autowired
public WalletService(MoneroWalletRpcClient rpc) {
this.rpc = rpc;
}

public List<TransferResult> sendPayment(List<TransferDestination> destinations) {
for (TransferDestination destination : destinations) {
log.info("Paying {} moneroj to {}", destination.getAmount() / 1e12, destination.getAddress());
}

try {
return rpc.transfer(destinations).get();
} catch (InterruptedException | ExecutionException e) {
log.error("Failed to transfer", e);
throw new RuntimeException(e);
}
}
}
@@ -1,7 +1,8 @@
daemon.address=http://localhost:18081/json_rpc
daemon.address=http://localhost:38081/json_rpc
wallet.address=http://localhost:38088/json_rpc
database.url=jdbc:postgresql://localhost:5432/postgres
database.user=postgres
database.pass=postgres
pplns.fee=100
pplns.feeDivisor=100
pplns.unlock=60
pplns.window=10
pplns.window=2

0 comments on commit 9e1a7d2

Please sign in to comment.
You can’t perform that action at this time.