diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b9d29b204..7f76cd89e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -45,6 +45,7 @@ jobs:
tag: ${{ github.ref }}
overwrite: true
prerelease: true
+ draft: true
body: |
This is BRS version ${{github.ref}}
diff --git a/conf/brs-default.properties b/conf/brs-default.properties
index 4a6f7e837..d42f01bde 100644
--- a/conf/brs-default.properties
+++ b/conf/brs-default.properties
@@ -41,12 +41,10 @@ P2P.UPnP = yes
P2P.myPlatform = PC
# A list of peer addresses / host names, separated by '; ' used for faster P2P networking bootstrap.
-# TODO: document what is taken if not set
-
-P2P.BootstrapPeers = node.burst.gittam.de; 80.122.157.25; 138.201.247.112; 35.194.43.230; 24.179.36.64; 136.243.54.19; 160.16.52.180; [2a02:c207:2016:1984:0:0:0:1]; wallet.smit.pro; wallet.logg.coffee; 37.205.11.73; 47.95.232.75; 93.73.103.148; wallet.burst.cryptoguru.org; 51.15.95.163; antigo.hopto.org; 61.171.0.50; burst.megash.it; wi3jodsj.dynu.net; 176.31.105.109; 24.51.189.190; burstwallet2.ddns.net; burstsecurity.com; 81.83.5.50; bloodreaver.no-ip.biz; burstnode.devtrue.net; 185.203.117.157; 138.201.159.96; 52.65.42.199; 159.69.21.174; home.schmiemann.online; 221.241.92.2; peer.wallet.burstcoin.ml; [2a03:3b40:100:0:0:0:1:52]; tompkins.ddns.net; 188.68.41.245; wallet.creepminer.net; burstpool.cloud; 173.249.1.215; 91.143.92.133:18123; 80.71.133.195; redfox.org; 146.247.237.139; 77.70.109.7; 117.48.195.3; 144.76.92.28; bangalore.burstsecurity.com; wallet.burstcoin.asia; aa.storj.eu; 195.201.124.43; wallet.daggeringcats.com; 84.113.147.25; 81.169.131.111; 5.39.93.90; 198.100.149.133; 148.251.78.147; 212.32.255.2; 95.216.142.146; 84.72.183.85; 75.100.126.227; 35.207.44.92; 107.150.6.121; burst.sagichdir.net; [2a07:5741:0:b12:0:0:0:1]; 108.238.244.144; 51.15.219.28; 81.217.76.37; bibenwei.com; 82.192.26.82; logg.coffee; burst-fi.megash.it; 87.227.172.104; 45.76.6.179; chorca.com; 87.98.244.116; 5.189.177.212; 207.38.188.197; 83.87.55.43; 151.248.189.93; 45.32.114.77; 95.165.132.145; 91.143.92.133; 85.217.171.59; 47.33.52.184; www.ollb.de; [2001:19f0:4400:6c35:5400:1ff:fe54:840]; 83.170.94.221; kartoffel.space:8000; 185.203.116.80; 39.106.178.146; 62.210.254.125; 80.65.49.246; 88.198.32.19; carless.ddns.net; 89.166.9.172; 45.77.250.34; 144.76.45.125; 95.216.0.50; 185.185.27.163; 75.100.126.22875.100.126.229; [2a07:5741:0:f8c:0:0:0:1]; 5.103.129.103; 213.32.102.141; 63.251.20.214
+P2P.BootstrapPeers = 212.98.92.236;177; 108.61.251.202; 177.153.50.43; 157.90.168.219; 212.98.92.236; 162.55.177.176; 190.15.195.118; 24.96.113.8; 88.64.234.237; 137.135.203.145; 70.108.6.237; 144.91.84.164; 213.32.102.141; 5.196.65.184; 89.163.239.219; 165.227.36.71
# These peers will always be sent rebroadcast transactions. They are also automatically added to P2P.BootstrapPeers, so no need for duplicates.
-P2P.rebroadcastTo = 77.66.65.240; 78.46.245.194; 94.130.190.156; 75.100.126.230; 75.100.126.226; 212.98.92.236;
+P2P.rebroadcastTo = 77.66.65.240; 172.67.142.5; 216.128.181.211; 77.68.73.180
# Connect to this many bootstrap connection peers before using the peer database to get connected faster. Please be aware, that higher != better (3-5 are usually good values) Set to 0 or comment out to disable.
P2P.NumBootstrapConnections = 3
@@ -57,6 +55,9 @@ P2P.BlacklistedPeers =
# Maintain active connections with at least that many peers. Also more != better (you want good peers, not just many)
P2P.MaxConnections = 20
+# Maximum number of blocks sent to other peers in a single request
+P2P.MaxBlocks = 720
+
# Use Peers Database? (Only if not in Offline mode)
P2P.usePeersDb = yes
# Save known peers in the PeersDB? (only if P2P.usePeersDB is true)
@@ -156,15 +157,14 @@ JETTY.API.GZIPFilter.methods = "GET, POST"
JETTY.API.GZIPFilter.bufferSize = 8192
JETTY.API.GZIPFilter.minGzipSize = 0
-# Developers or maintenance only! Enable API requests used for
-# blockchain and database manipulation. If this is enabled and your
-# wallet is public, you are very vulnerable.
-API.Debug = off
-
# Hosts or subnets from which to allow http/json API requests, if enabled.
# List delimited by ';', IPv4/IPv6 possible, default: localhost
API.allowed = 127.0.0.1; localhost; [0:0:0:0:0:0:0:1];
+# Key list to access the admin API requests, uncomment and replace with your own keys
+# delimited by ';' if more than one key should be available
+#API.adminKeyList = e673529588638d2129af1e0528a1642cf2e0c180
+
# Does the API accept additional/redundant parameters in an API call?
# default is no (Wallet accepts only params specified for given call)
# enable this if you have a sloppy client interacting, but please be aware that this
@@ -198,9 +198,14 @@ API.SSL = off
# Enforce requests that require POST to only be accepted when submitted as POST.
API.ServerEnforcePOST = yes
-# keystore file and password, required if uiSSL or apiSSL are enabled.
+# Your keystore file and password, required if uiSSL or apiSSL are enabled.
API.SSL_keyStorePath = keystore
API.SSL_keyStorePassword = password
+# If you use https://certbot.eff.org/ to issue your certificate, provide below the path for your keys.
+# BRS will automatically create the keystore file using the password above and will reload it weekly.
+# Make sure you configure certbot to renew your certificate automatically so you don't need to worry about it.
+# Note, you need 'openssl' on your path for this to work, most Linux distributions have it already.
+# API.SSL_letsencryptPath = /etc/letsencrypt/live/yourdomain.com
#### DATABASE ####
diff --git a/html/ui/html/modals/send_money.html b/html/ui/html/modals/send_money.html
index 09610a30f..bd662cd46 100644
--- a/html/ui/html/modals/send_money.html
+++ b/html/ui/html/modals/send_money.html
@@ -71,7 +71,7 @@
Send BURST
-
+
diff --git a/pom.xml b/pom.xml
index a2a00b9bb..ea74f479b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
burstcoinburstcoin
- 3.0.0
+ 3.0.1Burstcoin Reference Softwarehttps://github.com/burst-apps-team/burstcoin
diff --git a/src/brs/BlockchainProcessorImpl.java b/src/brs/BlockchainProcessorImpl.java
index b56b90772..4191b464d 100644
--- a/src/brs/BlockchainProcessorImpl.java
+++ b/src/brs/BlockchainProcessorImpl.java
@@ -779,10 +779,12 @@ public List popOffTo(int height) {
@Override
public void fullReset() {
- blockDb.deleteAll(false);
dbCacheManager.flushCache();
downloadCache.resetCache();
+ blockDb.deleteAll(false);
addGenesisBlock();
+ dbCacheManager.flushCache();
+ downloadCache.resetCache();
}
void setGetMoreBlocks(boolean getMoreBlocks) {
diff --git a/src/brs/Burst.java b/src/brs/Burst.java
index 70673fc58..26ede336e 100644
--- a/src/brs/Burst.java
+++ b/src/brs/Burst.java
@@ -30,6 +30,7 @@
import brs.util.LoggerConfigurator;
import brs.util.ThreadPool;
import brs.util.Time;
+import burst.kit.util.BurstKitUtils;
import io.grpc.Server;
import org.apache.commons.cli.CommandLine;
@@ -46,7 +47,7 @@
public final class Burst {
- public static final Version VERSION = Version.parse("v3.0.0");
+ public static final Version VERSION = Version.parse("v3.0.1");
public static final String APPLICATION = "BRS";
@@ -109,7 +110,7 @@ private static PropertyService loadProperties(String confFolder) {
} catch (IOException e) {
logger.info("Custom user properties file {} not loaded", PROPERTIES_NAME);
}
-
+
return new PropertyServiceImpl(properties);
}
@@ -175,6 +176,9 @@ private static void loadWallet(PropertyService propertyService) {
try {
long startTime = System.currentTimeMillis();
+
+ // Additional valid prefix
+ BurstKitUtils.addAddressPrefix(propertyService.getBoolean(Props.DEV_TESTNET) ? "TS" : "S");
final TimeService timeService = new TimeServiceImpl();
diff --git a/src/brs/BurstGUI.java b/src/brs/BurstGUI.java
index 8236570c7..42d8f5e80 100644
--- a/src/brs/BurstGUI.java
+++ b/src/brs/BurstGUI.java
@@ -92,6 +92,20 @@ public BurstGUI() {
}
if(lafc!=null) {
try {
+ UIManager.put( "control", new Color( 128, 128, 128) );
+ UIManager.put( "info", new Color(128,128,128) );
+ UIManager.put( "nimbusBase", new Color( 18, 30, 49) );
+ UIManager.put( "nimbusAlertYellow", new Color( 248, 187, 0) );
+ UIManager.put( "nimbusDisabledText", new Color( 128, 128, 128) );
+ UIManager.put( "nimbusFocus", new Color(115,164,209) );
+ UIManager.put( "nimbusGreen", new Color(176,179,50) );
+ UIManager.put( "nimbusInfoBlue", new Color( 66, 139, 221) );
+ UIManager.put( "nimbusLightBackground", new Color( 18, 30, 49) );
+ UIManager.put( "nimbusOrange", new Color(191,98,4) );
+ UIManager.put( "nimbusRed", new Color(169,46,34) );
+ UIManager.put( "nimbusSelectedText", new Color( 255, 255, 255) );
+ UIManager.put( "nimbusSelectionBackground", new Color( 104, 93, 156) );
+ UIManager.put( "text", new Color( 230, 230, 230) );
LookAndFeel laf = (LookAndFeel) lafc.getConstructor().newInstance();
UIManager.setLookAndFeel(laf);
} catch (Exception e) {
@@ -231,7 +245,7 @@ private TrayIcon createTrayIcon() {
toolBar.add(openPhoenixButton);
toolBar.add(openClassicButton);
toolBar.add(editConfButton);
- if(Burst.getPropertyService().getBoolean(Props.API_DEBUG) || Burst.getPropertyService().getBoolean(Props.DEV_TESTNET)) {
+ if(Burst.getPropertyService().getBoolean(Props.DEV_TESTNET)) {
toolBar.add(popOff10Button);
toolBar.add(popOff100Button);
// toolBar.add(popOffMaxButton);
diff --git a/src/brs/TransactionType.java b/src/brs/TransactionType.java
index 3af852e02..a128e5faf 100644
--- a/src/brs/TransactionType.java
+++ b/src/brs/TransactionType.java
@@ -230,8 +230,8 @@ public final boolean applyUnconfirmed(Transaction transaction, Account senderAcc
}
accountService.addToUnconfirmedBalanceNQT(senderAccount, -totalAmountNQT);
if (!applyAttachmentUnconfirmed(transaction, senderAccount)) {
- if (logger.isTraceEnabled()) {
- logger.trace("!applyAttachmentUnconfirmed({}, {})", transaction, senderAccount.getId());
+ if (logger.isDebugEnabled()) {
+ logger.debug("!applyAttachmentUnconfirmed({}, {})", transaction, senderAccount.getId());
}
accountService.addToUnconfirmedBalanceNQT(senderAccount, totalAmountNQT);
return false;
@@ -1963,6 +1963,12 @@ public String getDescription() {
Attachment.CommitmentAdd parseAttachment(JsonObject attachmentData) {
return new Attachment.CommitmentAdd(attachmentData);
}
+
+ protected Long calculateAttachmentTotalAmountNQT(Transaction transaction) {
+ CommitmentAdd commitmentAdd = (CommitmentAdd) transaction.getAttachment();
+ Long totalAmountNQT = commitmentAdd.getAmountNQT();
+ return totalAmountNQT;
+ }
@Override
boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) {
diff --git a/src/brs/db/sql/Db.java b/src/brs/db/sql/Db.java
index e96dcdd9c..671e4d02f 100644
--- a/src/brs/db/sql/Db.java
+++ b/src/brs/db/sql/Db.java
@@ -43,6 +43,8 @@ public final class Db {
private static DBCacheManagerImpl dbCacheManager;
+ private static Flyway flyway;
+
public static void init(PropertyService propertyService, DBCacheManagerImpl dbCacheManager) {
Db.dbCacheManager = dbCacheManager;
@@ -76,13 +78,11 @@ public static void init(PropertyService propertyService, DBCacheManagerImpl dbCa
FluentConfiguration flywayBuilder = Flyway.configure()
.dataSource(dbUrl, dbUsername, dbPassword)
.baselineOnMigrate(true);
- boolean runFlyway = false;
switch (dialect) {
case MYSQL:
case MARIADB:
flywayBuilder.locations("classpath:/db/migration_mariadb");
- runFlyway = true;
config.setAutoCommit(true);
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "512");
@@ -120,7 +120,6 @@ protected synchronized void initialize() throws SQLException {
case H2:
Class.forName("org.h2.Driver");
flywayBuilder.locations("classpath:/db/migration_h2");
- runFlyway = true;
config.setAutoCommit(true);
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
@@ -132,17 +131,24 @@ protected synchronized void initialize() throws SQLException {
break;
}
- cp = new HikariDataSource(config);
+ cp = new HikariDataSource(config);
- if (runFlyway) {
- logger.info("Running flyway migration");
- Flyway flyway = flywayBuilder.load();
- flyway.migrate();
- }
+ logger.info("Running flyway migration");
+ flyway = flywayBuilder.load();
+ flyway.migrate();
} catch (Exception e) {
throw new RuntimeException(e.toString(), e);
}
}
+
+ public static void clean() {
+ try {
+ flyway.clean();
+ flyway.migrate();
+ } catch (Exception e) {
+ logger.error(e.getMessage());
+ }
+ }
private Db() {
} // never
@@ -185,6 +191,24 @@ public static void shutdown() {
cp.close();
}
}
+
+ public static void backup(String filename) {
+ if (dialect == SQLDialect.H2) {
+ logger.info("Database backup to {} started, it might take a while.", filename);
+ try ( Connection con = cp.getConnection(); Statement stmt = con.createStatement() ) {
+ stmt.execute("BACKUP TO '" + filename + "'");
+ }
+ catch (SQLException e) {
+ logger.info(e.toString(), e);
+ }
+ finally {
+ logger.info("Database backup completed, file {}.", filename);
+ }
+ }
+ else {
+ logger.error("Backup not yet implemented for {}", dialect.toString());
+ }
+ }
private static Connection getPooledConnection() throws SQLException {
return cp.getConnection();
diff --git a/src/brs/db/sql/SqlBlockDb.java b/src/brs/db/sql/SqlBlockDb.java
index 5596e4be9..fb5dc4f62 100644
--- a/src/brs/db/sql/SqlBlockDb.java
+++ b/src/brs/db/sql/SqlBlockDb.java
@@ -9,14 +9,10 @@
import org.jooq.DeleteQuery;
import org.jooq.Record;
import org.jooq.SelectQuery;
-import org.jooq.impl.TableImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
import java.util.Optional;
import static brs.schema.Tables.BLOCK;
@@ -181,29 +177,7 @@ public void deleteAll(boolean force) {
return;
}
logger.info("Deleting blockchain...");
- Db.useDSLContext(ctx -> {
- List tables = new ArrayList<>(Arrays.asList(brs.schema.Tables.ACCOUNT,
- brs.schema.Tables.ACCOUNT_ASSET, brs.schema.Tables.ALIAS, brs.schema.Tables.ALIAS_OFFER,
- brs.schema.Tables.ASK_ORDER, brs.schema.Tables.ASSET, brs.schema.Tables.ASSET_TRANSFER,
- brs.schema.Tables.AT, brs.schema.Tables.AT_STATE, brs.schema.Tables.BID_ORDER,
- brs.schema.Tables.BLOCK, brs.schema.Tables.ESCROW, brs.schema.Tables.ESCROW_DECISION,
- brs.schema.Tables.GOODS, brs.schema.Tables.PEER, brs.schema.Tables.PURCHASE,
- brs.schema.Tables.PURCHASE_FEEDBACK, brs.schema.Tables.PURCHASE_PUBLIC_FEEDBACK,
- brs.schema.Tables.REWARD_RECIP_ASSIGN, brs.schema.Tables.SUBSCRIPTION,
- brs.schema.Tables.TRADE, brs.schema.Tables.TRANSACTION,
- brs.schema.Tables.UNCONFIRMED_TRANSACTION));
- for (TableImpl> table : tables) {
- try {
- ctx.truncate(table).execute();
- } catch (org.jooq.exception.DataAccessException e) {
- if (force) {
- logger.trace("exception during truncate {0}", table, e);
- } else {
- throw e;
- }
- }
- }
- });
+ Db.clean();
}
@Override
diff --git a/src/brs/http/API.java b/src/brs/http/API.java
index 7ded53c94..8d68e2e3d 100644
--- a/src/brs/http/API.java
+++ b/src/brs/http/API.java
@@ -9,6 +9,7 @@
import brs.services.*;
import brs.util.Subnet;
import brs.util.ThreadPool;
+
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
import org.eclipse.jetty.rewrite.handler.RewriteRegexRule;
import org.eclipse.jetty.rewrite.handler.Rule;
@@ -33,19 +34,20 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
public final class API {
private static final Logger logger = LoggerFactory.getLogger(API.class);
- @SuppressWarnings("squid:S1075")
private static final String API_PATH = "/burst";
- @SuppressWarnings("squid:S1075")
public static final String API_TEST_PATH = "/api-doc";
private final Server apiServer;
-
+
public API(TransactionProcessor transactionProcessor,
Blockchain blockchain, BlockchainProcessor blockchainProcessor, ParameterService parameterService,
AccountService accountService, AliasService aliasService,
@@ -74,7 +76,7 @@ public API(TransactionProcessor transactionProcessor,
else {
allowedBotHosts = null;
}
-
+
boolean enableAPIServer = propertyService.getBoolean(Props.API_SERVER);
if (enableAPIServer) {
final String host = propertyService.getString(Props.API_LISTEN);
@@ -90,8 +92,33 @@ public API(TransactionProcessor transactionProcessor,
httpsConfig.setSecurePort(port);
httpsConfig.addCustomizer(new SecureRequestCustomizer());
SslContextFactory sslContextFactory = new SslContextFactory.Server();
+
sslContextFactory.setKeyStorePath(propertyService.getString(Props.API_SSL_KEY_STORE_PATH));
sslContextFactory.setKeyStorePassword(propertyService.getString(Props.API_SSL_KEY_STORE_PASSWORD));
+
+ String letsencryptPath = propertyService.getString(Props.API_SSL_LETSENCRYPT_PATH);
+ if(letsencryptPath != null && letsencryptPath.length() > 0) {
+ try {
+ letsencryptToPkcs12(letsencryptPath, propertyService.getString(Props.API_SSL_KEY_STORE_PATH), propertyService.getString(Props.API_SSL_KEY_STORE_PASSWORD));
+ }
+ catch (Exception e) {
+ logger.error(e.getMessage());
+ }
+
+ // Reload the certificate every week, in case it was renewed
+ ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+ Runnable reloadCert = () -> {
+ try {
+ letsencryptToPkcs12(letsencryptPath, propertyService.getString(Props.API_SSL_KEY_STORE_PATH), propertyService.getString(Props.API_SSL_KEY_STORE_PASSWORD));
+ sslContextFactory.reload(consumer -> logger.info("SSL keystore from letsencrypt reloaded."));
+ }
+ catch (Exception e) {
+ logger.error(e.getMessage());
+ }
+ };
+ scheduler.scheduleWithFixedDelay(reloadCert, 7, 7, TimeUnit.DAYS);
+ }
+
sslContextFactory.setExcludeCipherSuites("SSL_RSA_WITH_DES_CBC_SHA",
"SSL_DHE_RSA_WITH_DES_CBC_SHA",
"SSL_DHE_DSS_WITH_DES_CBC_SHA",
@@ -135,7 +162,8 @@ public API(TransactionProcessor transactionProcessor,
APIServlet apiServlet = new APIServlet(transactionProcessor, blockchain, blockchainProcessor, parameterService,
accountService, aliasService, assetExchange, escrowService, digitalGoodsStoreService,
subscriptionService, atService, timeService, economicClustering, transactionService, blockService, generator, propertyService,
- apiTransactionManager, feeSuggestionCalculator, deepLinkQRCodeGenerator, indirectIncomingService, allowedBotHosts);
+ apiTransactionManager, feeSuggestionCalculator, deepLinkQRCodeGenerator, indirectIncomingService,
+ allowedBotHosts);
ServletHolder apiServletHolder = new ServletHolder(apiServlet);
apiHandler.addServlet(apiServletHolder, API_PATH);
@@ -199,7 +227,17 @@ public API(TransactionProcessor transactionProcessor,
}
}
+
+ private void letsencryptToPkcs12(String letsencryptPath, String p12File, String password) throws Exception {
+ // TODO: check if there is a way for us to use directly the PEM files and not need to convert this way
+ logger.info("Generating {} from {}", p12File, letsencryptPath);
+ String cmd = "openssl pkcs12 -export -in " + letsencryptPath + "/fullchain.pem "
+ + "-inkey " + letsencryptPath + "/privkey.pem -out " + p12File + " -password pass:" + password;
+ Process process = Runtime.getRuntime().exec(cmd);
+ process.waitFor();
+ }
+
private String regexpEscapeUrl(String url) {
return url.replace("/", "\\/");
}
diff --git a/src/brs/http/APIServlet.java b/src/brs/http/APIServlet.java
index 309550031..587b02df3 100644
--- a/src/brs/http/APIServlet.java
+++ b/src/brs/http/APIServlet.java
@@ -160,11 +160,11 @@ public APIServlet(TransactionProcessor transactionProcessor, Blockchain blockcha
map.put("generateDeeplink", GenerateDeeplink.instance);
map.put("generateDeeplinkQRCode", GenerateDeeplinkQR.instance);
- if (propertyService.getBoolean(Props.API_DEBUG) || propertyService.getBoolean(Props.DEV_TESTNET)) {
- map.put("clearUnconfirmedTransactions", new ClearUnconfirmedTransactions(transactionProcessor));
- map.put("fullReset", new FullReset(blockchainProcessor));
- map.put("popOff", new PopOff(blockchainProcessor, blockchain, blockService));
- }
+ // Calls that require an admin api key:
+ map.put("clearUnconfirmedTransactions", new ClearUnconfirmedTransactions(transactionProcessor, propertyService));
+ map.put("fullReset", new FullReset(blockchainProcessor, propertyService));
+ map.put("popOff", new PopOff(blockchainProcessor, blockchain, blockService, propertyService));
+ map.put("backupDB", new BackupDB(propertyService));
apiRequestHandlers = Collections.unmodifiableMap(map);
}
diff --git a/src/brs/http/APITag.java b/src/brs/http/APITag.java
index 267e02fe7..525a4575d 100644
--- a/src/brs/http/APITag.java
+++ b/src/brs/http/APITag.java
@@ -5,7 +5,7 @@ public enum APITag {
ACCOUNTS("Accounts"), ALIASES("Aliases"), AE("Asset Exchange"), CREATE_TRANSACTION("Create Transaction"),
BLOCKS("Blocks"), DGS("Digital Goods Store"), INFO("Server Info"), MESSAGES("Messages"),
MINING("Mining"), TRANSACTIONS("Transactions"), TOKENS("Tokens"), VS("Voting System"), AT("Automated Transaction"),
- FEES("Fees"), UTILS("Utils"), DEBUG("Debug"), PEER_INFO("Server Peer Info");
+ FEES("Fees"), UTILS("Utils"), ADMIN("Administrator"), PEER_INFO("Server Peer Info");
private final String displayName;
diff --git a/src/brs/http/APITestServlet.java b/src/brs/http/APITestServlet.java
index f3390c99e..a76943f95 100644
--- a/src/brs/http/APITestServlet.java
+++ b/src/brs/http/APITestServlet.java
@@ -153,12 +153,12 @@ public class APITestServlet extends HttpServlet {
private final Set allowedBotHosts;
private final List requestTypes;
- private final Map apiRequestHandlers;
+ private final Map apiRequestHandlers = new HashMap();
private final SortedMap> requestTags;
public APITestServlet(APIServlet apiServlet, Set allowedBotHosts) {
this.allowedBotHosts = allowedBotHosts;
- apiRequestHandlers = apiServlet.apiRequestHandlers;
+ apiRequestHandlers.putAll(apiServlet.apiRequestHandlers);
requestTags = buildRequestTags();
requestTypes = new ArrayList<>(apiRequestHandlers.keySet());
Collections.sort(requestTypes);
@@ -233,7 +233,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
Set taggedTypes = requestTags.get(requestTag);
for (String type : (taggedTypes != null ? taggedTypes : requestTypes)) {
requestHandler = apiRequestHandlers.get(type);
- writer.print(form(type, false, requestHandler.getClass().getName(), apiRequestHandlers.get(type).getParameters(),
+ List parameters = apiRequestHandlers.get(type).getParameters();
+ writer.print(form(type, false, requestHandler.getClass().getName(), parameters,
apiRequestHandlers.get(type).requirePost()));
bufJSCalls.append("apiCalls.push(\"").append(type).append("\");\n");
}
diff --git a/src/brs/http/BackupDB.java b/src/brs/http/BackupDB.java
new file mode 100644
index 000000000..84ed15b1d
--- /dev/null
+++ b/src/brs/http/BackupDB.java
@@ -0,0 +1,55 @@
+package brs.http;
+
+import static brs.http.common.Parameters.FILENAME_PARAMETER;
+import static brs.http.JSONResponses.ERROR_NOT_ALLOWED;
+import static brs.http.common.Parameters.API_KEY_PARAMETER;
+import static brs.http.common.ResultFields.ERROR_RESPONSE;
+
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+import brs.db.sql.Db;
+import brs.props.PropertyService;
+import brs.props.Props;
+
+final class BackupDB extends APIServlet.JsonRequestHandler {
+
+ private final List apiAdminKeyList;
+
+ BackupDB(PropertyService propertyService) {
+ super(new APITag[] {APITag.ADMIN}, FILENAME_PARAMETER, API_KEY_PARAMETER);
+
+ apiAdminKeyList = propertyService.getStringList(Props.API_ADMIN_KEY_LIST);
+ }
+
+ @Override
+ JsonElement processRequest(HttpServletRequest req) {
+
+ JsonObject response = new JsonObject();
+ String filename = req.getParameter(FILENAME_PARAMETER);
+ String apiKey = req.getParameter(API_KEY_PARAMETER);
+
+ if(!apiAdminKeyList.contains(apiKey)) {
+ return ERROR_NOT_ALLOWED;
+ }
+
+ if(filename == null || filename.length() == 0) {
+ response.addProperty(ERROR_RESPONSE, "invalid filename");
+ return response;
+ }
+
+ Db.backup(filename);
+
+ return response;
+ }
+
+ @Override
+ final boolean requirePost() {
+ return true;
+ }
+
+}
diff --git a/src/brs/http/ClearUnconfirmedTransactions.java b/src/brs/http/ClearUnconfirmedTransactions.java
index f40241d20..b26330eab 100644
--- a/src/brs/http/ClearUnconfirmedTransactions.java
+++ b/src/brs/http/ClearUnconfirmedTransactions.java
@@ -1,25 +1,41 @@
package brs.http;
import brs.TransactionProcessor;
+import brs.props.PropertyService;
+import brs.props.Props;
+
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import javax.servlet.http.HttpServletRequest;
+import static brs.http.JSONResponses.ERROR_NOT_ALLOWED;
+import static brs.http.common.Parameters.API_KEY_PARAMETER;
import static brs.http.common.ResultFields.DONE_RESPONSE;
import static brs.http.common.ResultFields.ERROR_RESPONSE;
+import java.util.List;
+
public final class ClearUnconfirmedTransactions extends APIServlet.JsonRequestHandler {
private final TransactionProcessor transactionProcessor;
+
+ private final List apiAdminKeyList;
- ClearUnconfirmedTransactions(TransactionProcessor transactionProcessor) {
- super(new APITag[]{APITag.DEBUG});
+ ClearUnconfirmedTransactions(TransactionProcessor transactionProcessor, PropertyService propertyService) {
+ super(new APITag[]{APITag.ADMIN}, API_KEY_PARAMETER);
this.transactionProcessor = transactionProcessor;
+
+ apiAdminKeyList = propertyService.getStringList(Props.API_ADMIN_KEY_LIST);
}
@Override
JsonElement processRequest(HttpServletRequest req) {
+ String apiKey = req.getParameter(API_KEY_PARAMETER);
+ if(!apiAdminKeyList.contains(apiKey)) {
+ return ERROR_NOT_ALLOWED;
+ }
+
JsonObject response = new JsonObject();
try {
transactionProcessor.clearUnconfirmedTransactions();
diff --git a/src/brs/http/FullReset.java b/src/brs/http/FullReset.java
index 04af8dd56..e99c960fb 100644
--- a/src/brs/http/FullReset.java
+++ b/src/brs/http/FullReset.java
@@ -1,25 +1,41 @@
package brs.http;
import brs.BlockchainProcessor;
+import brs.props.PropertyService;
+import brs.props.Props;
+
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import javax.servlet.http.HttpServletRequest;
+import static brs.http.JSONResponses.ERROR_NOT_ALLOWED;
+import static brs.http.common.Parameters.API_KEY_PARAMETER;
import static brs.http.common.ResultFields.DONE_RESPONSE;
import static brs.http.common.ResultFields.ERROR_RESPONSE;
+import java.util.List;
+
public final class FullReset extends APIServlet.JsonRequestHandler {
private final BlockchainProcessor blockchainProcessor;
+
+ private final List apiAdminKeyList;
- FullReset(BlockchainProcessor blockchainProcessor) {
- super(new APITag[]{APITag.DEBUG});
+ FullReset(BlockchainProcessor blockchainProcessor, PropertyService propertyService) {
+ super(new APITag[]{APITag.ADMIN}, API_KEY_PARAMETER);
this.blockchainProcessor = blockchainProcessor;
+
+ apiAdminKeyList = propertyService.getStringList(Props.API_ADMIN_KEY_LIST);
}
@Override
JsonElement processRequest(HttpServletRequest req) {
+ String apiKey = req.getParameter(API_KEY_PARAMETER);
+ if(!apiAdminKeyList.contains(apiKey)) {
+ return ERROR_NOT_ALLOWED;
+ }
+
JsonObject response = new JsonObject();
try {
blockchainProcessor.fullReset();
diff --git a/src/brs/http/GetMyInfo.java b/src/brs/http/GetMyInfo.java
index b8aa508a8..63df0fd48 100644
--- a/src/brs/http/GetMyInfo.java
+++ b/src/brs/http/GetMyInfo.java
@@ -3,14 +3,20 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
+import java.util.UUID;
+
import javax.servlet.http.HttpServletRequest;
final class GetMyInfo extends APIServlet.JsonRequestHandler {
static final GetMyInfo instance = new GetMyInfo();
+
+ private final String uuid;
private GetMyInfo() {
super(new APITag[] {APITag.INFO});
+
+ uuid = UUID.randomUUID().toString();
}
@Override
@@ -19,6 +25,7 @@ JsonElement processRequest(HttpServletRequest req) {
JsonObject response = new JsonObject();
response.addProperty("host", req.getRemoteHost());
response.addProperty("address", req.getRemoteAddr());
+ response.addProperty("UUID", uuid);
return response;
}
diff --git a/src/brs/http/GetState.java b/src/brs/http/GetState.java
index 191c1af02..c3b9bb2cc 100644
--- a/src/brs/http/GetState.java
+++ b/src/brs/http/GetState.java
@@ -18,8 +18,12 @@
import javax.servlet.http.HttpServletRequest;
import static brs.http.common.Parameters.INCLUDE_COUNTS_PARAMETER;
+import static brs.http.JSONResponses.ERROR_NOT_ALLOWED;
+import static brs.http.common.Parameters.API_KEY_PARAMETER;
import static brs.http.common.ResultFields.TIME_RESPONSE;
+import java.util.List;
+
final class GetState extends APIServlet.JsonRequestHandler {
private final Blockchain blockchain;
@@ -30,10 +34,11 @@ final class GetState extends APIServlet.JsonRequestHandler {
private final ATService atService;
private final Generator generator;
private final PropertyService propertyService;
+ private final List apiAdminKeyList;
GetState(Blockchain blockchain, AssetExchange assetExchange, AccountService accountService, EscrowService escrowService,
AliasService aliasService, TimeService timeService, ATService atService, Generator generator, PropertyService propertyService) {
- super(new APITag[] {APITag.INFO}, INCLUDE_COUNTS_PARAMETER);
+ super(new APITag[] {APITag.INFO}, INCLUDE_COUNTS_PARAMETER, API_KEY_PARAMETER);
this.blockchain = blockchain;
this.assetExchange = assetExchange;
this.accountService = accountService;
@@ -42,6 +47,8 @@ final class GetState extends APIServlet.JsonRequestHandler {
this.atService = atService;
this.generator = generator;
this.propertyService = propertyService;
+
+ apiAdminKeyList = propertyService.getStringList(Props.API_ADMIN_KEY_LIST);
}
@Override
@@ -57,6 +64,11 @@ JsonElement processRequest(HttpServletRequest req) {
response.addProperty("totalMinedNQT", blockchain.getTotalMined());
if ("true".equalsIgnoreCase(req.getParameter(INCLUDE_COUNTS_PARAMETER))) {
+ String apiKey = req.getParameter(API_KEY_PARAMETER);
+ if(!apiAdminKeyList.contains(apiKey)) {
+ return ERROR_NOT_ALLOWED;
+ }
+
long totalEffectiveBalance = accountService.getAllAccountsBalance();
response.addProperty("totalEffectiveBalance", totalEffectiveBalance / Constants.ONE_BURST);
response.addProperty("totalEffectiveBalanceNQT", totalEffectiveBalance);
diff --git a/src/brs/http/PopOff.java b/src/brs/http/PopOff.java
index 1446b94d7..cc92fc7f5 100644
--- a/src/brs/http/PopOff.java
+++ b/src/brs/http/PopOff.java
@@ -3,6 +3,8 @@
import brs.Block;
import brs.Blockchain;
import brs.BlockchainProcessor;
+import brs.props.PropertyService;
+import brs.props.Props;
import brs.services.BlockService;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
@@ -11,6 +13,8 @@
import javax.servlet.http.HttpServletRequest;
import java.util.List;
+import static brs.http.JSONResponses.ERROR_NOT_ALLOWED;
+import static brs.http.common.Parameters.API_KEY_PARAMETER;
import static brs.http.common.Parameters.HEIGHT_PARAMETER;
import static brs.http.common.Parameters.NUM_BLOCKS_PARAMETER;
import static brs.http.common.ResultFields.BLOCKS_RESPONSE;
@@ -21,16 +25,24 @@ final class PopOff extends APIServlet.JsonRequestHandler {
private final BlockchainProcessor blockchainProcessor;
private final Blockchain blockchain;
private final BlockService blockService;
+ private final List apiAdminKeyList;
- PopOff(BlockchainProcessor blockchainProcessor, Blockchain blockchain, BlockService blockService) {
- super(new APITag[] {APITag.DEBUG}, NUM_BLOCKS_PARAMETER, HEIGHT_PARAMETER);
+ PopOff(BlockchainProcessor blockchainProcessor, Blockchain blockchain, BlockService blockService, PropertyService propertyService) {
+ super(new APITag[] {APITag.ADMIN}, NUM_BLOCKS_PARAMETER, HEIGHT_PARAMETER, API_KEY_PARAMETER);
this.blockchainProcessor = blockchainProcessor;
this.blockchain = blockchain;
this.blockService = blockService;
+
+ apiAdminKeyList = propertyService.getStringList(Props.API_ADMIN_KEY_LIST);
}
@Override
JsonElement processRequest(HttpServletRequest req) {
+
+ String apiKey = req.getParameter(API_KEY_PARAMETER);
+ if(!apiAdminKeyList.contains(apiKey)) {
+ return ERROR_NOT_ALLOWED;
+ }
JsonObject response = new JsonObject();
int numBlocks = 0;
diff --git a/src/brs/http/common/Parameters.java b/src/brs/http/common/Parameters.java
index e278c2721..f97415b08 100644
--- a/src/brs/http/common/Parameters.java
+++ b/src/brs/http/common/Parameters.java
@@ -45,6 +45,7 @@ private Parameters() {
public static final String SUBSCRIPTION_PARAMETER = "subscription";
public static final String ALIAS_URI_PARAMETER = "aliasURI";
public static final String NAME_PARAMETER = "name";
+ public static final String FILENAME_PARAMETER = "filename";
public static final String DESCRIPTION_PARAMETER = "description";
public static final String FREQUENCY_PARAMETER = "frequency";
public static final String AT_PARAMETER = "at";
@@ -128,6 +129,7 @@ private Parameters() {
public static final String DOMAIN_PARAMETER = "domain";
public static final String ACTION_PARAMETER = "action";
public static final String PAYLOAD_PARAMETER = "payload";
+ public static final String API_KEY_PARAMETER = "apiKey";
public static boolean isFalse(String text) {
return "false".equalsIgnoreCase(text);
diff --git a/src/brs/peer/GetAccountBalance.java b/src/brs/peer/GetAccountBalance.java
deleted file mode 100644
index 263057c31..000000000
--- a/src/brs/peer/GetAccountBalance.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package brs.peer;
-
-import brs.Account;
-import brs.services.AccountService;
-import brs.util.Convert;
-import brs.util.JSON;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-
-/**
- * @deprecated This call is no longer made by the other peers so will soon be removed.
- */
-@Deprecated
-public class GetAccountBalance implements PeerServlet.PeerRequestHandler {
-
- private final AccountService accountService;
-
- static final String ACCOUNT_ID_PARAMETER_FIELD = "account";
- static final String BALANCE_NQT_RESPONSE_FIELD = "balanceNQT";
-
- @Deprecated
- GetAccountBalance(AccountService accountService) {
- this.accountService = accountService;
- }
-
- @Override
- public JsonElement processRequest(JsonObject request, Peer peer) {
-
- JsonObject response = new JsonObject();
-
- Long accountId = Convert.parseAccountId(JSON.getAsString(request.get(ACCOUNT_ID_PARAMETER_FIELD)));
- Account account = accountService.getAccount(accountId);
- if (account != null) {
- response.addProperty(BALANCE_NQT_RESPONSE_FIELD, Convert.toUnsignedLong(account.getBalanceNQT()));
- } else {
- response.addProperty(BALANCE_NQT_RESPONSE_FIELD, "0");
- }
-
- return response;
- }
-}
diff --git a/src/brs/peer/GetAccountRecentTransactions.java b/src/brs/peer/GetAccountRecentTransactions.java
deleted file mode 100644
index d0e7749f9..000000000
--- a/src/brs/peer/GetAccountRecentTransactions.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package brs.peer;
-
-import brs.Account;
-import brs.Blockchain;
-import brs.Transaction;
-import brs.services.AccountService;
-import brs.util.Convert;
-import brs.util.JSON;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-
-/**
- * @deprecated This call is no longer made by the other peers so will soon be removed.
- */
-@Deprecated
-public class GetAccountRecentTransactions implements PeerServlet.PeerRequestHandler {
-
- private final AccountService accountService;
- private final Blockchain blockchain;
-
- GetAccountRecentTransactions(AccountService accountService, Blockchain blockchain) {
- this.accountService = accountService;
- this.blockchain = blockchain;
- }
-
- @Override
- public JsonElement processRequest(JsonObject request, Peer peer) {
- JsonObject response = new JsonObject();
- Long accountId = Convert.parseAccountId(JSON.getAsString(request.get("account")));
- Account account = accountService.getAccount(accountId);
- JsonArray transactions = new JsonArray();
- if(account != null) {
- for (Transaction transaction : blockchain.getTransactions(account, 0, (byte)-1, (byte)0, 0, 0, 9, false)) {
- transactions.add(brs.http.JSONData.transaction(transaction, blockchain.getHeight()));
- }
- }
- response.add("transactions", transactions);
-
- return response;
- }
-
-}
diff --git a/src/brs/peer/GetNextBlocks.java b/src/brs/peer/GetNextBlocks.java
index f09feb6d6..f7f291ea9 100644
--- a/src/brs/peer/GetNextBlocks.java
+++ b/src/brs/peer/GetNextBlocks.java
@@ -3,6 +3,8 @@
import brs.Block;
import brs.Blockchain;
import brs.Constants;
+import brs.props.PropertyService;
+import brs.props.Props;
import brs.util.Convert;
import brs.util.JSON;
import com.google.gson.JsonArray;
@@ -13,14 +15,23 @@
import java.util.Collection;
import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
final class GetNextBlocks implements PeerServlet.PeerRequestHandler {
+
+ private static final Logger logger = LoggerFactory.getLogger(GetNextBlocks.class);
private final Blockchain blockchain;
private static final int MAX_LENGHT = 1048576;
private static final int MAX_BLOCKS = 1440 / 2; // maxRollback must be at least 1440 and we are using half of that
+ private final int maxBlocks;
- GetNextBlocks(Blockchain blockchain) {
+ GetNextBlocks(Blockchain blockchain, PropertyService propertyService) {
this.blockchain = blockchain;
+
+ maxBlocks = Math.min(MAX_BLOCKS, propertyService.getInt(Props.P2P_MAX_BLOCKS));
+ logger.info("P2P max number of blocks: {}", maxBlocks);
}
@@ -33,8 +44,8 @@ public JsonElement processRequest(JsonObject request, Peer peer) {
int totalLength = 0;
long blockId = Convert.parseUnsignedLong(JSON.getAsString(request.get("blockId")));
- while(totalLength < MAX_LENGHT && nextBlocks.size() < MAX_BLOCKS) {
- Collection extends Block> blocks = blockchain.getBlocksAfter(blockId, 100);
+ while(totalLength < MAX_LENGHT && nextBlocks.size() < maxBlocks) {
+ Collection extends Block> blocks = blockchain.getBlocksAfter(blockId, Math.min(100, maxBlocks));
if (blocks.isEmpty()) {
break;
}
diff --git a/src/brs/peer/PeerServlet.java b/src/brs/peer/PeerServlet.java
index 041833666..4b9d9596a 100644
--- a/src/brs/peer/PeerServlet.java
+++ b/src/brs/peer/PeerServlet.java
@@ -3,6 +3,7 @@
import brs.Blockchain;
import brs.BlockchainProcessor;
import brs.TransactionProcessor;
+import brs.props.PropertyService;
import brs.services.AccountService;
import brs.services.TimeService;
import brs.util.CountingInputStream;
@@ -59,7 +60,8 @@ interface RequestLifecycleHook {
public PeerServlet(TimeService timeService, AccountService accountService,
Blockchain blockchain,
TransactionProcessor transactionProcessor,
- BlockchainProcessor blockchainProcessor) {
+ BlockchainProcessor blockchainProcessor,
+ PropertyService propertyService) {
final Map map = new HashMap<>();
map.put("addPeers", AddPeers.instance);
map.put("getCumulativeDifficulty", new GetCumulativeDifficulty(blockchain));
@@ -67,13 +69,11 @@ public PeerServlet(TimeService timeService, AccountService accountService,
map.put("getMilestoneBlockIds", new GetMilestoneBlockIds(blockchain));
map.put("getNextBlockIds", new GetNextBlockIds(blockchain));
map.put("getBlocksFromHeight", new GetBlocksFromHeight(blockchain));
- map.put("getNextBlocks", new GetNextBlocks(blockchain));
+ map.put("getNextBlocks", new GetNextBlocks(blockchain, propertyService));
map.put("getPeers", GetPeers.instance);
map.put("getUnconfirmedTransactions", new GetUnconfirmedTransactions(transactionProcessor));
map.put("processBlock", new ProcessBlock(blockchain, blockchainProcessor));
map.put("processTransactions", new ProcessTransactions(transactionProcessor));
- map.put("getAccountBalance", new GetAccountBalance(accountService));
- map.put("getAccountRecentTransactions", new GetAccountRecentTransactions(accountService, blockchain));
peerRequestHandlers = Collections.unmodifiableMap(map);
}
diff --git a/src/brs/peer/Peers.java b/src/brs/peer/Peers.java
index 71bff566e..4b798f998 100644
--- a/src/brs/peer/Peers.java
+++ b/src/brs/peer/Peers.java
@@ -220,6 +220,7 @@ public static void init(TimeService timeService, AccountService accountService,
}
maxNumberOfConnectedPublicPeers = propertyService.getInt(Props.P2P_MAX_CONNECTIONS);
+ logger.info("P2P max connections: {}", maxNumberOfConnectedPublicPeers);
connectTimeout = propertyService.getInt(Props.P2P_TIMEOUT_CONNECT_MS);
readTimeout = propertyService.getInt(Props.P2P_TIMEOUT_READ_MS);
@@ -351,7 +352,7 @@ static void init(TimeService timeService, AccountService accountService, Blockch
peerServer.addConnector(connector);
ServletHolder peerServletHolder = new ServletHolder(new PeerServlet(timeService, accountService, blockchain,
- transactionProcessor, blockchainProcessor));
+ transactionProcessor, blockchainProcessor, propertyService));
boolean isGzipEnabled = propertyService.getBoolean(Props.JETTY_P2P_GZIP_FILTER);
peerServletHolder.setInitParameter("isGzipEnabled", Boolean.toString(isGzipEnabled));
diff --git a/src/brs/props/Props.java b/src/brs/props/Props.java
index eefb5a46e..8430fb16c 100644
--- a/src/brs/props/Props.java
+++ b/src/brs/props/Props.java
@@ -98,6 +98,7 @@ public class Props {
public static final Prop P2P_TIMEOUT_CONNECT_MS = new Prop<>("P2P.TimeoutConnect_ms", 4000);
public static final Prop P2P_TIMEOUT_READ_MS = new Prop<>("P2P.TimeoutRead_ms", 8000);
public static final Prop P2P_BLACKLISTING_TIME_MS = new Prop<>("P2P.BlacklistingTime_ms", 600000);
+ public static final Prop P2P_MAX_BLOCKS = new Prop<>("P2P.MaxBlocks", 720);
public static final Prop P2P_TIMEOUT_IDLE_MS = new Prop<>("P2P.TimeoutIdle_ms", 30000);
@@ -114,11 +115,11 @@ public class Props {
public static final Prop P2P_MAX_UNCONFIRMED_TRANSACTIONS_RAW_SIZE_BYTES_TO_SEND = new Prop<>("P2P.maxUTRawSizeBytesToSend", 175000);
// API options
- public static final Prop API_DEBUG = new Prop<>("API.Debug", false);
public static final Prop API_SSL = new Prop<>("API.SSL", false);
public static final Prop API_SERVER = new Prop<>("API.Server", true);
public static final Prop API_V2_SERVER = new Prop<>("API.V2.Server", false);
public static final Prop API_ALLOWED = new Prop<>("API.allowed", "127.0.0.1; localhost; [0:0:0:0:0:0:0:1];");
+ public static final Prop API_ADMIN_KEY_LIST = new Prop<>("API.adminKeyList", "");
public static final Prop API_ACCEPT_SURPLUS_PARAMS = new Prop<>("API.AcceptSurplusParams", false);
@@ -130,6 +131,7 @@ public class Props {
public static final Prop API_UI_DIR = new Prop<>("API.UI_Dir", "html/ui");
public static final Prop API_SSL_KEY_STORE_PATH = new Prop<>("API.SSL_keyStorePath", "keystore");
public static final Prop API_SSL_KEY_STORE_PASSWORD = new Prop<>("API.SSL_keyStorePassword", "password");
+ public static final Prop API_SSL_LETSENCRYPT_PATH = new Prop<>("API.SSL_letsencryptPath", "");
public static final Prop API_SERVER_IDLE_TIMEOUT = new Prop<>("API.ServerIdleTimeout", 30000);
public static final Prop API_SERVER_ENFORCE_POST = new Prop<>("API.ServerEnforcePOST", true);
public static final Prop API_ALLOWED_ORIGINS = new Prop<>("API.AllowedOrigins", "*");
diff --git a/src/brs/unconfirmedtransactions/ReservedBalanceCache.java b/src/brs/unconfirmedtransactions/ReservedBalanceCache.java
index 4869152bf..a767f1f81 100644
--- a/src/brs/unconfirmedtransactions/ReservedBalanceCache.java
+++ b/src/brs/unconfirmedtransactions/ReservedBalanceCache.java
@@ -1,9 +1,14 @@
package brs.unconfirmedtransactions;
import brs.Account;
+import brs.Burst;
import brs.BurstException;
+import brs.Constants;
import brs.BurstException.ValidationException;
import brs.Transaction;
+import brs.TransactionType;
+import brs.Attachment.CommitmentRemove;
+import brs.Blockchain;
import brs.db.store.AccountStore;
import brs.util.Convert;
import org.slf4j.Logger;
@@ -45,14 +50,31 @@ void reserveBalanceAndPut(Transaction transaction) throws BurstException.Validat
}
throw new BurstException.NotCurrentlyValidException("Account unknown");
- } else if ( amountNQT > senderAccount.getUnconfirmedBalanceNQT() ) {
+ }
+
+ if ( amountNQT > senderAccount.getUnconfirmedBalanceNQT() ) {
if (LOGGER.isInfoEnabled()) {
- LOGGER.info(String.format("Transaction %d: Account %d balance too low. You have %d > %d Balance", transaction.getId(), transaction.getSenderId(), amountNQT, senderAccount.getUnconfirmedBalanceNQT()));
+ LOGGER.debug(String.format("Transaction %d: Account %d balance too low. You have %d > %d Balance", transaction.getId(), transaction.getSenderId(), amountNQT, senderAccount.getUnconfirmedBalanceNQT()));
}
-
throw new BurstException.NotCurrentlyValidException("Insufficient funds");
}
+ if(transaction.getType() == TransactionType.BurstMining.COMMITMENT_REMOVE) {
+ CommitmentRemove commitmentRemove = (CommitmentRemove) transaction.getAttachment();
+ long totalAmountNQT = commitmentRemove.getAmountNQT();
+
+ Blockchain blockchain = Burst.getBlockchain();
+ int nBlocksMined = blockchain.getBlocksCount(senderAccount, blockchain.getHeight() - Constants.MAX_ROLLBACK, blockchain.getHeight());
+ long amountCommitted = blockchain.getCommittedAmount(senderAccount, blockchain.getHeight(), blockchain.getHeight(), transaction);
+ if (nBlocksMined > 0 || amountCommitted < totalAmountNQT ) {
+ if (LOGGER.isInfoEnabled()) {
+ LOGGER.debug("Transaction {}: Account {} commitment remove not allowed. Blocks mined {}, amount commitment {}, amount removing {}",
+ transaction.getId(), transaction.getSenderId(), nBlocksMined, amountCommitted, totalAmountNQT);
+ }
+ throw new BurstException.NotCurrentlyValidException("Commitment remove not allowed");
+ }
+ }
+
reservedBalanceCache.put(transaction.getSenderId(), amountNQT);
}
diff --git a/src/brs/unconfirmedtransactions/UnconfirmedTransactionStoreImpl.java b/src/brs/unconfirmedtransactions/UnconfirmedTransactionStoreImpl.java
index ef37834c8..ebb4d6e6e 100644
--- a/src/brs/unconfirmedtransactions/UnconfirmedTransactionStoreImpl.java
+++ b/src/brs/unconfirmedtransactions/UnconfirmedTransactionStoreImpl.java
@@ -96,7 +96,7 @@ public boolean put(Transaction transaction, Peer peer) throws ValidationExceptio
removeCheapestFirstToExpireTransaction();
}
} else {
- logger.info("Transaction {}: Will not add a cheaper duplicate UT", transaction.getId());
+ logger.debug("Transaction {}: Will not add a cheaper duplicate UT", transaction.getId());
}
} else {
addTransaction(transaction, peer);
diff --git a/test/java/brs/http/ClearUnconfirmedTransactionsTest.java b/test/java/brs/http/ClearUnconfirmedTransactionsTest.java
index 700d17e87..f0b1b33d6 100644
--- a/test/java/brs/http/ClearUnconfirmedTransactionsTest.java
+++ b/test/java/brs/http/ClearUnconfirmedTransactionsTest.java
@@ -2,6 +2,8 @@
import brs.TransactionProcessor;
import brs.common.QuickMocker;
+import brs.props.PropertyService;
+import brs.props.Props;
import brs.util.JSON;
import com.google.gson.JsonObject;
import org.junit.Before;
@@ -9,38 +11,66 @@
import javax.servlet.http.HttpServletRequest;
+import static brs.http.common.Parameters.API_KEY_PARAMETER;
import static brs.http.common.ResultFields.DONE_RESPONSE;
import static brs.http.common.ResultFields.ERROR_RESPONSE;
+import static brs.http.common.ResultFields.ERROR_CODE_RESPONSE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
+import java.util.ArrayList;
+
public class ClearUnconfirmedTransactionsTest {
private ClearUnconfirmedTransactions t;
private TransactionProcessor transactionProcessorMock;
+ private PropertyService propertyService;
+
+ private static final String KEY = "abc";
@Before
public void init() {
transactionProcessorMock = mock(TransactionProcessor.class);
+ propertyService = mock(PropertyService.class);
+
+ ArrayList keys = new ArrayList<>();
+ keys.add(KEY);
+ doReturn(keys).when(propertyService).getStringList(Props.API_ADMIN_KEY_LIST);
- this.t = new ClearUnconfirmedTransactions(transactionProcessorMock);
+ this.t = new ClearUnconfirmedTransactions(transactionProcessorMock, propertyService);
}
@Test
public void processRequest() {
final HttpServletRequest req = QuickMocker.httpServletRequest();
+
+ doReturn(KEY).when(req).getParameter(API_KEY_PARAMETER);
final JsonObject result = ((JsonObject) t.processRequest(req));
assertEquals(true, JSON.getAsBoolean(result.get(DONE_RESPONSE)));
}
+
+ @Test
+ public void processRequestNotAllowed() {
+ final HttpServletRequest req = QuickMocker.httpServletRequest();
+
+ doReturn("").when(req).getParameter(API_KEY_PARAMETER);
+
+ final JsonObject result = ((JsonObject) t.processRequest(req));
+
+ assertEquals(7, JSON.getAsInt(result.get(ERROR_CODE_RESPONSE)));
+ }
@Test
public void processRequest_runtimeExceptionOccurs() {
final HttpServletRequest req = QuickMocker.httpServletRequest();
+
+ doReturn(KEY).when(req).getParameter(API_KEY_PARAMETER);
doThrow(new RuntimeException("errorMessage")).when(transactionProcessorMock).clearUnconfirmedTransactions();
diff --git a/test/java/brs/http/FullResetTest.java b/test/java/brs/http/FullResetTest.java
index 3408a5104..0943f2e17 100644
--- a/test/java/brs/http/FullResetTest.java
+++ b/test/java/brs/http/FullResetTest.java
@@ -2,6 +2,8 @@
import brs.BlockchainProcessor;
import brs.common.QuickMocker;
+import brs.props.PropertyService;
+import brs.props.Props;
import brs.util.JSON;
import com.google.gson.JsonObject;
import org.junit.Before;
@@ -9,38 +11,64 @@
import javax.servlet.http.HttpServletRequest;
+import static brs.http.common.Parameters.API_KEY_PARAMETER;
import static brs.http.common.ResultFields.DONE_RESPONSE;
+import static brs.http.common.ResultFields.ERROR_CODE_RESPONSE;
import static brs.http.common.ResultFields.ERROR_RESPONSE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
+import java.util.ArrayList;
+
public class FullResetTest {
private FullReset t;
private BlockchainProcessor blockchainProcessor;
+ private PropertyService propertyService;
+
+ private static final String KEY = "abc";
@Before
public void init() {
blockchainProcessor = mock(BlockchainProcessor.class);
+ propertyService = mock(PropertyService.class);
+
+ ArrayList keys = new ArrayList<>();
+ keys.add(KEY);
+ doReturn(keys).when(propertyService).getStringList(Props.API_ADMIN_KEY_LIST);
- this.t = new FullReset(blockchainProcessor);
+ this.t = new FullReset(blockchainProcessor, propertyService);
}
@Test
public void processRequest() {
final HttpServletRequest req = QuickMocker.httpServletRequest();
+ doReturn(KEY).when(req).getParameter(API_KEY_PARAMETER);
final JsonObject result = ((JsonObject) t.processRequest(req));
assertTrue(JSON.getAsBoolean(result.get(DONE_RESPONSE)));
}
+
+ @Test
+ public void processRequestNotAllowed() {
+ final HttpServletRequest req = QuickMocker.httpServletRequest();
+
+ doReturn("").when(req).getParameter(API_KEY_PARAMETER);
+
+ final JsonObject result = ((JsonObject) t.processRequest(req));
+
+ assertEquals(7, JSON.getAsInt(result.get(ERROR_CODE_RESPONSE)));
+ }
@Test
public void processRequest_runtimeExceptionOccurs() {
final HttpServletRequest req = QuickMocker.httpServletRequest();
+ doReturn(KEY).when(req).getParameter(API_KEY_PARAMETER);
doThrow(new RuntimeException("errorMessage")).when(blockchainProcessor).fullReset();
diff --git a/test/java/brs/peer/GetAccountBalanceTest.java b/test/java/brs/peer/GetAccountBalanceTest.java
deleted file mode 100644
index 2cd750c1b..000000000
--- a/test/java/brs/peer/GetAccountBalanceTest.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package brs.peer;
-
-import brs.Account;
-import brs.services.AccountService;
-import brs.util.JSON;
-import com.google.gson.JsonObject;
-import org.junit.Before;
-import org.junit.Test;
-
-import static brs.common.TestConstants.TEST_ACCOUNT_ID;
-import static brs.common.TestConstants.TEST_ACCOUNT_NUMERIC_ID_PARSED;
-import static brs.peer.GetAccountBalance.ACCOUNT_ID_PARAMETER_FIELD;
-import static brs.peer.GetAccountBalance.BALANCE_NQT_RESPONSE_FIELD;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-@Deprecated
-public class GetAccountBalanceTest {
-
- private GetAccountBalance t;
-
- private AccountService mockAccountService;
-
- @Before
- public void setUp() {
- mockAccountService = mock(AccountService.class);
-
- t = new GetAccountBalance(mockAccountService);
- }
-
- @Test
- public void processRequest() {
- final JsonObject req = new JsonObject();
- req.addProperty(ACCOUNT_ID_PARAMETER_FIELD, TEST_ACCOUNT_ID);
- final Peer peer = mock(Peer.class);
-
- long mockBalanceNQT = 5;
- Account mockAccount = mock(Account.class);
- when(mockAccount.getBalanceNQT()).thenReturn(mockBalanceNQT);
-
- when(mockAccountService.getAccount(eq(TEST_ACCOUNT_NUMERIC_ID_PARSED))).thenReturn(mockAccount);
-
- final JsonObject result = (JsonObject) t.processRequest(req, peer);
-
- assertEquals("" + mockBalanceNQT, JSON.getAsString(result.get(BALANCE_NQT_RESPONSE_FIELD)));
- }
-
- @Test
- public void processRequest_notExistingAccount() {
- final JsonObject req = new JsonObject();
- req.addProperty(ACCOUNT_ID_PARAMETER_FIELD, TEST_ACCOUNT_ID);
- final Peer peer = mock(Peer.class);
-
- when(mockAccountService.getAccount(eq(TEST_ACCOUNT_NUMERIC_ID_PARSED))).thenReturn(null);
-
- final JsonObject result = (JsonObject) t.processRequest(req, peer);
-
- assertEquals("0", JSON.getAsString(result.get(BALANCE_NQT_RESPONSE_FIELD)));
- }
-
-}
diff --git a/test/java/brs/peer/GetAccountRecentTransactionsTest.java b/test/java/brs/peer/GetAccountRecentTransactionsTest.java
deleted file mode 100644
index d8d5d6c16..000000000
--- a/test/java/brs/peer/GetAccountRecentTransactionsTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package brs.peer;
-
-import brs.Account;
-import brs.Blockchain;
-import brs.Transaction;
-import brs.TransactionType.DigitalGoods;
-import brs.common.AbstractUnitTest;
-import brs.common.QuickMocker;
-import brs.common.QuickMocker.JSONParam;
-import brs.common.TestConstants;
-import brs.services.AccountService;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonPrimitive;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Collection;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-;
-
-public class GetAccountRecentTransactionsTest extends AbstractUnitTest {
-
- private GetAccountRecentTransactions t;
-
- private AccountService mockAccountService;
- private Blockchain mockBlockchain;
-
- @Before
- public void setUp() {
- mockAccountService = mock(AccountService.class);
- mockBlockchain = mock(Blockchain.class);
-
- t = new GetAccountRecentTransactions(mockAccountService, mockBlockchain);
- }
-
- @Test
- public void processRequest() {
- final String accountId = TestConstants.TEST_ACCOUNT_NUMERIC_ID;
-
- final JsonObject request = QuickMocker.jsonObject(new JSONParam("account", new JsonPrimitive(accountId)));
-
- final Peer peerMock = mock(Peer.class);
-
- final Account mockAccount = mock(Account.class);
-
- final Transaction mockTransaction = mock(Transaction.class);
- when(mockTransaction.getType()).thenReturn(DigitalGoods.DELISTING);
- final Collection transactionsIterator = mockCollection(mockTransaction);
-
- when(mockAccountService.getAccount(eq(TestConstants.TEST_ACCOUNT_NUMERIC_ID_PARSED))).thenReturn(mockAccount);
- when(mockBlockchain.getTransactions(eq(mockAccount), eq(0), eq((byte) -1), eq((byte) 0), eq(0), eq(0), eq(9), eq(false))).thenReturn(transactionsIterator);
-
- final JsonObject result = (JsonObject) t.processRequest(request, peerMock);
- assertNotNull(result);
-
- final JsonArray transactionsResult = (JsonArray) result.get("transactions");
- assertNotNull(transactionsResult);
- assertEquals(1, transactionsResult.size());
- }
-
-}
diff --git a/test/java/brs/peer/GetNextBlocksTest.java b/test/java/brs/peer/GetNextBlocksTest.java
index 88e95e6d7..86f73e0f1 100644
--- a/test/java/brs/peer/GetNextBlocksTest.java
+++ b/test/java/brs/peer/GetNextBlocksTest.java
@@ -3,6 +3,9 @@
import brs.Block;
import brs.Blockchain;
import brs.Genesis;
+import brs.props.PropertyService;
+import brs.props.Props;
+
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@@ -23,20 +26,23 @@
public class GetNextBlocksTest {
private GetNextBlocks getNextBlocks;
private Blockchain mockBlockchain;
+ private PropertyService mockPropertyService;
private Peer mockPeer;
@Before
public void setUpGetNextBlocksTest() {
mockBlockchain = mock(Blockchain.class);
+ mockPropertyService = mock(PropertyService.class);
mockPeer = mock(Peer.class);
Block mockBlock = mock(Block.class);
when(mockBlock.getJsonObject()).thenReturn(new JsonObject());
+ when(mockPropertyService.getInt(Props.P2P_MAX_BLOCKS)).thenReturn(720);
List blocks = new ArrayList<>();
for (int i = 0; i < 100; i++) {
blocks.add(mockBlock);
}
when(mockBlockchain.getBlocksAfter(ArgumentMatchers.eq(Genesis.GENESIS_BLOCK_ID), ArgumentMatchers.anyInt())).thenReturn(blocks);
- getNextBlocks = new GetNextBlocks(mockBlockchain);
+ getNextBlocks = new GetNextBlocks(mockBlockchain, mockPropertyService);
}
@Test