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 @@
- +
diff --git a/pom.xml b/pom.xml index a2a00b9bb..ea74f479b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ burstcoin burstcoin - 3.0.0 + 3.0.1 Burstcoin Reference Software https://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 blocks = blockchain.getBlocksAfter(blockId, 100); + while(totalLength < MAX_LENGHT && nextBlocks.size() < maxBlocks) { + Collection 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