forked from Qortal/qortal
Interim proxy minting commit
Added /addresses/proxying to find proxy forging mappings. Added /addresses/proxykey/{genprivkey}/{recipientpubkey} to calculate proxy private key. New Block.regenerate factory method to create new Blocks but without having to reprocess ATs, etc. Added support for proxied generator in Block.calcGeneratorsTarget BlockGenerator now generates and checks new blocks for various generators, including proxy generators. BlockGenerator now uses generator private keys supplied by Settings. Corresponding changes to Settings to load base58-encoded private keys. + minor stuff
This commit is contained in:
parent
9b859f3efd
commit
c9035edd2c
@ -1,6 +1,8 @@
|
|||||||
package org.qora.api.resource;
|
package org.qora.api.resource;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||||
@ -8,6 +10,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
@ -15,6 +18,7 @@ import javax.ws.rs.POST;
|
|||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
@ -25,7 +29,9 @@ import org.qora.api.ApiException;
|
|||||||
import org.qora.api.ApiExceptionFactory;
|
import org.qora.api.ApiExceptionFactory;
|
||||||
import org.qora.asset.Asset;
|
import org.qora.asset.Asset;
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
|
import org.qora.crypto.Ed25519;
|
||||||
import org.qora.data.account.AccountData;
|
import org.qora.data.account.AccountData;
|
||||||
|
import org.qora.data.account.ProxyForgerData;
|
||||||
import org.qora.data.transaction.ProxyForgingTransactionData;
|
import org.qora.data.transaction.ProxyForgingTransactionData;
|
||||||
import org.qora.group.Group;
|
import org.qora.group.Group;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
@ -268,6 +274,58 @@ public class AddressesResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/proxying")
|
||||||
|
@Operation(
|
||||||
|
summary = "List accounts involved in proxy forging, with reward percentage",
|
||||||
|
description = "Returns list of accounts. At least one of \"proxiedFor\" or \"proxiedBy\" needs to be supplied.",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
content = @Content(mediaType = MediaType.APPLICATION_JSON, array = @ArraySchema(schema = @Schema(implementation = ProxyForgerData.class)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
|
||||||
|
public List<ProxyForgerData> getProxying(@QueryParam("proxiedFor") List<String> recipients,
|
||||||
|
@QueryParam("proxiedBy") List<String> forgers,
|
||||||
|
@Parameter(
|
||||||
|
ref = "limit"
|
||||||
|
) @QueryParam("limit") Integer limit, @Parameter(
|
||||||
|
ref = "offset"
|
||||||
|
) @QueryParam("offset") Integer offset, @Parameter(
|
||||||
|
ref = "reverse"
|
||||||
|
) @QueryParam("reverse") Boolean reverse) {
|
||||||
|
if (recipients.isEmpty() && forgers.isEmpty())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
return repository.getAccountRepository().findProxyAccounts(recipients, forgers, limit, offset, reverse);
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/proxykey/{generatorprivatekey}/{recipientpublickey}")
|
||||||
|
@Operation(
|
||||||
|
summary = "Calculate proxy private key",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public String calculateProxyKey(@PathParam("generatorprivatekey") String generatorKey58, @PathParam("recipientpublickey") String recipientKey58) {
|
||||||
|
byte[] generatorKey = Base58.decode(generatorKey58);
|
||||||
|
byte[] recipientKey = Base58.decode(recipientKey58);
|
||||||
|
|
||||||
|
byte[] sharedSecret = Ed25519.getSharedSecret(recipientKey, generatorKey);
|
||||||
|
|
||||||
|
byte[] proxySeed = Crypto.digest(sharedSecret);
|
||||||
|
|
||||||
|
return Base58.encode(proxySeed);
|
||||||
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/proxyforging")
|
@Path("/proxyforging")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -240,6 +240,61 @@ public class Block {
|
|||||||
generator.getPublicKey(), generatorSignature, atCount, atFees);
|
generator.getPublicKey(), generatorSignature, atCount, atFees);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct another block using this block as template, but with different generator account.
|
||||||
|
* <p>
|
||||||
|
* NOTE: uses the same transactions list, AT states, etc.
|
||||||
|
*
|
||||||
|
* @param generator
|
||||||
|
* @return
|
||||||
|
* @throws DataException
|
||||||
|
*/
|
||||||
|
public Block regenerate(PrivateKeyAccount generator) throws DataException {
|
||||||
|
Block newBlock = new Block(this.repository, this.blockData);
|
||||||
|
|
||||||
|
BlockData parentBlockData = this.getParent();
|
||||||
|
Block parentBlock = new Block(repository, parentBlockData);
|
||||||
|
|
||||||
|
newBlock.generator = generator;
|
||||||
|
|
||||||
|
// Copy AT state data
|
||||||
|
newBlock.ourAtStates = this.ourAtStates;
|
||||||
|
newBlock.atStates = newBlock.ourAtStates;
|
||||||
|
newBlock.ourAtFees = this.ourAtFees;
|
||||||
|
|
||||||
|
// Calculate new block timestamp
|
||||||
|
int version = this.blockData.getVersion();
|
||||||
|
byte[] reference = this.blockData.getReference();
|
||||||
|
BigDecimal generatingBalance = this.blockData.getGeneratingBalance();
|
||||||
|
|
||||||
|
byte[] generatorSignature;
|
||||||
|
try {
|
||||||
|
generatorSignature = generator
|
||||||
|
.sign(BlockTransformer.getBytesForGeneratorSignature(parentBlockData.getGeneratorSignature(), generatingBalance, generator));
|
||||||
|
} catch (TransformationException e) {
|
||||||
|
throw new DataException("Unable to calculate next block generator signature", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
long timestamp = parentBlock.calcNextBlockTimestamp(version, generatorSignature, generator);
|
||||||
|
|
||||||
|
newBlock.transactions = this.transactions;
|
||||||
|
int transactionCount = this.blockData.getTransactionCount();
|
||||||
|
BigDecimal totalFees = this.blockData.getTotalFees();
|
||||||
|
byte[] transactionsSignature = null; // We'll calculate this later
|
||||||
|
Integer height = this.blockData.getHeight();
|
||||||
|
|
||||||
|
int atCount = newBlock.ourAtStates.size();
|
||||||
|
BigDecimal atFees = newBlock.ourAtFees;
|
||||||
|
|
||||||
|
newBlock.blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
||||||
|
generator.getPublicKey(), generatorSignature, atCount, atFees);
|
||||||
|
|
||||||
|
// Resign to update transactions signature
|
||||||
|
newBlock.sign();
|
||||||
|
|
||||||
|
return newBlock;
|
||||||
|
}
|
||||||
|
|
||||||
// Getters/setters
|
// Getters/setters
|
||||||
|
|
||||||
public BlockData getBlockData() {
|
public BlockData getBlockData() {
|
||||||
@ -359,7 +414,7 @@ public class Block {
|
|||||||
return actualBlockTime;
|
return actualBlockTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigInteger calcGeneratorsTarget(Account nextBlockGenerator) throws DataException {
|
private BigInteger calcGeneratorsTarget(PublicKeyAccount nextBlockGenerator) throws DataException {
|
||||||
// Start with 32-byte maximum integer representing all possible correct "guesses"
|
// Start with 32-byte maximum integer representing all possible correct "guesses"
|
||||||
// Where a "correct guess" is an integer greater than the threshold represented by calcBlockHash()
|
// Where a "correct guess" is an integer greater than the threshold represented by calcBlockHash()
|
||||||
byte[] targetBytes = new byte[32];
|
byte[] targetBytes = new byte[32];
|
||||||
@ -371,9 +426,17 @@ public class Block {
|
|||||||
BigInteger baseTarget = BigInteger.valueOf(calcBaseTarget(calcNextBlockGeneratingBalance()));
|
BigInteger baseTarget = BigInteger.valueOf(calcBaseTarget(calcNextBlockGeneratingBalance()));
|
||||||
target = target.divide(baseTarget);
|
target = target.divide(baseTarget);
|
||||||
|
|
||||||
|
// If generator is actually proxy account then use forger's account to calculate target.
|
||||||
|
BigDecimal generatingBalance;
|
||||||
|
ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(nextBlockGenerator.getPublicKey());
|
||||||
|
if (proxyForgerData != null)
|
||||||
|
generatingBalance = new PublicKeyAccount(this.repository, proxyForgerData.getForgerPublicKey()).getGeneratingBalance();
|
||||||
|
else
|
||||||
|
generatingBalance = nextBlockGenerator.getGeneratingBalance();
|
||||||
|
|
||||||
// Multiply by account's generating balance
|
// Multiply by account's generating balance
|
||||||
// So the greater the account's generating balance then the greater the remaining "correct guesses"
|
// So the greater the account's generating balance then the greater the remaining "correct guesses"
|
||||||
target = target.multiply(nextBlockGenerator.getGeneratingBalance().toBigInteger());
|
target = target.multiply(generatingBalance.toBigInteger());
|
||||||
|
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
@ -410,8 +473,8 @@ public class Block {
|
|||||||
return new BigInteger(1, hash);
|
return new BigInteger(1, hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calculate next block's timestamp, given next block's version, generator signature and generator's private key */
|
/** Calculate next block's timestamp, given next block's version, generator signature and generator's public key */
|
||||||
private long calcNextBlockTimestamp(int nextBlockVersion, byte[] nextBlockGeneratorSignature, PrivateKeyAccount nextBlockGenerator) throws DataException {
|
private long calcNextBlockTimestamp(int nextBlockVersion, byte[] nextBlockGeneratorSignature, PublicKeyAccount nextBlockGenerator) throws DataException {
|
||||||
BigInteger hashValue = calcNextBlockHash(nextBlockVersion, nextBlockGeneratorSignature, nextBlockGenerator);
|
BigInteger hashValue = calcNextBlockHash(nextBlockVersion, nextBlockGeneratorSignature, nextBlockGenerator);
|
||||||
BigInteger target = calcGeneratorsTarget(nextBlockGenerator);
|
BigInteger target = calcGeneratorsTarget(nextBlockGenerator);
|
||||||
|
|
||||||
@ -946,6 +1009,8 @@ public class Block {
|
|||||||
BlockData parentBlockData = parentBlock.getBlockData();
|
BlockData parentBlockData = parentBlock.getBlockData();
|
||||||
|
|
||||||
BigInteger hashValue = this.calcBlockHash();
|
BigInteger hashValue = this.calcBlockHash();
|
||||||
|
|
||||||
|
// calcGeneratorsTarget handles proxy forging aspect
|
||||||
BigInteger target = parentBlock.calcGeneratorsTarget(this.generator);
|
BigInteger target = parentBlock.calcGeneratorsTarget(this.generator);
|
||||||
|
|
||||||
// Multiply target by guesses
|
// Multiply target by guesses
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package org.qora.block;
|
package org.qora.block;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@ -26,8 +29,6 @@ import org.qora.utils.Base58;
|
|||||||
public class BlockGenerator extends Thread {
|
public class BlockGenerator extends Thread {
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
private byte[] generatorPrivateKey;
|
|
||||||
private PrivateKeyAccount generator;
|
|
||||||
private boolean running;
|
private boolean running;
|
||||||
|
|
||||||
// Other properties
|
// Other properties
|
||||||
@ -35,8 +36,7 @@ public class BlockGenerator extends Thread {
|
|||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
public BlockGenerator(byte[] generatorPrivateKey) {
|
public BlockGenerator() {
|
||||||
this.generatorPrivateKey = generatorPrivateKey;
|
|
||||||
this.running = true;
|
this.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,6 +45,11 @@ public class BlockGenerator extends Thread {
|
|||||||
public void run() {
|
public void run() {
|
||||||
Thread.currentThread().setName("BlockGenerator");
|
Thread.currentThread().setName("BlockGenerator");
|
||||||
|
|
||||||
|
List<byte[]> generatorKeys = Settings.getInstance().getGeneratorKeys();
|
||||||
|
// No generators?
|
||||||
|
if (generatorKeys.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
if (Settings.getInstance().getWipeUnconfirmedOnStart()) {
|
if (Settings.getInstance().getWipeUnconfirmedOnStart()) {
|
||||||
// Wipe existing unconfirmed transactions
|
// Wipe existing unconfirmed transactions
|
||||||
@ -58,37 +63,59 @@ public class BlockGenerator extends Thread {
|
|||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
generator = new PrivateKeyAccount(repository, generatorPrivateKey);
|
List<PrivateKeyAccount> generators = generatorKeys.stream().map(key -> new PrivateKeyAccount(repository, key)).collect(Collectors.toList());
|
||||||
|
|
||||||
// Going to need this a lot...
|
// Going to need this a lot...
|
||||||
BlockRepository blockRepository = repository.getBlockRepository();
|
BlockRepository blockRepository = repository.getBlockRepository();
|
||||||
Block previousBlock = null;
|
Block previousBlock = null;
|
||||||
Block newBlock = null;
|
|
||||||
|
List<Block> newBlocks = null;
|
||||||
|
|
||||||
while (running) {
|
while (running) {
|
||||||
// Check blockchain hasn't changed
|
// Check blockchain hasn't changed
|
||||||
BlockData lastBlockData = blockRepository.getLastBlock();
|
BlockData lastBlockData = blockRepository.getLastBlock();
|
||||||
if (previousBlock == null || !Arrays.equals(previousBlock.getSignature(), lastBlockData.getSignature())) {
|
if (previousBlock == null || !Arrays.equals(previousBlock.getSignature(), lastBlockData.getSignature())) {
|
||||||
previousBlock = new Block(repository, lastBlockData);
|
previousBlock = new Block(repository, lastBlockData);
|
||||||
newBlock = null;
|
newBlocks = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do we need to build a potential new block?
|
// Do we need to build a potential new blocks?
|
||||||
if (newBlock == null)
|
if (newBlocks == null) {
|
||||||
newBlock = new Block(repository, previousBlock.getBlockData(), generator);
|
// First block does the AT heavy-lifting
|
||||||
|
newBlocks = new ArrayList<>(generators.size());
|
||||||
|
Block newBlock = new Block(repository, previousBlock.getBlockData(), generators.get(0));
|
||||||
|
newBlocks.add(newBlock);
|
||||||
|
|
||||||
|
// The blocks for other generators require less effort...
|
||||||
|
for (int i = 1; i < generators.size(); ++i)
|
||||||
|
newBlocks.add(newBlock.regenerate(generators.get(i)));
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure we're the only thread modifying the blockchain
|
// Make sure we're the only thread modifying the blockchain
|
||||||
Lock blockchainLock = Controller.getInstance().getBlockchainLock();
|
Lock blockchainLock = Controller.getInstance().getBlockchainLock();
|
||||||
if (blockchainLock.tryLock())
|
if (blockchainLock.tryLock())
|
||||||
generation: try {
|
generation: try {
|
||||||
// Is new block's timestamp valid yet?
|
List<Block> goodBlocks = new ArrayList<>();
|
||||||
// We do a separate check as some timestamp checks are skipped for testnet
|
|
||||||
if (newBlock.isTimestampValid() != ValidationResult.OK)
|
for (Block testBlock : newBlocks) {
|
||||||
|
// Is new block's timestamp valid yet?
|
||||||
|
// We do a separate check as some timestamp checks are skipped for testnet
|
||||||
|
if (testBlock.isTimestampValid() != ValidationResult.OK)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Is new block valid yet? (Before adding unconfirmed transactions)
|
||||||
|
if (testBlock.isValid() != ValidationResult.OK)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
goodBlocks.add(testBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (goodBlocks.isEmpty())
|
||||||
break generation;
|
break generation;
|
||||||
|
|
||||||
// Is new block valid yet? (Before adding unconfirmed transactions)
|
// Pick random generator
|
||||||
if (newBlock.isValid() != ValidationResult.OK)
|
int winningIndex = new Random().nextInt(goodBlocks.size());
|
||||||
break generation;
|
Block newBlock = goodBlocks.get(winningIndex);
|
||||||
|
|
||||||
// Delete invalid transactions
|
// Delete invalid transactions
|
||||||
deleteInvalidTransactions(repository);
|
deleteInvalidTransactions(repository);
|
||||||
|
@ -47,7 +47,7 @@ public class blockgenerator {
|
|||||||
System.exit(2);
|
System.exit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockGenerator blockGenerator = new BlockGenerator(privateKey);
|
BlockGenerator blockGenerator = new BlockGenerator();
|
||||||
blockGenerator.start();
|
blockGenerator.start();
|
||||||
|
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||||
|
@ -42,7 +42,6 @@ import org.qora.repository.hsqldb.HSQLDBRepositoryFactory;
|
|||||||
import org.qora.settings.Settings;
|
import org.qora.settings.Settings;
|
||||||
import org.qora.transaction.Transaction;
|
import org.qora.transaction.Transaction;
|
||||||
import org.qora.transaction.Transaction.ValidationResult;
|
import org.qora.transaction.Transaction.ValidationResult;
|
||||||
import org.qora.utils.Base58;
|
|
||||||
import org.qora.utils.NTP;
|
import org.qora.utils.NTP;
|
||||||
|
|
||||||
public class Controller extends Thread {
|
public class Controller extends Thread {
|
||||||
@ -158,13 +157,9 @@ public class Controller extends Thread {
|
|||||||
System.exit(2);
|
System.exit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX extract private key needed for block gen
|
LOGGER.info("Starting block generator");
|
||||||
if (args.length == 0 || !args[0].equals("NO-BLOCK-GEN")) {
|
blockGenerator = new BlockGenerator();
|
||||||
LOGGER.info("Starting block generator");
|
blockGenerator.start();
|
||||||
byte[] privateKey = Base58.decode(args.length > 0 ? args[0] : "A9MNsATgQgruBUjxy2rjWY36Yf19uRioKZbiLFT2P7c6");
|
|
||||||
blockGenerator = new BlockGenerator(privateKey);
|
|
||||||
blockGenerator.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.info("Starting API on port " + Settings.getInstance().getApiPort());
|
LOGGER.info("Starting API on port " + Settings.getInstance().getApiPort());
|
||||||
try {
|
try {
|
||||||
|
@ -4,6 +4,9 @@ import java.math.BigDecimal;
|
|||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
|
|
||||||
|
import org.qora.crypto.Crypto;
|
||||||
|
|
||||||
// All properties to be converted to JSON via JAXB
|
// All properties to be converted to JSON via JAXB
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
@ -47,4 +50,9 @@ public class ProxyForgerData {
|
|||||||
return this.share;
|
return this.share;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@XmlElement(name = "forger")
|
||||||
|
public String getForger() {
|
||||||
|
return Crypto.toAddress(this.forgerPublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,8 @@ public interface AccountRepository {
|
|||||||
|
|
||||||
public ProxyForgerData getProxyForgeData(byte[] proxyPublicKey) throws DataException;
|
public ProxyForgerData getProxyForgeData(byte[] proxyPublicKey) throws DataException;
|
||||||
|
|
||||||
|
public List<ProxyForgerData> findProxyAccounts(List<String> recipients, List<String> forgers, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
public void save(ProxyForgerData proxyForgerData) throws DataException;
|
public void save(ProxyForgerData proxyForgerData) throws DataException;
|
||||||
|
|
||||||
public void delete(byte[] forgerPublickey, String recipient) throws DataException;
|
public void delete(byte[] forgerPublickey, String recipient) throws DataException;
|
||||||
|
@ -15,6 +15,8 @@ import org.qora.data.account.ProxyForgerData;
|
|||||||
import org.qora.repository.AccountRepository;
|
import org.qora.repository.AccountRepository;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
|
|
||||||
|
import static org.qora.repository.hsqldb.HSQLDBRepository.nPlaceholders;
|
||||||
|
|
||||||
public class HSQLDBAccountRepository implements AccountRepository {
|
public class HSQLDBAccountRepository implements AccountRepository {
|
||||||
|
|
||||||
protected HSQLDBRepository repository;
|
protected HSQLDBRepository repository;
|
||||||
@ -331,6 +333,51 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProxyForgerData> findProxyAccounts(List<String> recipients, List<String> forgers, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT forger, recipient, share, proxy_public_key FROM ProxyForgers ";
|
||||||
|
List<Object> args = new ArrayList<>();
|
||||||
|
|
||||||
|
if (!forgers.isEmpty()) {
|
||||||
|
sql += "JOIN Accounts ON Accounts.public_key = ProxyForgers.forger "
|
||||||
|
+ "WHERE Accounts.account IN (" + nPlaceholders(forgers.size()) + ") ";
|
||||||
|
args.addAll(forgers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!recipients.isEmpty()) {
|
||||||
|
sql += forgers.isEmpty() ? "WHERE " : "AND ";
|
||||||
|
sql += "recipient IN (" + nPlaceholders(recipients.size()) + ") ";
|
||||||
|
args.addAll(recipients);
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += "ORDER BY recipient, share";
|
||||||
|
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
|
List<ProxyForgerData> proxyAccounts = new ArrayList<>();
|
||||||
|
|
||||||
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, args.toArray())) {
|
||||||
|
if (resultSet == null)
|
||||||
|
return proxyAccounts;
|
||||||
|
|
||||||
|
do {
|
||||||
|
byte[] forgerPublicKey = resultSet.getBytes(1);
|
||||||
|
String recipient = resultSet.getString(2);
|
||||||
|
BigDecimal share = resultSet.getBigDecimal(3);
|
||||||
|
byte[] proxyPublicKey = resultSet.getBytes(4);
|
||||||
|
|
||||||
|
proxyAccounts.add(new ProxyForgerData(forgerPublicKey, recipient, proxyPublicKey, share));
|
||||||
|
} while (resultSet.next());
|
||||||
|
|
||||||
|
return proxyAccounts;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to find proxy forge accounts in repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(ProxyForgerData proxyForgerData) throws DataException {
|
public void save(ProxyForgerData proxyForgerData) throws DataException {
|
||||||
HSQLDBSaver saveHelper = new HSQLDBSaver("ProxyForgers");
|
HSQLDBSaver saveHelper = new HSQLDBSaver("ProxyForgers");
|
||||||
|
@ -11,6 +11,7 @@ import java.time.Instant;
|
|||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
@ -407,4 +408,9 @@ public class HSQLDBRepository implements Repository {
|
|||||||
return offsetDateTime.toInstant().toEpochMilli();
|
return offsetDateTime.toInstant().toEpochMilli();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Convenience method to return n comma-separated, placeholders as a string. */
|
||||||
|
public static String nPlaceholders(int n) {
|
||||||
|
return String.join(", ", Collections.nCopies(n, "?"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -5,6 +5,7 @@ import java.io.FileNotFoundException;
|
|||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.xml.bind.JAXBContext;
|
import javax.xml.bind.JAXBContext;
|
||||||
import javax.xml.bind.JAXBException;
|
import javax.xml.bind.JAXBException;
|
||||||
@ -12,6 +13,7 @@ import javax.xml.bind.UnmarshalException;
|
|||||||
import javax.xml.bind.Unmarshaller;
|
import javax.xml.bind.Unmarshaller;
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||||
import javax.xml.transform.stream.StreamSource;
|
import javax.xml.transform.stream.StreamSource;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
@ -19,6 +21,7 @@ import org.apache.logging.log4j.Logger;
|
|||||||
import org.eclipse.persistence.exceptions.XMLMarshalException;
|
import org.eclipse.persistence.exceptions.XMLMarshalException;
|
||||||
import org.eclipse.persistence.jaxb.JAXBContextFactory;
|
import org.eclipse.persistence.jaxb.JAXBContextFactory;
|
||||||
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
|
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
|
||||||
|
import org.qora.api.Base58TypeAdapter;
|
||||||
import org.qora.block.BlockChain;
|
import org.qora.block.BlockChain;
|
||||||
|
|
||||||
// All properties to be converted to JSON via JAXB
|
// All properties to be converted to JSON via JAXB
|
||||||
@ -62,6 +65,10 @@ public class Settings {
|
|||||||
private String blockchainConfig = "blockchain.json";
|
private String blockchainConfig = "blockchain.json";
|
||||||
private boolean useBitcoinTestNet = false;
|
private boolean useBitcoinTestNet = false;
|
||||||
|
|
||||||
|
// Private keys to use for generating blocks
|
||||||
|
@XmlJavaTypeAdapter(type = byte[].class, value = Base58TypeAdapter.class)
|
||||||
|
private List<byte[]> generatorKeys;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
private Settings() {
|
private Settings() {
|
||||||
@ -228,4 +235,8 @@ public class Settings {
|
|||||||
return this.useBitcoinTestNet;
|
return this.useBitcoinTestNet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<byte[]> getGeneratorKeys() {
|
||||||
|
return this.generatorKeys;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user