Replace settings-based generator private keys with DB table and API calls

This commit is contained in:
catbref 2019-03-20 12:05:35 +00:00
parent c9035edd2c
commit 2f6ef32f35
8 changed files with 219 additions and 33 deletions

View File

@ -3,8 +3,10 @@ package org.qora.api.resource;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -12,14 +14,18 @@ import java.time.LocalDate;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import org.qora.account.PrivateKeyAccount;
import org.qora.api.ApiError;
import org.qora.api.ApiErrors;
import org.qora.api.ApiExceptionFactory;
@ -29,6 +35,11 @@ import org.qora.controller.Controller;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.repository.RepositoryManager;
import org.qora.data.account.ForgingAccountData;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.repository.RepositoryManager;
import org.qora.utils.Base58;
@Path("/admin")
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
@ -137,7 +148,101 @@ public class AdminResource {
} catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
}
}
@GET
@Path("/forgingaccounts")
@Operation(
summary = "List accounts used to forge by BlockGenerator",
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.APPLICATION_JSON, array = @ArraySchema(schema = @Schema(implementation = ForgingAccountData.class)))
)
}
)
@ApiErrors({ApiError.REPOSITORY_ISSUE})
public List<ForgingAccountData> getForgingAccounts() {
try (final Repository repository = RepositoryManager.getRepository()) {
return repository.getAccountRepository().getForgingAccounts();
} catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
}
}
@POST
@Path("/forgingaccounts")
@Operation(
summary = "Add account to use to forge by BlockGenerator",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "string"
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string"))
)
}
)
@ApiErrors({ApiError.REPOSITORY_ISSUE})
public String addForgingAccount(String seed58) {
byte[] seed = Base58.decode(seed58.trim());
// Check seed is valid
try {
new PrivateKeyAccount(null, seed);
} catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY, e);
}
try (final Repository repository = RepositoryManager.getRepository()) {
ForgingAccountData forgingAccountData = new ForgingAccountData(seed);
repository.getAccountRepository().save(forgingAccountData);
repository.saveChanges();
} catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
}
return "true";
}
@DELETE
@Path("/forgingaccounts")
@Operation(
summary = "Delete account to use to forge by BlockGenerator",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "string"
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string"))
)
}
)
public String deleteForgingAccount(String seed58) {
byte[] seed = Base58.decode(seed58.trim());
try (final Repository repository = RepositoryManager.getRepository()) {
if (repository.getAccountRepository().delete(seed) == 0)
return "false";
repository.saveChanges();
} catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
}
return "true";
}
}

View File

@ -385,9 +385,14 @@ public class UtilsResource {
if (privateKey.length != 32)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
try {
byte[] publicKey = new PrivateKeyAccount(null, privateKey).getPublicKey();
return Base58.encode(publicKey);
} catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY, e);
}
}
@GET

View File

@ -12,6 +12,7 @@ import org.apache.logging.log4j.Logger;
import org.qora.account.PrivateKeyAccount;
import org.qora.block.Block.ValidationResult;
import org.qora.controller.Controller;
import org.qora.data.account.ForgingAccountData;
import org.qora.data.block.BlockData;
import org.qora.data.transaction.TransactionData;
import org.qora.repository.BlockRepository;
@ -45,11 +46,6 @@ public class BlockGenerator extends Thread {
public void run() {
Thread.currentThread().setName("BlockGenerator");
List<byte[]> generatorKeys = Settings.getInstance().getGeneratorKeys();
// No generators?
if (generatorKeys.isEmpty())
return;
try (final Repository repository = RepositoryManager.getRepository()) {
if (Settings.getInstance().getWipeUnconfirmedOnStart()) {
// Wipe existing unconfirmed transactions
@ -63,32 +59,37 @@ public class BlockGenerator extends Thread {
repository.saveChanges();
}
List<PrivateKeyAccount> generators = generatorKeys.stream().map(key -> new PrivateKeyAccount(repository, key)).collect(Collectors.toList());
// Going to need this a lot...
BlockRepository blockRepository = repository.getBlockRepository();
Block previousBlock = null;
List<Block> newBlocks = null;
List<Block> newBlocks = new ArrayList<>();
while (running) {
// Check blockchain hasn't changed
BlockData lastBlockData = blockRepository.getLastBlock();
if (previousBlock == null || !Arrays.equals(previousBlock.getSignature(), lastBlockData.getSignature())) {
previousBlock = new Block(repository, lastBlockData);
newBlocks = null;
newBlocks.clear();
}
// Do we need to build a potential new blocks?
if (newBlocks == null) {
// 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);
// Do we need to build any potential new blocks?
List<ForgingAccountData> forgingAccountsData = repository.getAccountRepository().getForgingAccounts();
List<PrivateKeyAccount> forgingAccounts = forgingAccountsData.stream().map(accountData -> new PrivateKeyAccount(repository, accountData.getSeed())).collect(Collectors.toList());
// Discard accounts we have blocks for
forgingAccounts.removeIf(account -> newBlocks.stream().anyMatch(newBlock -> newBlock.getGenerator().getAddress().equals(account.getAddress())));
for (PrivateKeyAccount generator : forgingAccounts) {
// First block does the AT heavy-lifting
if (newBlocks.isEmpty()) {
Block newBlock = new Block(repository, previousBlock.getBlockData(), generator);
newBlocks.add(newBlock);
} else {
// The blocks for other generators require less effort...
for (int i = 1; i < generators.size(); ++i)
newBlocks.add(newBlock.regenerate(generators.get(i)));
Block newBlock = newBlocks.get(0);
newBlocks.add(newBlock.regenerate(generator));
}
}
// Make sure we're the only thread modifying the blockchain
@ -131,7 +132,7 @@ public class BlockGenerator extends Thread {
if (validationResult != ValidationResult.OK) {
// No longer valid? Report and discard
LOGGER.error("Valid, generated block now invalid '" + validationResult.name() + "' after adding unconfirmed transactions?");
newBlock = null;
newBlocks.clear();
break generation;
}
@ -146,7 +147,7 @@ public class BlockGenerator extends Thread {
} catch (DataException e) {
// Unable to process block - report and discard
LOGGER.error("Unable to process newly generated block?", e);
newBlock = null;
newBlocks.clear();
}
} finally {
blockchainLock.unlock();

View File

@ -0,0 +1,29 @@
package org.qora.data.account;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
// All properties to be converted to JSON via JAXB
@XmlAccessorType(XmlAccessType.FIELD)
public class ForgingAccountData {
// Properties
protected byte[] seed;
// Constructors
// For JAXB
protected ForgingAccountData() {
}
public ForgingAccountData(byte[] seed) {
this.seed = seed;
}
// Getters/Setters
public byte[] getSeed() {
return this.seed;
}
}

View File

@ -4,6 +4,7 @@ import java.util.List;
import org.qora.data.account.AccountBalanceData;
import org.qora.data.account.AccountData;
import org.qora.data.account.ForgingAccountData;
import org.qora.data.account.ProxyForgerData;
public interface AccountRepository {
@ -91,4 +92,12 @@ public interface AccountRepository {
public void delete(byte[] forgerPublickey, String recipient) throws DataException;
// Forging accounts used by BlockGenerator
public List<ForgingAccountData> getForgingAccounts() throws DataException;
public void save(ForgingAccountData forgingAccountData) throws DataException;
public int delete(byte[] forgingAccountSeed) throws DataException;
}

View File

@ -11,6 +11,7 @@ import java.util.stream.Collectors;
import org.qora.data.account.AccountBalanceData;
import org.qora.data.account.AccountData;
import org.qora.data.account.ForgingAccountData;
import org.qora.data.account.ProxyForgerData;
import org.qora.repository.AccountRepository;
import org.qora.repository.DataException;
@ -401,4 +402,45 @@ public class HSQLDBAccountRepository implements AccountRepository {
}
}
// Forging accounts used by BlockGenerator
public List<ForgingAccountData> getForgingAccounts() throws DataException {
List<ForgingAccountData> forgingAccounts = new ArrayList<>();
try (ResultSet resultSet = this.repository.checkedExecute("SELECT forger_seed FROM ForgingAccounts")) {
if (resultSet == null)
return forgingAccounts;
do {
byte[] forgerSeed = resultSet.getBytes(1);
forgingAccounts.add(new ForgingAccountData(forgerSeed));
} while (resultSet.next());
return forgingAccounts;
} catch (SQLException e) {
throw new DataException("Unable to find forging accounts in repository", e);
}
}
public void save(ForgingAccountData forgingAccountData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("ForgingAccounts");
saveHelper.bind("forger_seed", forgingAccountData.getSeed());
try {
saveHelper.execute(this.repository);
} catch (SQLException e) {
throw new DataException("Unable to save forging account into repository", e);
}
}
public int delete(byte[] forgingAccountSeed) throws DataException {
try {
return this.repository.delete("ForgingAccounts", "forger_seed = ?", forgingAccountSeed);
} catch (SQLException e) {
throw new DataException("Unable to delete forging account from repository", e);
}
}
}

View File

@ -719,6 +719,12 @@ public class HSQLDBDatabaseUpdates {
stmt.execute("CREATE INDEX ProxyForgersProxyPublicKeyIndex ON ProxyForgers (proxy_public_key)");
break;
case 40:
// Stash of private keys used for generating blocks. These should be proxy keys!
stmt.execute("CREATE TYPE QoraKeySeed AS VARBINARY(32)");
stmt.execute("CREATE TABLE ForgingAccounts (forger_seed QoraKeySeed NOT NULL, PRIMARY KEY (forger_seed))");
break;
default:
// nothing to do
return false;

View File

@ -5,7 +5,6 @@ import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
@ -13,7 +12,6 @@ import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.transform.stream.StreamSource;
import org.apache.logging.log4j.LogManager;
@ -21,7 +19,6 @@ import org.apache.logging.log4j.Logger;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
import org.qora.api.Base58TypeAdapter;
import org.qora.block.BlockChain;
// All properties to be converted to JSON via JAXB
@ -65,10 +62,6 @@ public class Settings {
private String blockchainConfig = "blockchain.json";
private boolean useBitcoinTestNet = false;
// Private keys to use for generating blocks
@XmlJavaTypeAdapter(type = byte[].class, value = Base58TypeAdapter.class)
private List<byte[]> generatorKeys;
// Constructors
private Settings() {
@ -235,8 +228,4 @@ public class Settings {
return this.useBitcoinTestNet;
}
public List<byte[]> getGeneratorKeys() {
return this.generatorKeys;
}
}