From da3b69399bc490d2488b0454cc82fde7c71485cd Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Mon, 16 Sep 2019 20:30:21 +0100 Subject: [PATCH 01/22] This is to initiate the update necessary for the fulfillment of proposal #110: Fully functional and basic Monero (XMR) wallet integrated to Monero RPC Wallet running on localhost with the following features: 1. Viewing balance (total and unlocked). 2. Sending XMR. 3. Viewing XMR Primary address to receive XMR. 4. Viewing and searching transactions. 5. Transaction/payment proof/verification (almost certified complete). TODOs: 1. Completion of automation of Monero related trades (integration to wallet). 2. Unit Tests (first order - XmrFormatter and XmrValidator). 3.Payment proof testing completion. --- .gitignore | 1 + build.gradle | 2 + common/src/main/proto/pb.proto | 7 + .../core/payment/PaymentAccountFactory.java | 15 + .../presentation/CorePresentationModule.java | 2 + .../main/java/bisq/core/user/Preferences.java | 110 ++++ .../bisq/core/user/PreferencesPayload.java | 286 ++++++----- .../main/java/bisq/core/xmr/XmrFormatter.java | 90 ++++ .../trade/XmrTradeAutomationInterceptor.java | 58 +++ .../bisq/core/xmr/wallet/TxProofHandler.java | 28 + .../bisq/core/xmr/wallet/XmrTxListItem.java | 82 +++ .../core/xmr/wallet/XmrWalletRpcWrapper.java | 374 ++++++++++++++ .../listeners/WalletBalanceListener.java | 44 ++ .../resources/i18n/displayStrings.properties | 154 ++++-- .../components/WalletAddressTextField.java | 139 +++++ .../desktop/main/account/AccountView.fxml | 7 +- .../desktop/main/account/AccountView.java | 10 +- .../content/wallet/AltCoinWalletsView.fxml | 35 ++ .../content/wallet/AltCoinWalletsView.java | 126 +++++ .../content/wallet/monero/XmrBalanceUtil.java | 106 ++++ .../content/wallet/monero/XmrWalletView.fxml | 44 ++ .../content/wallet/monero/XmrWalletView.java | 160 ++++++ .../wallet/monero/receive/XmrReceiveView.fxml | 35 ++ .../wallet/monero/receive/XmrReceiveView.java | 139 +++++ .../wallet/monero/send/XmrSendView.fxml | 34 ++ .../wallet/monero/send/XmrSendView.java | 294 +++++++++++ .../wallet/monero/tx/XmrTxProofWindow.java | 129 +++++ .../content/wallet/monero/tx/XmrTxView.fxml | 29 ++ .../content/wallet/monero/tx/XmrTxView.java | 482 ++++++++++++++++++ .../settings/preferences/PreferencesView.java | 87 +++- .../java/bisq/desktop/util/FormBuilder.java | 11 +- .../desktop/util/validation/XmrValidator.java | 157 ++++++ .../java/bisq/desktop/GuiceSetupTest.java | 4 +- 33 files changed, 3085 insertions(+), 196 deletions(-) create mode 100644 core/src/main/java/bisq/core/xmr/XmrFormatter.java create mode 100644 core/src/main/java/bisq/core/xmr/trade/XmrTradeAutomationInterceptor.java create mode 100644 core/src/main/java/bisq/core/xmr/wallet/TxProofHandler.java create mode 100644 core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java create mode 100644 core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java create mode 100644 core/src/main/java/bisq/core/xmr/wallet/listeners/WalletBalanceListener.java create mode 100644 desktop/src/main/java/bisq/desktop/components/WalletAddressTextField.java create mode 100644 desktop/src/main/java/bisq/desktop/main/account/content/wallet/AltCoinWalletsView.fxml create mode 100644 desktop/src/main/java/bisq/desktop/main/account/content/wallet/AltCoinWalletsView.java create mode 100644 desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrBalanceUtil.java create mode 100644 desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrWalletView.fxml create mode 100644 desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrWalletView.java create mode 100644 desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/receive/XmrReceiveView.fxml create mode 100644 desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/receive/XmrReceiveView.java create mode 100644 desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.fxml create mode 100644 desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java create mode 100644 desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxProofWindow.java create mode 100644 desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.fxml create mode 100644 desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java create mode 100644 desktop/src/main/java/bisq/desktop/util/validation/XmrValidator.java diff --git a/.gitignore b/.gitignore index 0a68aa585c3..a1407627ce2 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ deploy /monitor/TorHiddenServiceStartupTimeTests/* /monitor/monitor-tor/* .java-version +/.metadata/ diff --git a/build.gradle b/build.gradle index 13caa2557ed..b5d1497a0c7 100644 --- a/build.gradle +++ b/build.gradle @@ -75,12 +75,14 @@ configure(subprojects) { } repositories { + mavenLocal() mavenCentral() maven { url 'https://jitpack.io' } } dependencies { testCompile "junit:junit:$junitVersion" + compile 'woodser:monero-wallet-java:0.0.2-SNAPSHOT' } tasks.withType(JavaCompile) { diff --git a/common/src/main/proto/pb.proto b/common/src/main/proto/pb.proto index 8f518b8ec50..ee4d5931154 100644 --- a/common/src/main/proto/pb.proto +++ b/common/src/main/proto/pb.proto @@ -1378,6 +1378,13 @@ message PreferencesPayload { int32 ignore_dust_threshold = 51; double buyer_security_deposit_as_percent_for_crypto = 52; int32 block_notify_port = 53; + bool use_bisq_xmr_wallet = 54; + string xmr_user_host = 55; + string xmr_host_port = 56; + string xmr_rpc_user = 57; + string xmr_rpc_pwd = 58; + repeated string xmr_hosts = 59; + int32 xmr_host_option_ordinal = 60; } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/payment/PaymentAccountFactory.java b/core/src/main/java/bisq/core/payment/PaymentAccountFactory.java index 044967cd7a6..fb2e3f66496 100644 --- a/core/src/main/java/bisq/core/payment/PaymentAccountFactory.java +++ b/core/src/main/java/bisq/core/payment/PaymentAccountFactory.java @@ -17,7 +17,10 @@ package bisq.core.payment; +import bisq.core.locale.TradeCurrency; import bisq.core.payment.payload.PaymentMethod; +import bisq.core.xmr.wallet.XmrWalletRpcWrapper; + public class PaymentAccountFactory { public static PaymentAccount getPaymentAccount(PaymentMethod paymentMethod) { @@ -89,4 +92,16 @@ public static PaymentAccount getPaymentAccount(PaymentMethod paymentMethod) { throw new RuntimeException("Not supported PaymentMethod: " + paymentMethod); } } + + public static InstantCryptoCurrencyAccount producePrimaryWalletAccount(XmrWalletRpcWrapper xmrWalletRpcWrapper, TradeCurrency xmrTradeCurrency) { + InstantCryptoCurrencyAccount account = new InstantCryptoCurrencyAccount(); + account.init(); + + account.setAddress(xmrWalletRpcWrapper.getPrimaryAddress()); + account.setSingleTradeCurrency(xmrTradeCurrency); + account.setAccountName("XMR Wallet: " + account.getAddress().substring(0, 6) + "..."); + + return account; + } + } diff --git a/core/src/main/java/bisq/core/presentation/CorePresentationModule.java b/core/src/main/java/bisq/core/presentation/CorePresentationModule.java index 6d84520d3ec..1b37718602c 100644 --- a/core/src/main/java/bisq/core/presentation/CorePresentationModule.java +++ b/core/src/main/java/bisq/core/presentation/CorePresentationModule.java @@ -19,6 +19,8 @@ import bisq.common.app.AppModule; +import bisq.core.xmr.wallet.XmrWalletRpcWrapper; + import org.springframework.core.env.Environment; import com.google.inject.Singleton; diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 52e96b90dad..fadee3e0aef 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -77,6 +77,10 @@ @Slf4j @Singleton public final class Preferences implements PersistedDataHost, BridgeAddressProvider { + //TODO(niyid) Monero Wallet params: XMR_MAIN_NET_EXPLORERS, XMR_TEST_NET_EXPLORERS + //TODO(niyid) Monero Wallet params: Local Host(127.0.0.1)/Remote Host node, Port + //TODO(niyid) Monero Wallet params: List of available remote nodes + //TODO(niyid) Monero Wallet params equivalents of: btcNodesFromOptions, useTorFlagFromOptions, rpcUserFromOptions, rpcPwFromOptions private static final ArrayList BTC_MAIN_NET_EXPLORERS = new ArrayList<>(Arrays.asList( new BlockChainExplorer("Blockstream.info", "https://blockstream.info/tx/", "https://blockstream.info/address/"), @@ -129,14 +133,19 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid private final ObservableList fiatCurrenciesAsObservable = FXCollections.observableArrayList(); private final ObservableList cryptoCurrenciesAsObservable = FXCollections.observableArrayList(); private final ObservableList tradeCurrenciesAsObservable = FXCollections.observableArrayList(); + private final ObservableList xmrHostsAsObservable = FXCollections.observableArrayList(); private final ObservableMap dontShowAgainMapAsObservable = FXCollections.observableHashMap(); private final Storage storage; private final BisqEnvironment bisqEnvironment; private final String btcNodesFromOptions, useTorFlagFromOptions, referralIdFromOptions, fullDaoNodeFromOptions, rpcUserFromOptions, rpcPwFromOptions, blockNotifyPortFromOptions; + + private final String xmrUserHostDelegate, xmrHostPortDelegate, xmrRpcUserDelegate, xmrRpcPwdDelegate; @Getter private final BooleanProperty useStandbyModeProperty = new SimpleBooleanProperty(prefPayload.isUseStandbyMode()); + @Getter + private final BooleanProperty useBisqXmrWalletProperty = new SimpleBooleanProperty(prefPayload.isUseBisqXmrWallet()); /////////////////////////////////////////////////////////////////////////////////////////// @@ -166,6 +175,11 @@ public Preferences(Storage storage, this.rpcUserFromOptions = rpcUser; this.rpcPwFromOptions = rpcPassword; this.blockNotifyPortFromOptions = rpcBlockNotificationPort; + + xmrUserHostDelegate = "127.0.0.1"; + xmrHostPortDelegate = "29088"; + xmrRpcUserDelegate = null; + xmrRpcPwdDelegate = null; useAnimationsProperty.addListener((ov) -> { prefPayload.setUseAnimations(useAnimationsProperty.get()); @@ -178,6 +192,11 @@ public Preferences(Storage storage, persist(); }); + useBisqXmrWalletProperty.addListener((ov) -> { + prefPayload.setUseBisqXmrWallet(useBisqXmrWalletProperty.get()); + persist(); + }); + fiatCurrenciesAsObservable.addListener((javafx.beans.Observable ov) -> { prefPayload.getFiatCurrencies().clear(); prefPayload.getFiatCurrencies().addAll(fiatCurrenciesAsObservable); @@ -259,11 +278,13 @@ public void readPersisted() { // set all properties useAnimationsProperty.set(prefPayload.isUseAnimations()); useStandbyModeProperty.set(prefPayload.isUseStandbyMode()); + useBisqXmrWalletProperty.set(prefPayload.isUseBisqXmrWallet()); useCustomWithdrawalTxFeeProperty.set(prefPayload.isUseCustomWithdrawalTxFee()); withdrawalTxFeeInBytesProperty.set(prefPayload.getWithdrawalTxFeeInBytes()); tradeCurrenciesAsObservable.addAll(prefPayload.getFiatCurrencies()); tradeCurrenciesAsObservable.addAll(prefPayload.getCryptoCurrencies()); + xmrHostsAsObservable.addAll(prefPayload.getXmrHosts()); dontShowAgainMapAsObservable.putAll(getDontShowAgainMap()); // Override settings with options if set @@ -368,6 +389,27 @@ public void removeCryptoCurrency(CryptoCurrency tradeCurrency) { } } + public void addXmrHost(String host) { + if (!xmrHostsAsObservable.contains(host)) { + xmrHostsAsObservable.add(host); + } + } + + public void removeXmrHost(String host) { + if (xmrHostsAsObservable.size() > 1) { + if (xmrHostsAsObservable.contains(host)) + xmrHostsAsObservable.remove(host); + + if (prefPayload.getXmrUserHost() != null && + prefPayload.getXmrUserHost().equals(host)) { + setXmrUserHost(xmrHostsAsObservable.get(0)); + } + } else { + log.error("you cannot remove the last Monero host"); + } + } + + public void setBlockChainExplorer(BlockChainExplorer blockChainExplorer) { if (BisqEnvironment.getBaseCurrencyNetwork().isMainnet()) setBlockChainExplorerMainNet(blockChainExplorer); @@ -608,6 +650,10 @@ public void setUseStandbyMode(boolean useStandbyMode) { this.useStandbyModeProperty.set(useStandbyMode); } + public void setUseBisqXmrWallet(boolean useBisqXmrWallet) { + this.useBisqXmrWalletProperty.set(useBisqXmrWallet); + } + public void setTakeOfferSelectedPaymentAccountId(String value) { prefPayload.setTakeOfferSelectedPaymentAccountId(value); persist(); @@ -639,6 +685,32 @@ public void setRpcPw(String value) { } } + public void setXmrUserHostDelegate(String value) { + prefPayload.setXmrUserHost(value); + persist(); + } + + public void setXmrHostPortDelegate(String value) { + prefPayload.setXmrHostPort(value); + persist(); + } + + public void setXmrRpcUserDelegate(String value) { + // We only persist if we have not set the program argument + if (xmrUserHostDelegate == null || xmrUserHostDelegate.isEmpty()) { + prefPayload.setXmrRpcUser(value); + persist(); + } + } + + public void setXmrRpcPwdDelegate(String value) { + // We only persist if we have not set the program argument + if (xmrRpcPwdDelegate == null || xmrRpcPwdDelegate.isEmpty()) { + prefPayload.setXmrRpcPwd(value); + persist(); + } + } + public void setBlockNotifyPort(int value) { // We only persist if we have not set the program argument if (blockNotifyPortFromOptions == null || blockNotifyPortFromOptions.isEmpty()) { @@ -672,6 +744,10 @@ public ObservableList getCryptoCurrenciesAsObservable() { public ObservableList getTradeCurrenciesAsObservable() { return tradeCurrenciesAsObservable; } + + public ObservableList getXmrHostsAsObservable() { + return xmrHostsAsObservable; + } public ObservableMap getDontShowAgainMapAsObservable() { return dontShowAgainMapAsObservable; @@ -791,6 +867,38 @@ public String getRpcPw() { } } + public String getXmrUserHostDelegate() { + if (xmrUserHostDelegate != null && !xmrUserHostDelegate.isEmpty()) { + return xmrUserHostDelegate; + } else { + return prefPayload.getXmrUserHost(); + } + } + + public String getXmrHostPortDelegate() { + if (xmrHostPortDelegate != null && !xmrHostPortDelegate.isEmpty()) { + return xmrHostPortDelegate; + } else { + return prefPayload.getXmrUserHost(); + } + } + + public String getXmrRpcUserDelegate() { + if (xmrRpcUserDelegate != null && !xmrRpcUserDelegate.isEmpty()) { + return xmrRpcUserDelegate; + } else { + return prefPayload.getXmrRpcUser(); + } + } + + public String getXmrRpcPwdDelegate() { + if (xmrRpcPwdDelegate != null && !xmrRpcPwdDelegate.isEmpty()) { + return xmrRpcPwdDelegate; + } else { + return prefPayload.getXmrRpcPwd(); + } + } + public int getBlockNotifyPort() { if (blockNotifyPortFromOptions != null && !blockNotifyPortFromOptions.isEmpty()) { try { @@ -911,6 +1019,8 @@ private interface ExcludesDelegateMethods { long getWithdrawalTxFeeInBytes(); void setUseStandbyMode(boolean useStandbyMode); + + void setUseBisqXmrWallet(boolean useBisqXmrWallet); void setTakeOfferSelectedPaymentAccountId(String value); diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index cfba52a34f5..9c52152ea5c 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -124,8 +124,17 @@ public final class PreferencesPayload implements PersistableEnvelope { private int ignoreDustThreshold = 600; private double buyerSecurityDepositAsPercentForCrypto = getDefaultBuyerSecurityDepositAsPercent(new CryptoCurrencyAccount()); private int blockNotifyPort; - - + + + private boolean useBisqXmrWallet = false; + private String xmrUserHost = "127.0.0.1"; + private String xmrHostPort = "29088"; + @Nullable + private String xmrRpcUser; + @Nullable + private String xmrRpcPwd; + private List xmrHosts = new ArrayList<>(); + private int xmrHostOptionOrdinal; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////////////////// @@ -139,136 +148,151 @@ public PreferencesPayload() { /////////////////////////////////////////////////////////////////////////////////////////// @Override - public Message toProtoMessage() { - protobuf.PreferencesPayload.Builder builder = protobuf.PreferencesPayload.newBuilder() - .setUserLanguage(userLanguage) - .setUserCountry((protobuf.Country) userCountry.toProtoMessage()) - .addAllFiatCurrencies(fiatCurrencies.stream() - .map(fiatCurrency -> ((protobuf.TradeCurrency) fiatCurrency.toProtoMessage())) - .collect(Collectors.toList())) - .addAllCryptoCurrencies(cryptoCurrencies.stream() - .map(cryptoCurrency -> ((protobuf.TradeCurrency) cryptoCurrency.toProtoMessage())) - .collect(Collectors.toList())) - .setBlockChainExplorerMainNet((protobuf.BlockChainExplorer) blockChainExplorerMainNet.toProtoMessage()) - .setBlockChainExplorerTestNet((protobuf.BlockChainExplorer) blockChainExplorerTestNet.toProtoMessage()) - .setBsqBlockChainExplorer((protobuf.BlockChainExplorer) bsqBlockChainExplorer.toProtoMessage()) - .setAutoSelectArbitrators(autoSelectArbitrators) - .putAllDontShowAgainMap(dontShowAgainMap) - .setTacAccepted(tacAccepted) - .setUseTorForBitcoinJ(useTorForBitcoinJ) - .setShowOwnOffersInOfferBook(showOwnOffersInOfferBook) - .setWithdrawalTxFeeInBytes(withdrawalTxFeeInBytes) - .setUseCustomWithdrawalTxFee(useCustomWithdrawalTxFee) - .setMaxPriceDistanceInPercent(maxPriceDistanceInPercent) - .setTradeStatisticsTickUnitIndex(tradeStatisticsTickUnitIndex) - .setResyncSpvRequested(resyncSpvRequested) - .setSortMarketCurrenciesNumerically(sortMarketCurrenciesNumerically) - .setUsePercentageBasedPrice(usePercentageBasedPrice) - .putAllPeerTagMap(peerTagMap) - .setBitcoinNodes(bitcoinNodes) - .addAllIgnoreTradersList(ignoreTradersList) - .setDirectoryChooserPath(directoryChooserPath) - .setBuyerSecurityDepositAsLong(buyerSecurityDepositAsLong) - .setUseAnimations(useAnimations) - .setPayFeeInBtc(payFeeInBtc) - .setBridgeOptionOrdinal(bridgeOptionOrdinal) - .setTorTransportOrdinal(torTransportOrdinal) - .setBitcoinNodesOptionOrdinal(bitcoinNodesOptionOrdinal) - .setUseSoundForMobileNotifications(useSoundForMobileNotifications) - .setUseTradeNotifications(useTradeNotifications) - .setUseMarketNotifications(useMarketNotifications) - .setUsePriceNotifications(usePriceNotifications) - .setUseStandbyMode(useStandbyMode) - .setIsDaoFullNode(isDaoFullNode) - .setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent) - .setIgnoreDustThreshold(ignoreDustThreshold) - .setBuyerSecurityDepositAsPercentForCrypto(buyerSecurityDepositAsPercentForCrypto) - .setBlockNotifyPort(blockNotifyPort); - Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory); - Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage())); - Optional.ofNullable(offerBookChartScreenCurrencyCode).ifPresent(builder::setOfferBookChartScreenCurrencyCode); - Optional.ofNullable(tradeChartsScreenCurrencyCode).ifPresent(builder::setTradeChartsScreenCurrencyCode); - Optional.ofNullable(buyScreenCurrencyCode).ifPresent(builder::setBuyScreenCurrencyCode); - Optional.ofNullable(sellScreenCurrencyCode).ifPresent(builder::setSellScreenCurrencyCode); - Optional.ofNullable(selectedPaymentAccountForCreateOffer).ifPresent( - account -> builder.setSelectedPaymentAccountForCreateOffer(selectedPaymentAccountForCreateOffer.toProtoMessage())); - Optional.ofNullable(bridgeAddresses).ifPresent(builder::addAllBridgeAddresses); - Optional.ofNullable(customBridges).ifPresent(builder::setCustomBridges); - Optional.ofNullable(referralId).ifPresent(builder::setReferralId); - Optional.ofNullable(phoneKeyAndToken).ifPresent(builder::setPhoneKeyAndToken); - Optional.ofNullable(rpcUser).ifPresent(builder::setRpcUser); - Optional.ofNullable(rpcPw).ifPresent(builder::setRpcPw); - Optional.ofNullable(takeOfferSelectedPaymentAccountId).ifPresent(builder::setTakeOfferSelectedPaymentAccountId); + public Message toProtoMessage() { + protobuf.PreferencesPayload.Builder builder = protobuf.PreferencesPayload.newBuilder() + .setUserLanguage(userLanguage) + .setUserCountry((protobuf.Country) userCountry.toProtoMessage()) + .addAllFiatCurrencies(fiatCurrencies.stream() + .map(fiatCurrency -> ((protobuf.TradeCurrency) fiatCurrency.toProtoMessage())) + .collect(Collectors.toList())) + .addAllCryptoCurrencies(cryptoCurrencies.stream() + .map(cryptoCurrency -> ((protobuf.TradeCurrency) cryptoCurrency.toProtoMessage())) + .collect(Collectors.toList())) + .setBlockChainExplorerMainNet((protobuf.BlockChainExplorer) blockChainExplorerMainNet.toProtoMessage()) + .setBlockChainExplorerTestNet((protobuf.BlockChainExplorer) blockChainExplorerTestNet.toProtoMessage()) + .setBsqBlockChainExplorer((protobuf.BlockChainExplorer) bsqBlockChainExplorer.toProtoMessage()) + .setAutoSelectArbitrators(autoSelectArbitrators) + .putAllDontShowAgainMap(dontShowAgainMap) + .setTacAccepted(tacAccepted) + .setUseTorForBitcoinJ(useTorForBitcoinJ) + .setShowOwnOffersInOfferBook(showOwnOffersInOfferBook) + .setWithdrawalTxFeeInBytes(withdrawalTxFeeInBytes) + .setUseCustomWithdrawalTxFee(useCustomWithdrawalTxFee) + .setMaxPriceDistanceInPercent(maxPriceDistanceInPercent) + .setTradeStatisticsTickUnitIndex(tradeStatisticsTickUnitIndex) + .setResyncSpvRequested(resyncSpvRequested) + .setSortMarketCurrenciesNumerically(sortMarketCurrenciesNumerically) + .setUsePercentageBasedPrice(usePercentageBasedPrice) + .putAllPeerTagMap(peerTagMap) + .setBitcoinNodes(bitcoinNodes) + .addAllIgnoreTradersList(ignoreTradersList) + .setDirectoryChooserPath(directoryChooserPath) + .setBuyerSecurityDepositAsLong(buyerSecurityDepositAsLong) + .setUseAnimations(useAnimations) + .setPayFeeInBtc(payFeeInBtc) + .setBridgeOptionOrdinal(bridgeOptionOrdinal) + .setTorTransportOrdinal(torTransportOrdinal) + .setBitcoinNodesOptionOrdinal(bitcoinNodesOptionOrdinal) + .setUseSoundForMobileNotifications(useSoundForMobileNotifications) + .setUseTradeNotifications(useTradeNotifications) + .setUseMarketNotifications(useMarketNotifications) + .setUsePriceNotifications(usePriceNotifications) + .setUseStandbyMode(useStandbyMode) + .setIsDaoFullNode(isDaoFullNode) + .setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent) + .setIgnoreDustThreshold(ignoreDustThreshold) + .setBuyerSecurityDepositAsPercentForCrypto(buyerSecurityDepositAsPercentForCrypto) + .setBlockNotifyPort(blockNotifyPort) + .setUseBisqXmrWallet(useBisqXmrWallet) + .setXmrUserHost(xmrUserHost) + .setXmrHostPort(xmrHostPort) + .setXmrRpcUser(xmrRpcUser) + .setXmrRpcPwd(xmrRpcPwd) + .addAllXmrHosts(xmrHosts) + .setXmrHostOptionOrdinal(xmrHostOptionOrdinal); - return protobuf.PersistableEnvelope.newBuilder().setPreferencesPayload(builder).build(); - } + Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory); + Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage())); + Optional.ofNullable(offerBookChartScreenCurrencyCode).ifPresent(builder::setOfferBookChartScreenCurrencyCode); + Optional.ofNullable(tradeChartsScreenCurrencyCode).ifPresent(builder::setTradeChartsScreenCurrencyCode); + Optional.ofNullable(buyScreenCurrencyCode).ifPresent(builder::setBuyScreenCurrencyCode); + Optional.ofNullable(sellScreenCurrencyCode).ifPresent(builder::setSellScreenCurrencyCode); + Optional.ofNullable(selectedPaymentAccountForCreateOffer).ifPresent( + account -> builder.setSelectedPaymentAccountForCreateOffer(selectedPaymentAccountForCreateOffer.toProtoMessage())); + Optional.ofNullable(bridgeAddresses).ifPresent(builder::addAllBridgeAddresses); + Optional.ofNullable(customBridges).ifPresent(builder::setCustomBridges); + Optional.ofNullable(referralId).ifPresent(builder::setReferralId); + Optional.ofNullable(phoneKeyAndToken).ifPresent(builder::setPhoneKeyAndToken); + Optional.ofNullable(rpcUser).ifPresent(builder::setRpcUser); + Optional.ofNullable(rpcPw).ifPresent(builder::setRpcPw); + Optional.ofNullable(takeOfferSelectedPaymentAccountId).ifPresent(builder::setTakeOfferSelectedPaymentAccountId); + + return protobuf.PersistableEnvelope.newBuilder().setPreferencesPayload(builder).build(); + } public static PersistableEnvelope fromProto(protobuf.PreferencesPayload proto, CoreProtoResolver coreProtoResolver) { - final protobuf.Country userCountry = proto.getUserCountry(); - PaymentAccount paymentAccount = null; - if (proto.hasSelectedPaymentAccountForCreateOffer() && proto.getSelectedPaymentAccountForCreateOffer().hasPaymentMethod()) - paymentAccount = PaymentAccount.fromProto(proto.getSelectedPaymentAccountForCreateOffer(), coreProtoResolver); - - return new PreferencesPayload( - proto.getUserLanguage(), - Country.fromProto(userCountry), - proto.getFiatCurrenciesList().isEmpty() ? new ArrayList<>() : - new ArrayList<>(proto.getFiatCurrenciesList().stream() - .map(FiatCurrency::fromProto) - .collect(Collectors.toList())), - proto.getCryptoCurrenciesList().isEmpty() ? new ArrayList<>() : - new ArrayList<>(proto.getCryptoCurrenciesList().stream() - .map(CryptoCurrency::fromProto) - .collect(Collectors.toList())), - BlockChainExplorer.fromProto(proto.getBlockChainExplorerMainNet()), - BlockChainExplorer.fromProto(proto.getBlockChainExplorerTestNet()), - BlockChainExplorer.fromProto(proto.getBsqBlockChainExplorer()), - ProtoUtil.stringOrNullFromProto(proto.getBackupDirectory()), - proto.getAutoSelectArbitrators(), - Maps.newHashMap(proto.getDontShowAgainMapMap()), - proto.getTacAccepted(), - proto.getUseTorForBitcoinJ(), - proto.getShowOwnOffersInOfferBook(), - proto.hasPreferredTradeCurrency() ? TradeCurrency.fromProto(proto.getPreferredTradeCurrency()) : null, - proto.getWithdrawalTxFeeInBytes(), - proto.getUseCustomWithdrawalTxFee(), - proto.getMaxPriceDistanceInPercent(), - ProtoUtil.stringOrNullFromProto(proto.getOfferBookChartScreenCurrencyCode()), - ProtoUtil.stringOrNullFromProto(proto.getTradeChartsScreenCurrencyCode()), - ProtoUtil.stringOrNullFromProto(proto.getBuyScreenCurrencyCode()), - ProtoUtil.stringOrNullFromProto(proto.getSellScreenCurrencyCode()), - proto.getTradeStatisticsTickUnitIndex(), - proto.getResyncSpvRequested(), - proto.getSortMarketCurrenciesNumerically(), - proto.getUsePercentageBasedPrice(), - Maps.newHashMap(proto.getPeerTagMapMap()), - proto.getBitcoinNodes(), - proto.getIgnoreTradersListList(), - proto.getDirectoryChooserPath(), - proto.getBuyerSecurityDepositAsLong(), - proto.getUseAnimations(), - paymentAccount, - proto.getPayFeeInBtc(), - proto.getBridgeAddressesList().isEmpty() ? null : new ArrayList<>(proto.getBridgeAddressesList()), - proto.getBridgeOptionOrdinal(), - proto.getTorTransportOrdinal(), - ProtoUtil.stringOrNullFromProto(proto.getCustomBridges()), - proto.getBitcoinNodesOptionOrdinal(), - proto.getReferralId().isEmpty() ? null : proto.getReferralId(), - proto.getPhoneKeyAndToken().isEmpty() ? null : proto.getPhoneKeyAndToken(), - proto.getUseSoundForMobileNotifications(), - proto.getUseTradeNotifications(), - proto.getUseMarketNotifications(), - proto.getUsePriceNotifications(), - proto.getUseStandbyMode(), - proto.getIsDaoFullNode(), - proto.getRpcUser().isEmpty() ? null : proto.getRpcUser(), - proto.getRpcPw().isEmpty() ? null : proto.getRpcPw(), - proto.getTakeOfferSelectedPaymentAccountId().isEmpty() ? null : proto.getTakeOfferSelectedPaymentAccountId(), - proto.getBuyerSecurityDepositAsPercent(), - proto.getIgnoreDustThreshold(), - proto.getBuyerSecurityDepositAsPercentForCrypto(), - proto.getBlockNotifyPort()); - - } + final protobuf.Country userCountry = proto.getUserCountry(); + PaymentAccount paymentAccount = null; + if (proto.hasSelectedPaymentAccountForCreateOffer() && proto.getSelectedPaymentAccountForCreateOffer().hasPaymentMethod()) + paymentAccount = PaymentAccount.fromProto(proto.getSelectedPaymentAccountForCreateOffer(), coreProtoResolver); + + return new PreferencesPayload( + proto.getUserLanguage(), + Country.fromProto(userCountry), + proto.getFiatCurrenciesList().isEmpty() ? new ArrayList<>() : + new ArrayList<>(proto.getFiatCurrenciesList().stream() + .map(FiatCurrency::fromProto) + .collect(Collectors.toList())), + proto.getCryptoCurrenciesList().isEmpty() ? new ArrayList<>() : + new ArrayList<>(proto.getCryptoCurrenciesList().stream() + .map(CryptoCurrency::fromProto) + .collect(Collectors.toList())), + BlockChainExplorer.fromProto(proto.getBlockChainExplorerMainNet()), + BlockChainExplorer.fromProto(proto.getBlockChainExplorerTestNet()), + BlockChainExplorer.fromProto(proto.getBsqBlockChainExplorer()), + ProtoUtil.stringOrNullFromProto(proto.getBackupDirectory()), + proto.getAutoSelectArbitrators(), + Maps.newHashMap(proto.getDontShowAgainMapMap()), + proto.getTacAccepted(), + proto.getUseTorForBitcoinJ(), + proto.getShowOwnOffersInOfferBook(), + proto.hasPreferredTradeCurrency() ? TradeCurrency.fromProto(proto.getPreferredTradeCurrency()) : null, + proto.getWithdrawalTxFeeInBytes(), + proto.getUseCustomWithdrawalTxFee(), + proto.getMaxPriceDistanceInPercent(), + ProtoUtil.stringOrNullFromProto(proto.getOfferBookChartScreenCurrencyCode()), + ProtoUtil.stringOrNullFromProto(proto.getTradeChartsScreenCurrencyCode()), + ProtoUtil.stringOrNullFromProto(proto.getBuyScreenCurrencyCode()), + ProtoUtil.stringOrNullFromProto(proto.getSellScreenCurrencyCode()), + proto.getTradeStatisticsTickUnitIndex(), + proto.getResyncSpvRequested(), + proto.getSortMarketCurrenciesNumerically(), + proto.getUsePercentageBasedPrice(), + Maps.newHashMap(proto.getPeerTagMapMap()), + proto.getBitcoinNodes(), + proto.getIgnoreTradersListList(), + proto.getDirectoryChooserPath(), + proto.getBuyerSecurityDepositAsLong(), + proto.getUseAnimations(), + paymentAccount, + proto.getPayFeeInBtc(), + proto.getBridgeAddressesList().isEmpty() ? null : new ArrayList<>(proto.getBridgeAddressesList()), + proto.getBridgeOptionOrdinal(), + proto.getTorTransportOrdinal(), + ProtoUtil.stringOrNullFromProto(proto.getCustomBridges()), + proto.getBitcoinNodesOptionOrdinal(), + proto.getReferralId().isEmpty() ? null : proto.getReferralId(), + proto.getPhoneKeyAndToken().isEmpty() ? null : proto.getPhoneKeyAndToken(), + proto.getUseSoundForMobileNotifications(), + proto.getUseTradeNotifications(), + proto.getUseMarketNotifications(), + proto.getUsePriceNotifications(), + proto.getUseStandbyMode(), + proto.getIsDaoFullNode(), + proto.getRpcUser().isEmpty() ? null : proto.getRpcUser(), + proto.getRpcPw().isEmpty() ? null : proto.getRpcPw(), + proto.getTakeOfferSelectedPaymentAccountId().isEmpty() ? null : proto.getTakeOfferSelectedPaymentAccountId(), + proto.getBuyerSecurityDepositAsPercent(), + proto.getIgnoreDustThreshold(), + proto.getBuyerSecurityDepositAsPercentForCrypto(), + proto.getBlockNotifyPort(), + proto.getUseBisqXmrWallet(), + proto.getXmrUserHost(), + proto.getXmrHostPort(), + proto.getXmrRpcUser(), + proto.getXmrRpcPwd(), + proto.getXmrHostsList(), + proto.getXmrHostOptionOrdinal()); + + } } diff --git a/core/src/main/java/bisq/core/xmr/XmrFormatter.java b/core/src/main/java/bisq/core/xmr/XmrFormatter.java new file mode 100644 index 00000000000..6bcbc594847 --- /dev/null +++ b/core/src/main/java/bisq/core/xmr/XmrFormatter.java @@ -0,0 +1,90 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.xmr; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +import javax.inject.Inject; + +import bisq.core.locale.GlobalSettings; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class XmrFormatter { + + public static final BigInteger MINIMUM_SENDABLE_AMOUNT = new BigInteger("2000000000"); + + private DecimalFormat amountFormat; + + @Inject + public XmrFormatter() { + super(); + + setFormatter(GlobalSettings.getLocale()); + amountFormat.setMinimumFractionDigits(2); + } + + private void setFormatter(Locale locale) { + amountFormat = (DecimalFormat) NumberFormat.getNumberInstance(locale); + amountFormat.setMinimumFractionDigits(12); + amountFormat.setMaximumFractionDigits(12); + } + + public static BigDecimal formatAsScaled(BigInteger amount) { + return new BigDecimal(amount != null ? amount : BigInteger.ZERO, 12); + } + + public String formatBigInteger(BigInteger amount) { + BigDecimal scaledAmount = formatAsScaled(amount); + return amountFormat.format(scaledAmount).concat(" XMR"); + } + + public String formatDateTime(Date date) { + return formatDateTime(date, true); + } + + public String formatDateTime(Date date, boolean useLocaleAndLocalTimezone) { + Locale locale = useLocaleAndLocalTimezone ? getLocale() : Locale.US; + DateFormat dateInstance = DateFormat.getDateInstance(DateFormat.DEFAULT, locale); + DateFormat timeInstance = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale); + if (!useLocaleAndLocalTimezone) { + dateInstance.setTimeZone(TimeZone.getTimeZone("UTC")); + timeInstance.setTimeZone(TimeZone.getTimeZone("UTC")); + } + return formatDateTime(date, dateInstance, timeInstance); + } + + public String formatDateTime(Date date, DateFormat dateFormatter, DateFormat timeFormatter) { + if (date != null) { + return dateFormatter.format(date) + " " + timeFormatter.format(date); + } else { + return ""; + } + } + + public Locale getLocale() { + return GlobalSettings.getLocale(); + } +} diff --git a/core/src/main/java/bisq/core/xmr/trade/XmrTradeAutomationInterceptor.java b/core/src/main/java/bisq/core/xmr/trade/XmrTradeAutomationInterceptor.java new file mode 100644 index 00000000000..b6b41f606a0 --- /dev/null +++ b/core/src/main/java/bisq/core/xmr/trade/XmrTradeAutomationInterceptor.java @@ -0,0 +1,58 @@ +package bisq.core.xmr.trade; + +import java.io.Serializable; + +import javax.inject.Inject; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +//TODO(niyid) In development +/* +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.stereotype.Component; +*/ + +import bisq.core.user.Preferences; + +/*@Aspect +@Component +@Configuration +@EnableAspectJAutoProxy*/ +public class XmrTradeAutomationInterceptor implements Serializable { + + protected final Logger log = LoggerFactory.getLogger(XmrTradeAutomationInterceptor.class); + + /** + * + */ + private static final long serialVersionUID = -603802504694339146L; + private Preferences preferences; + + @Inject + public XmrTradeAutomationInterceptor(Preferences preferences) { + this.preferences = preferences; + } + +/* + @Around("@annotation(LogExecutionTime)") + public Object transferFundsToSeller(ProceedingJoinPoint joinPoint) throws Throwable { + log.info("transferFundsToSeller({})", joinPoint.getArgs()); + + //TODO Get trade object (an instance of SellerAsMakerTrade or BuyerAsMakerTrade) and check the + //TODO Check if trade automation is active - PreferencesPayload.useBisqXmrWallet = true + //TODO Check if wallet balance can cover trade + //TODO If balance enough, do transfer + + Object returnValue = joinPoint.proceed(); + + return returnValue; + } +*/ +} diff --git a/core/src/main/java/bisq/core/xmr/wallet/TxProofHandler.java b/core/src/main/java/bisq/core/xmr/wallet/TxProofHandler.java new file mode 100644 index 00000000000..1a0f83fd222 --- /dev/null +++ b/core/src/main/java/bisq/core/xmr/wallet/TxProofHandler.java @@ -0,0 +1,28 @@ +package bisq.core.xmr.wallet; + +public interface TxProofHandler { + + /** + * + * @param txId + * @param message + * @param signature + */ + void update(String txId, String message, String signature); + + /** + * + */ + void playAnimation(); + + /** + * + */ + void stopAnimation(); + + /** + * + * @param resourceMessage + */ + void popupErrorWindow(String resourceMessage); +} diff --git a/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java b/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java new file mode 100644 index 00000000000..4c917aa5f99 --- /dev/null +++ b/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java @@ -0,0 +1,82 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have confirmed a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.xmr.wallet; + +import java.math.BigInteger; +import java.time.Instant; +import java.util.Date; + +import bisq.core.locale.Res; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import monero.wallet.model.MoneroTxWallet; + +@Slf4j +@EqualsAndHashCode +@Data +public class XmrTxListItem { + + @Getter + private final String txId; + @Getter + private final Date date; + @Getter + private final String paymentId; + @Getter + private final String direction; + @Getter + private BigInteger amount; + @Getter + private boolean confirmed; + @Getter + private int confirmations = 0; + @Getter + private String key; + @Getter + private Integer mixin; + @Getter + private Integer unlockTime; + @Getter + private String destinationAddress; + + public XmrTxListItem(MoneroTxWallet txWallet) { + txId = txWallet.getId(); + paymentId = txWallet.getPaymentId(); + Long timestamp = txWallet.getBlock().getTimestamp(); + date = timestamp != null && timestamp != 0 ? Date.from(Instant.ofEpochSecond(timestamp)) : null; + confirmed = txWallet.isConfirmed(); + confirmations = txWallet.getNumConfirmations(); + key = txWallet.getKey(); + mixin = txWallet.getMixin(); + unlockTime = txWallet.getUnlockTime(); + BigInteger valueSentToMe = txWallet.getIncomingAmount() != null ? txWallet.getIncomingAmount() : BigInteger.ZERO; + BigInteger valueSentFromMe = txWallet.getOutgoingAmount() != null ? txWallet.getOutgoingAmount() : BigInteger.ZERO; + if(txWallet.isOutgoing()) { + destinationAddress = txWallet.getOutgoingTransfer() != null && + txWallet.getOutgoingTransfer().getDestinations() != null ? + txWallet.getOutgoingTransfer().getDestinations().get(0).getAddress() : null; + } else { + destinationAddress = txWallet.getIncomingTransfers() != null ? + txWallet.getIncomingTransfers().get(0).getAddress() : null; + } + amount = txWallet.isIncoming() ? valueSentToMe : valueSentFromMe; + direction = txWallet.isIncoming() ? Res.get("shared.account.wallet.tx.item.in") : Res.get("shared.account.wallet.tx.item.out"); + } +} diff --git a/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java b/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java new file mode 100644 index 00000000000..14d88e784e3 --- /dev/null +++ b/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java @@ -0,0 +1,374 @@ +package bisq.core.xmr.wallet; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.IOException; +import java.math.BigInteger; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.function.Predicate; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.net.InetAddresses; + +import bisq.asset.CryptoNoteAddressValidator; +import bisq.common.UserThread; +import bisq.common.app.DevEnv; +import bisq.core.locale.Res; +import bisq.core.user.Preferences; +import bisq.core.xmr.wallet.listeners.WalletBalanceListener; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.control.ComboBox; +import javafx.scene.control.TextArea; +import lombok.extern.slf4j.Slf4j; +import monero.rpc.MoneroRpcConnection; +import monero.wallet.MoneroWalletRpc; +import monero.wallet.model.MoneroSendPriority; +import monero.wallet.model.MoneroSendRequest; +import monero.wallet.model.MoneroTxWallet; + +@Slf4j +@Singleton +public class XmrWalletRpcWrapper { + public static String HOST = "127.0.0.1"; + public static int PORT = 29088; + protected final Logger log = LoggerFactory.getLogger(this.getClass()); + private MoneroWalletRpc walletRpc; + private boolean xmrWalletRpcRunning = false; + private String primaryAddress; + + //TODO(niyid) onChangeListener to dynamically create and set new walletRpc instance. + //TODO(niyid) onChangeListener fires only after any of host, port, user, password have changed + //TODO(niyid) Only allow testnet, stagenet connections in dev/test. Only mainnet allowed in prod. + + @Inject + public XmrWalletRpcWrapper(Preferences preferences) { + log.info("instantiating MoneroWalletRpc..."); + HOST = preferences.getXmrUserHostDelegate(); + PORT = Integer.parseInt(preferences.getXmrHostPortDelegate()); + //TODO(niyid) Use preferences to determine which wallet to load in XmrWalletRpcWrapper + openWalletRpcInstance(null); + if(xmrWalletRpcRunning) { + //TODO(niyid) Uncomment later +/**/ + CryptoNoteAddressValidator validator; + long[] validPrefixes = {}; + if(DevEnv.isDevMode()) { + validPrefixes = new long[]{24, 36, 53, 63}; + validator = new CryptoNoteAddressValidator(true, validPrefixes); + } else { + validPrefixes = new long[]{18, 42}; + validator = new CryptoNoteAddressValidator(true, validPrefixes); + } + if(!validator.validate(primaryAddress).isValid()) { + log.info("Wallet RPC Connection not valid (MAINNET/TESTNET mix-up); shutting down..."); + xmrWalletRpcRunning = false; + walletRpc.close(); + walletRpc = null; + } +/**/ + } + } + + public void update(WalletBalanceListener listener, HashMap walletRpcData) { + Runnable command = new Runnable() { + + @Override + public void run() { + checkNotNull(walletRpc, Res.get("mainView.networkWarning.localhostLost", "Monero")); + listener.playAnimation(); + if(walletRpcData != null) { + long time0; + if(walletRpcData.containsKey("getBalance")) { + time0 = System.currentTimeMillis(); + BigInteger balance = walletRpc.getBalance(); + walletRpcData.put("getBalance", balance); + log.info("listen -time: {}ms - balance: {}", (System.currentTimeMillis() - time0), balance); + } + if(walletRpcData.containsKey("getUnlockedBalance")) { + time0 = System.currentTimeMillis(); + BigInteger unlockedBalance = walletRpc.getUnlockedBalance(); + walletRpcData.put("getUnlockedBalance", unlockedBalance); + log.info("listen -time: {}ms - unlockedBalance: {}", (System.currentTimeMillis() - time0)); + } + if(walletRpcData.containsKey("getPrimaryAddress")) { + time0 = System.currentTimeMillis(); + primaryAddress = walletRpc.getPrimaryAddress(); + walletRpcData.put("getPrimaryAddress", primaryAddress); + log.info("listen -time: {}ms - address: {}", (System.currentTimeMillis() - time0), primaryAddress); + } + if(walletRpcData.containsKey("getTxs")) { + time0 = System.currentTimeMillis(); + List txList = walletRpc.getTxs(); + if(txList != null && !txList.isEmpty()) { + walletRpcData.put("getTxs", transformTxWallet(txList)); + log.info("listen -time: {}ms - transactions: {}", (System.currentTimeMillis() - time0), txList.size()); + } else { + List list = Collections.emptyList(); + walletRpcData.put("getTxs", list); + } + } + } + listener.onUpdateBalances(walletRpcData); + listener.stopAnimation(); + } + }; + try { + Platform.runLater(command); + } catch (Exception e) { + listener.popupErrorWindow(Res.get("shared.account.wallet.popup.error.startupFailed")); + } + } + + private List transformTxWallet(List txList) { + Predicate predicate = new Predicate<>() { + + @Override + public boolean test(MoneroTxWallet t) { + return t.isRelayed(); + } + }; + List list = new ArrayList<>(); + txList.stream().filter(predicate).forEach(txWallet -> list.add(new XmrTxListItem(txWallet))); + log.info("transformTxWallet => {}", list.size()); + + return list.size() > 100 ? list.subList(0, 100) : list;//Reduce transactions to no more than 100. + } + + public void searchTx(WalletBalanceListener listener, String commaSeparatedIds) { + Runnable command = new Runnable() { + + @Override + public void run() { + checkNotNull(walletRpc, Res.get("mainView.networkWarning.localhostLost", "Monero")); + HashMap walletRpcData = new HashMap<>(); + listener.playAnimation(); + if(commaSeparatedIds != null && !commaSeparatedIds.isEmpty()) { + String searchParam = commaSeparatedIds.replaceAll(" ", ""); + + searchParam.split(","); + long time0 = System.currentTimeMillis(); + List txs = walletRpc.getTxs(Arrays.asList(searchParam.split(","))); + walletRpcData.put("getTxs", transformTxWallet(txs)); + log.info("listen -time: {}ms - searchTx: {}", (System.currentTimeMillis() - time0), txs.size()); + } + listener.onUpdateBalances(walletRpcData); + listener.stopAnimation(); + } + }; + try { + Platform.runLater(command); + } catch (Exception e) { + listener.popupErrorWindow(Res.get("shared.account.wallet.popup.error.startupFailed")); + } + } + + public void createTx(WalletBalanceListener listener, Integer accountIndex, String address, + BigInteger amount, MoneroSendPriority priority, boolean doNotRelay, HashMap walletRpcData) { + Runnable command = new Runnable() { + + @Override + public void run() { + checkNotNull(walletRpc, Res.get("mainView.networkWarning.localhostLost", "Monero")); + listener.playAnimation(); + long time0 = System.currentTimeMillis(); + MoneroSendRequest request = new MoneroSendRequest(accountIndex, address, amount, priority); + request.setDoNotRelay(doNotRelay); + MoneroTxWallet tx = walletRpc.send(request); + walletRpcData.put("getBalance", walletRpc.getBalance()); + walletRpcData.put("getUnlockedBalance", walletRpc.getUnlockedBalance()); + walletRpcData.put("getDoNotRelay", tx.getDoNotRelay()); + walletRpcData.put("getExtra", tx.getExtra()); + walletRpcData.put("getFee", tx.getFee()); + walletRpcData.put("getId", tx.getId()); + walletRpcData.put("getKey", tx.getKey()); + walletRpcData.put("getLastRelayedTimestamp", tx.getLastRelayedTimestamp() != null ? new Date(tx.getLastRelayedTimestamp()) : null); + walletRpcData.put("getMixin", tx.getMixin()); + walletRpcData.put("getNumConfirmations", tx.getNumConfirmations()); + walletRpcData.put("getOutgoingAmount", tx.getOutgoingAmount()); + walletRpcData.put("getOutgoingTransfer", tx.getOutgoingTransfer()); + walletRpcData.put("getPaymentId", tx.getPaymentId()); + walletRpcData.put("getTimestamp", tx.getBlock().getTimestamp() != null ? new Date(tx.getBlock().getTimestamp()) : null); + walletRpcData.put("getSize", tx.getSize()); + walletRpcData.put("getUnlockTime", tx.getUnlockTime()); + walletRpcData.put("getVersion", tx.getVersion()); + if(doNotRelay) { + walletRpcData.put("txToRelay", tx); + } + log.info("MoneroTxWallet => {}", walletRpcData); + log.info("createTx -time: {}ms - createTx: {}", (System.currentTimeMillis() - time0), tx.getSize()); + listener.onUpdateBalances(walletRpcData); + listener.stopAnimation(); + } + }; + try { + Platform.runLater(command); + } catch (Exception e) { + listener.popupErrorWindow(Res.get("shared.account.wallet.popup.error.startupFailed")); + } + } + + public void relayTx(WalletBalanceListener listener, HashMap walletRpcData) { + Runnable command = new Runnable() { + + @Override + public void run() { + checkNotNull(walletRpc, Res.get("mainView.networkWarning.localhostLost", "Monero")); + listener.playAnimation(); + MoneroTxWallet txToRelay = (MoneroTxWallet) walletRpcData.get("txToRelay"); + if(txToRelay != null) { + txToRelay.setDoNotRelay(false); + long time0 = System.currentTimeMillis(); + String txId = walletRpc.relayTx(txToRelay); + walletRpcData.put("txId", txId); + walletRpcData.put("getMetadata", txToRelay.getMetadata()); + log.info("relayTx metadata: {}", txToRelay.getMetadata()); + log.info("relayTx -time: {}ms - txId: {}", (System.currentTimeMillis() - time0), txId); + } + listener.stopAnimation(); + } + }; + try { + Platform.runLater(command); + } catch (Exception e) { + listener.popupErrorWindow(Res.get("shared.account.wallet.popup.error.startupFailed")); + } + } + + public void fetchLanguages(WalletBalanceListener listener, ComboBox languageComboBox) { + log.info("createWalletRpcInstance - {}, {}"); + ObservableList languageOptions = FXCollections.observableArrayList(); + Runnable command = new Runnable() { + + @Override + public void run() { + checkNotNull(walletRpc, Res.get("mainView.networkWarning.localhostLost", "Monero")); + languageOptions.addAll(walletRpc.getLanguages()); + languageComboBox.setItems(languageOptions); + } + }; + try { + Platform.runLater(command); + } catch (Exception e) { + listener.popupErrorWindow(Res.get("shared.account.wallet.popup.error.startupFailed")); + } + } + + public void openWalletRpcInstance(WalletBalanceListener listener) { + log.info("openWalletRpcInstance - {}, {}", HOST, PORT); + Thread checkIfXmrLocalHostNodeIsRunningThread = new Thread(() -> { + Thread.currentThread().setName("checkIfXmrLocalHostNodeIsRunningThread"); + Socket socket = null; + try { + socket = new Socket(); + socket.connect(new InetSocketAddress(InetAddresses.forString(HOST), PORT), 5000); + log.info("Localhost Monero Wallet RPC detected."); + UserThread.execute(() -> { + xmrWalletRpcRunning = true; + walletRpc = new MoneroWalletRpc(new MoneroRpcConnection("http://" + HOST + ":" + PORT)); + }); + } catch (Throwable e) { + log.info("createWalletRpcInstance - {}", e.getMessage()); + e.printStackTrace(); + if(listener != null) { + listener.popupErrorWindow(Res.get("shared.account.wallet.popup.error.startupFailed", "Monero", e.getLocalizedMessage())); + } + } finally { + if (socket != null) { + try { + socket.close(); + } catch (IOException ignore) { + } + } + } + }); + checkIfXmrLocalHostNodeIsRunningThread.start(); + } + + public boolean isXmrWalletRpcRunning() { + return xmrWalletRpcRunning; + } + + public void createWalletRpcInstance(WalletBalanceListener listener, String walletFile, String password, String language, TextArea seedWordsTextArea) { + log.info("createWalletRpcInstance - {}, {}, {}, {}", HOST, PORT, walletFile, password); + Thread checkIfXmrLocalHostNodeIsRunningThread = new Thread(() -> { + Thread.currentThread().setName("checkIfXmrLocalHostNodeIsRunningThread"); + Socket socket = null; + try { + socket = new Socket(); + socket.connect(new InetSocketAddress(InetAddresses.forString(HOST), PORT), 5000); + log.info("Localhost Monero Wallet RPC detected."); + UserThread.execute(() -> { + xmrWalletRpcRunning = true; + walletRpc = new MoneroWalletRpc(new MoneroRpcConnection("http://" + HOST + ":" + PORT)); + walletRpc.createWalletRandom(walletFile, password, language); + String mnemonic = walletRpc.getMnemonic(); + seedWordsTextArea.setText(mnemonic); + }); + } catch (Throwable e) { + log.info("createWalletRpcInstance - {}", e.getMessage()); + e.printStackTrace(); + if(listener != null) { + listener.popupErrorWindow(Res.get("shared.account.wallet.popup.error.startupFailed", "Monero", e.getLocalizedMessage())); + } + } finally { + if (socket != null) { + try { + socket.close(); + } catch (IOException ignore) { + } + } + } + }); + checkIfXmrLocalHostNodeIsRunningThread.start(); + } + + public void handleTxProof(TxProofHandler handler, String txId, String message) { + Runnable command = new Runnable() { + + @Override + public void run() { + checkNotNull(walletRpc, Res.get("mainView.networkWarning.localhostLost", "Monero")); + handler.playAnimation(); + if(handler != null) { + long time0 = System.currentTimeMillis(); + String signature = walletRpc.getSpendProof(txId, message); + log.info("relayTx -time: {}ms - txId: {}", (System.currentTimeMillis() - time0), txId); + log.info("relayTx signature: {}", signature); + handler.update(txId, message, signature); + } + handler.stopAnimation(); + } + }; + try { + Platform.runLater(command); + } catch (Exception e) { + handler.popupErrorWindow(Res.get("shared.account.wallet.popup.error.startupFailed")); + } + } + + public MoneroWalletRpc getWalletRpc() { + return walletRpc; + } + + public String getPrimaryAddress() { + if(primaryAddress == null) { + walletRpc = new MoneroWalletRpc(new MoneroRpcConnection("http://" + HOST + ":" + PORT)); + primaryAddress = walletRpc.getPrimaryAddress(); + } + return primaryAddress; + } +} diff --git a/core/src/main/java/bisq/core/xmr/wallet/listeners/WalletBalanceListener.java b/core/src/main/java/bisq/core/xmr/wallet/listeners/WalletBalanceListener.java new file mode 100644 index 00000000000..b378f44b297 --- /dev/null +++ b/core/src/main/java/bisq/core/xmr/wallet/listeners/WalletBalanceListener.java @@ -0,0 +1,44 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.xmr.wallet.listeners; + +import java.util.HashMap; + +public interface WalletBalanceListener { + /** + * + * @param walletRpcData + */ + void onUpdateBalances(HashMap walletRpcData); + + /** + * + */ + void playAnimation(); + + /** + * + */ + void stopAnimation(); + + /** + * + * @param resourceMessage + */ + void popupErrorWindow(String resourceMessage); +} diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 3677449f242..94b1e1fe162 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -62,10 +62,6 @@ shared.priceWithCur=Price in {0} shared.priceInCurForCur=Price in {0} for 1 {1} shared.fixedPriceInCurForCur=Fixed price in {0} for 1 {1} shared.amount=Amount -shared.txFee=Transaction Fee -shared.makerFee=Maker Fee -shared.buyerSecurityDeposit=Buyer Deposit -shared.sellerSecurityDeposit=Seller Deposit shared.amountWithCur=Amount in {0} shared.volumeWithCur=Volume in {0} shared.currency=Currency @@ -207,6 +203,85 @@ shared.proposal=Proposal shared.votes=Votes shared.learnMore=Learn more shared.dismiss=Dismiss +shared.yes=YES +shared.no=NO +shared.search=Search + +shared.account.wallet.host=Select {0} Wallet host +shared.account.wallet.port={0} Wallet port +shared.account.wallet.user={0} Wallet user +shared.account.wallet.pass={0} Wallet password +shared.account.wallet.menuItem.send=Send +shared.account.wallet.menuItem.receive=Receive +shared.account.wallet.menuItem.transactions=Transactions +shared.account.wallet.menuItem.addWallet=Add wallet +shared.account.wallet.password.enterWalletFile=Enter wallet file +shared.account.wallet.password.openWallet=Open wallet +shared.account.wallet.password.createWallet=Create wallet +shared.account.wallet.password.enterPassword=Enter password +shared.account.wallet.password.confirmPassword=Confirm password +shared.account.wallet.dashboard.myBalance=My wallet balance +shared.account.wallet.receive.fundYourWallet=Your receive address +shared.account.wallet.receive.walletAddress=Wallet address +shared.account.wallet.dashboard.actualBalance=Actual balance +shared.account.wallet.dashboard.unlockedBalance=Unlocked/available balance +shared.account.wallet.dashboard.unconfirmedChangeBalance=Balance of all unconfirmed +shared.account.wallet.dashboard.unverifiedBalance=Balance of all unverified +shared.account.wallet.dashboard.totalBalance=Total balance +shared.account.wallet.send.sendFunds=Send funds +shared.account.wallet.send.priority=Choose priority +shared.account.wallet.send.priorityLabel=Select a priority option +shared.account.wallet.send.receiverAddress=Receiver's address +shared.account.wallet.send.setAmount=Set amount to withdraw (min. amount is {0}) +shared.account.wallet.send.feeAmount=Last transaction fee +shared.account.wallet.send.sendFunds.headline=Confirm withdrawal request + +shared.account.wallet.send.sendFunds.details=Sending: {0}\nTo receiving address: {1}.\nRequired transaction fee is: {2} ({3} byte)\nTransaction size: {4} Kb\n\nThe recipient will receive: {5}\n\nAre you sure you want to withdraw that amount? +shared.account.wallet.send.send=Send funds +shared.account.wallet.message.info.openWallet=Opening wallet; please wait... +shared.account.wallet.message.info.createWallet=Creating wallet; please wait... +shared.account.wallet.popup.error.walletNameRequired=Wallet name is required +shared.account.wallet.popup.error.languageRequired=Language is required +shared.account.wallet.popup.error.startupFailed=Connection to Wallet failed +shared.account.wallet.popup.error.transactionFailed=An error occurred with the transaction: {0} +shared.account.wallet.popup.error.balanceTooLow={0} wallet {1} balance too low: {2} +shared.account.wallet.popup.error.txProofError=Transaction proof cannot be performed: missing signatures. + +shared.account.wallet.tx.searchPrompt=Comma separated transaction ids +shared.account.wallet.tx.proof=Transaction Proof +shared.account.wallet.tx.item.in=IN +shared.account.wallet.tx.item.out=OUT +shared.account.wallet.tx.item.na=N/A +shared.account.wallet.tx.item.txId=Transaction ID +shared.account.wallet.tx.item.paymentId=Payment ID +shared.account.wallet.tx.item.key=Key +shared.account.wallet.tx.item.datetime=Received At +shared.account.wallet.tx.item.direction=Direction +shared.account.wallet.tx.item.amount=Amount +shared.account.wallet.tx.item.confirmed=Confirmed +shared.account.wallet.tx.item.mixin=Mixin +shared.account.wallet.tx.item.confirmations=Confirmations +shared.account.wallet.tx.item.message=Message +shared.account.wallet.tx.item.destination=Destination +shared.account.wallet.tx.item.signature=Signature + +shared.account.wallet.validation.empty=Empty input is not allowed. +shared.account.wallet.validation.NaN=Input is not a valid number. +shared.account.wallet.validation.notAnInteger=Input is not an integer value. +shared.account.wallet.validation.zero=Input of 0 is not allowed. +shared.account.wallet.validation.negative=A negative value is not allowed. +shared.account.wallet.validation.fraction=Input results in a {0} value with a fraction of the smallest unit. +shared.account.wallet.validation.toLarge=Input larger than {0} is not allowed. +shared.account.wallet.validation.toSmall=Input smaller than {0} is not allowed. +shared.account.wallet.validation.securityDeposit.toSmall=Input smaller than {0} is not allowed. +shared.account.wallet.validation.passwordTooShort=The password you entered is too short. It needs to have min. 8 characters. +shared.account.wallet.validation.passwordTooLong=The password you entered is too long. It cannot be longer than 50 characters. +shared.account.wallet.validation.invalidAddress=The address is not correct. Please check the address format. +shared.account.wallet.validation.integerOnly=Please enter integer numbers only. +shared.account.wallet.validation.inputError=Your input caused an error:\n{0} +shared.account.wallet.validation.insufficientBalance=Your available balance is {0}. +shared.account.wallet.validation.exceedsMaxTradeLimit=Your trade limit is {0}. +shared.account.wallet.validation.amountBelowMinAmount=Min. amount is {0} #################################################################### # UI views @@ -249,7 +324,7 @@ mainView.footer.btcInfo.synchronizingWith=Synchronizing with mainView.footer.btcInfo.synchronizedWith=Synchronized with mainView.footer.btcInfo.connectingTo=Connecting to mainView.footer.btcInfo.connectionFailed=connection failed -mainView.footer.p2pInfo=Bisq network peers: {0} +mainView.footer.p2pInfo=P2P network peers: {0} mainView.footer.daoFullNode=DAO full node mainView.bootstrapState.connectionToTorNetwork=(1/4) Connecting to Tor network... @@ -259,16 +334,18 @@ mainView.bootstrapState.initialDataReceived=(4/4) Initial data received mainView.bootstrapWarning.noSeedNodesAvailable=No seed nodes available mainView.bootstrapWarning.noNodesAvailable=No seed nodes and peers available -mainView.bootstrapWarning.bootstrappingToP2PFailed=Bootstrapping to Bisq network failed +mainView.bootstrapWarning.bootstrappingToP2PFailed=Bootstrapping to P2P network failed mainView.p2pNetworkWarnMsg.noNodesAvailable=There are no seed nodes or persisted peers available for requesting data.\nPlease check your internet connection or try to restart the application. -mainView.p2pNetworkWarnMsg.connectionToP2PFailed=Connecting to the Bisq network failed (reported error: {0}).\nPlease check your internet connection or try to restart the application. +mainView.p2pNetworkWarnMsg.connectionToP2PFailed=Connecting to the P2P network failed (reported error: {0}).\nPlease check your internet connection or try to restart the application. mainView.walletServiceErrorMsg.timeout=Connecting to the Bitcoin network failed because of a timeout. mainView.walletServiceErrorMsg.connectionError=Connection to the Bitcoin network failed because of an error: {0} +mainView.genericErrorMsg.connectionError=Connection to the {0} network failed because of an error: {1} mainView.networkWarning.allConnectionsLost=You lost the connection to all {0} network peers.\nMaybe you lost your internet connection or your computer was in standby mode. mainView.networkWarning.localhostBitcoinLost=You lost the connection to the localhost Bitcoin node.\nPlease restart the Bisq application to connect to other Bitcoin nodes or restart the localhost Bitcoin node. +mainView.networkWarning.localhostLost=You lost the connection to the localhost {0} node.\nPlease restart the Bisq application to connect to other {0} nodes or restart the localhost {0} node. mainView.version.update=(Update available) @@ -918,6 +995,7 @@ setting.preferences.avoidStandbyMode=Avoid standby mode setting.preferences.deviationToLarge=Values higher than {0}% are not allowed. setting.preferences.txFee=Withdrawal transaction fee (satoshis/byte) setting.preferences.useCustomValue=Use custom value +setting.preferences.useBisqXmrWallet=Use Bisq Wallets (XMR) setting.preferences.txFeeMin=Transaction fee must be at least {0} satoshis/byte setting.preferences.txFeeTooLarge=Your input is above any reasonable value (>5000 satoshis/byte). Transaction fee is usually in the range of 50-400 satoshis/byte. setting.preferences.ignorePeers=Ignored peers [onion address:port] @@ -945,7 +1023,7 @@ settings.preferences.selectCurrencyNetwork=Select network setting.preferences.daoOptions=DAO options setting.preferences.dao.resync.label=Rebuild DAO state from genesis tx setting.preferences.dao.resync.button=Resync -setting.preferences.dao.resync.popup=After an application restart the Bisq network governance data will be reloaded from \ +setting.preferences.dao.resync.popup=After an application restart the P2P network governance data will be reloaded from \ the seed nodes and the BSQ consensus state will be rebuilt from the genesis transaction. setting.preferences.dao.isDaoFullNode=Run Bisq as DAO full node setting.preferences.dao.rpcUser=RPC username @@ -958,7 +1036,7 @@ setting.preferences.dao.fullNodeInfo.ok=Open docs page setting.preferences.dao.fullNodeInfo.cancel=No, I stick with lite node mode settings.net.btcHeader=Bitcoin network -settings.net.p2pHeader=Bisq network +settings.net.p2pHeader=P2P network settings.net.onionAddressLabel=My onion address settings.net.btcNodesLabel=Use custom Bitcoin Core nodes settings.net.bitcoinPeersLabel=Connected peers @@ -1041,20 +1119,23 @@ because Bisq is a decentralized exchange, all your data is kept on your computer account.menu.paymentAccount=National currency accounts account.menu.altCoinsAccountView=Altcoin accounts +account.menu.altCoinsWalletView=Altcoin Wallets account.menu.password=Wallet password account.menu.seedWords=Wallet seed account.menu.backup=Backup account.menu.notifications=Notifications +account.menu.wallets.moneroWalletView=Monero +account.menu.wallets.monero.navigation.funds.depositFunds=\"Funds/Receive funds\" account.arbitratorRegistration.pubKey=Public key account.arbitratorRegistration.register=Register arbitrator account.arbitratorRegistration.revoke=Revoke registration account.arbitratorRegistration.info.msg=Please note that you need to stay available for 15 days after revoking as there might be trades which are using you as arbitrator. The max. allowed trade period is 8 days and the dispute process might take up to 7 days. account.arbitratorRegistration.warn.min1Language=You need to set at least 1 language.\nWe added the default language for you. -account.arbitratorRegistration.removedSuccess=You have successfully removed your arbitrator from the Bisq network. +account.arbitratorRegistration.removedSuccess=You have successfully removed your arbitrator from the P2P network. account.arbitratorRegistration.removedFailed=Could not remove arbitrator.{0} -account.arbitratorRegistration.registerSuccess=You have successfully registered your arbitrator to the Bisq network. +account.arbitratorRegistration.registerSuccess=You have successfully registered your arbitrator to the P2P network. account.arbitratorRegistration.registerFailed=Could not register arbitrator.{0} account.arbitratorSelection.minOneArbitratorRequired=You need to set at least 1 language.\nWe added the default language for you. @@ -1113,29 +1194,6 @@ arbitrator in case of a dispute.\n\n\ There is no payment ID required, just the normal public address.\n\ If you are not sure about that process visit (https://www.getmonero.org/resources/user-guides/prove-payment.html) \ or the Monero forum (https://forum.getmonero.org) to find more information. -account.altcoin.popup.msr.msg=Trading MSR on Bisq requires that you understand and fulfill \ -the following requirements:\n\n\ -For sending MSR, you need to use either the official Masari GUI wallet, Masari CLI wallet with the \ -store-tx-info flag enabled (enabled by default) or the Masari web wallet (https://wallet.getmasari.org). Please be sure you can access the tx key as \ -that would be required in case of a dispute.\n\ -masari-wallet-cli (use the command get_tx_key)\n\ -masari-wallet-gui (go to history tab and click on the (P) button for payment proof)\n\n\ -Masari Web Wallet (goto Account -> transaction history and view details on your sent transaction)\n\n -Verification can be accomplished in-wallet.\n\ -masari-wallet-cli : using command (check_tx_key).\n\ -masari-wallet-gui : on the Advanced > Prove/Check page.\n\ -Verification can be accomplished in the block explorer \n\ -Open block explorer (https://explorer.getmasari.org), use the search bar to find your transaction hash.\n\ -Once transaction is found, scroll to bottom to the 'Prove Sending' area and fill in details as needed.\n\ -You need to provide the arbitrator the following data in case of a dispute:\n\ -- The tx private key\n\ -- The transaction hash\n\ -- The recipient's public address\n\n\ -Failure to provide the above data, or if you used an incompatible wallet, will result in losing the \ -dispute case. The MSR sender is responsible for providing verification of the MSR transfer to the \ -arbitrator in case of a dispute.\n\n\ -There is no payment ID required, just the normal public address.\n\ -If you are not sure about that process, ask for help on the Official Masari Discord (https://discord.gg/sMCwMqs). account.altcoin.popup.blur.msg=Trading BLUR on Bisq requires that you understand and fulfill \ the following requirements:\n\n\ To send BLUR you must use the Blur Network CLI or GUI Wallet. \n\n\ @@ -1151,18 +1209,6 @@ transfer using the Blur Transaction Viewer (https://blur.cash/#tx-viewer).\n\n\ Failure to provide the required information to the arbitrator will result in losing the dispute case. In all cases of dispute, the \ BLUR sender bears 100% of the burden of responsibility in verifying transactions to an arbitrator. \n\n\ If you do not understand these requirements, do not trade on Bisq. First, seek help at the Blur Network Discord (https://discord.gg/dMWaqVW). -account.altcoin.popup.solo.msg=Trading Solo on Bisq requires that you understand and fulfill \ -the following requirements:\n\n\ -To send Solo you must use the Solo Network CLI Wallet. \n\n\ -If you are using the CLI wallet, a transaction hash (tx ID) will be displayed after a transfer is sent. You must save \ -this information. Immediately after sending the transfer, you must use the command 'get_tx_key' to retrieve the \ -transaction private key. If you fail to perform this step, you may not be able to retrieve the key later. \n\n\ -In the event that arbitration is necessary, you must present the following to an arbitrator: 1.) the transaction ID, \ -2.) the transaction private key, and 3.) the recipient's address. The arbitrator will then verify the Solo \ -transfer using the Solo Block Explorer by searching for the transaction and then using the "Prove sending" function (https://explorer.minesolo.com/).\n\n\ -failure to provide the required information to the arbitrator will result in losing the dispute case. In all cases of dispute, the \ -Solo sender bears 100% of the burden of responsibility in verifying transactions to an arbitrator. \n\n\ -If you do not understand these requirements, do not trade on Bisq. First, seek help at the Solo Network Discord (https://discord.minesolo.com/). account.altcoin.popup.cash2.msg=Trading CASH2 on Bisq requires that you understand and fulfill \ the following requirements:\n\n\ To send CASH2 you must use the Cash2 Wallet version 3 or higher. \n\n\ @@ -1522,7 +1568,7 @@ dao.results.cycle.value.postFix.isDefaultValue=(default value) dao.results.cycle.value.postFix.hasChanged=(has been changed in voting) dao.results.invalidVotes=We had invalid votes in that voting cycle. That can happen if a vote was \ - not distributed well in the Bisq network.\n{0} + not distributed well in the P2P network.\n{0} # suppress inspection "UnusedProperty" dao.phase.PHASE_UNDEFINED=Undefined @@ -2011,8 +2057,6 @@ dao.monitor.daoState.utxoConflicts=UTXO conflicts dao.monitor.daoState.utxoConflicts.blockHeight=Block height: {0} dao.monitor.daoState.utxoConflicts.sumUtxo=Sum of all UTXO: {0} BSQ dao.monitor.daoState.utxoConflicts.sumBsq=Sum of all BSQ: {0} BSQ -dao.monitor.daoState.checkpoint.popup=DAO state is not in sync with the network. \ - After restart the DAO state will resync. dao.monitor.proposal.headline=Proposals state dao.monitor.proposal.table.headline=Chain of proposal state hashes @@ -2323,6 +2367,8 @@ popup.warning.tradePeriod.halfReached=Your trade with ID {0} has reached the hal popup.warning.tradePeriod.ended=Your trade with ID {0} has reached the max. allowed trading period and is not completed.\n\nThe trade period ended on {1}\n\nPlease check your trade at \"Portfolio/Open trades\" for contacting the arbitrator. popup.warning.noTradingAccountSetup.headline=You have not setup a trading account popup.warning.noTradingAccountSetup.msg=You need to setup a national currency or altcoin account before you can create an offer.\nDo you want to setup an account? +popup.warning.noXmrWalletRpcSetup.headline=You have not setup a Monero RPC Wallet +popup.warning.noXmrWalletRpcSetup.msg=You need to setup a Monero RPC Wallet and have it running if the '{0}' switch on the preferences screen is turned on.\nDo you want to turn it off? popup.warning.noArbitratorsAvailable=There are no arbitrators available. popup.warning.notFullyConnected=You need to wait until you are fully connected to the network.\nThat might take up to about 2 minutes at startup. popup.warning.notSufficientConnectionsToBtcNetwork=You need to wait until you have at least {0} connections to the Bitcoin network. @@ -2332,7 +2378,9 @@ popup.warning.tooLargePercentageValue=You cannot set a percentage of 100% or lar popup.warning.examplePercentageValue=Please enter a percentage number like \"5.4\" for 5.4% popup.warning.noPriceFeedAvailable=There is no price feed available for that currency. You cannot use a percent based price.\nPlease select the fixed price. popup.warning.sendMsgFailed=Sending message to your trading partner failed.\nPlease try again and if it continue to fail report a bug. -popup.warning.insufficientBtcFundsForBsqTx=You don''t have sufficient BTC funds for paying the miner fee for that transaction.\n\ +popup.warning.insufficientBtcFundsForBsqTx=You don''t have sufficient BTC funds for paying the miner fee for that transaction.\n +popup.warning.insufficientFundsForTx=You don''t have sufficient funds for paying the miner fee for this transaction.\n\ + Please fund your BTC wallet.\nMissing funds: {0} popup.warning.bsqChangeBelowDustException=This transaction creates a BSQ change output which is below dust \ limit (5.46 BSQ) and would be rejected by the Bitcoin network.\n\n\ @@ -2482,7 +2530,6 @@ list.currency.editList=Edit currency list table.placeholder.noItems=Currently there are no {0} available table.placeholder.noData=Currently there is no data available -table.placeholder.processingData=Processing data... peerInfoIcon.tooltip.tradePeer=Trading peer's @@ -2495,6 +2542,7 @@ peerInfoIcon.tooltip.unknownAge=Payment account age not known. tooltip.openPopupForDetails=Open popup for details tooltip.openBlockchainForAddress=Open external blockchain explorer for address: {0} tooltip.openBlockchainForTx=Open external blockchain explorer for transaction: {0} +tooltip.openTxProof=Open transaction proof: {0} confidence.unknown=Unknown transaction status confidence.seen=Seen by {0} peer(s) / 0 confirmations @@ -2599,6 +2647,8 @@ password.forgotPassword=Forgot password? password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\n\ It is highly recommended to make a backup of the application directory and write down your seed words before setting a password! password.backupWasDone=I have already done a backup +confirm.tooLong=Password confirmation must be less than 500 characters. +confirm.noMatch=Password confirmation did not match. seed.seedWords=Wallet seed words seed.enterSeedWords=Enter wallet seed words @@ -2786,7 +2836,7 @@ payment.f2f.info='Face to Face' trades have different rules and come with differ To be sure you fully understand the differences with 'Face to Face' trades please read the instructions and \ recommendations at: 'https://docs.bisq.network/trading-rules.html#f2f-trading' payment.f2f.info.openURL=Open web page -payment.f2f.offerbook.tooltip.countryAndCity=Country and city: {0} / {1} +payment.f2f.offerbook.tooltip.countryAndCity=County and city: {0} / {1} payment.f2f.offerbook.tooltip.extra=Additional information: {0} diff --git a/desktop/src/main/java/bisq/desktop/components/WalletAddressTextField.java b/desktop/src/main/java/bisq/desktop/components/WalletAddressTextField.java new file mode 100644 index 00000000000..67fdb267055 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/components/WalletAddressTextField.java @@ -0,0 +1,139 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.components; + +import bisq.desktop.main.overlays.notifications.Notification; +import bisq.desktop.util.GUIUtil; + +import bisq.core.locale.Res; + +import bisq.common.util.Utilities; + +import org.bitcoinj.core.Coin; + +import de.jensd.fx.fontawesome.AwesomeDude; +import de.jensd.fx.fontawesome.AwesomeIcon; + +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.AnchorPane; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +//This is a generic wallet address component comparable to the BSQ specific BsqAddressTextField component. +public class WalletAddressTextField extends AnchorPane { + private final StringProperty address = new SimpleStringProperty(); + private final StringProperty paymentLabel = new SimpleStringProperty(); + private final ObjectProperty amountAsCoin = new SimpleObjectProperty<>(Coin.ZERO); + private boolean wasPrimaryButtonDown; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + public WalletAddressTextField() { + TextField textField = new BisqTextField(); + textField.setId("address-text-field"); + textField.setEditable(false); + textField.textProperty().bind(address); + String tooltipText = Res.get("addressTextField.copyToClipboard"); + textField.setTooltip(new Tooltip(tooltipText)); + + textField.setOnMousePressed(event -> wasPrimaryButtonDown = event.isPrimaryButtonDown()); + textField.setOnMouseReleased(event -> { + if (wasPrimaryButtonDown && address.get() != null && address.get().length() > 0) { + Utilities.copyToClipboard(address.get()); + Notification walletFundedNotification = new Notification() + .notification(Res.get("addressTextField.addressCopiedToClipboard")) + .hideCloseButton() + .autoClose(); + + walletFundedNotification.show(); + } + + wasPrimaryButtonDown = false; + }); + + textField.focusTraversableProperty().set(focusTraversableProperty().get()); + //TODO app wide focus + //focusedProperty().addListener((ov, oldValue, newValue) -> textField.requestFocus()); + + + Label copyIcon = new Label(); + copyIcon.setLayoutY(3); + copyIcon.getStyleClass().addAll("icon", "highlight"); + copyIcon.setTooltip(new Tooltip(Res.get("addressTextField.copyToClipboard"))); + AwesomeDude.setIcon(copyIcon, AwesomeIcon.COPY); + copyIcon.setOnMouseClicked(e -> GUIUtil.showFeeInfoBeforeExecute(() -> { + if (address.get() != null && address.get().length() > 0) + Utilities.copyToClipboard(address.get()); + })); + + AnchorPane.setRightAnchor(copyIcon, 5.0); + AnchorPane.setRightAnchor(textField, 30.0); + AnchorPane.setLeftAnchor(textField, 0.0); + + getChildren().addAll(textField, copyIcon); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters/Setters + /////////////////////////////////////////////////////////////////////////////////////////// + + public void setAddress(String address) { + this.address.set(address); + } + + public String getAddress() { + return address.get(); + } + + public StringProperty addressProperty() { + return address; + } + + public Coin getAmountAsCoin() { + return amountAsCoin.get(); + } + + public ObjectProperty amountAsCoinProperty() { + return amountAsCoin; + } + + public void setAmountAsCoin(Coin amountAsCoin) { + this.amountAsCoin.set(amountAsCoin); + } + + public String getPaymentLabel() { + return paymentLabel.get(); + } + + public StringProperty paymentLabelProperty() { + return paymentLabel; + } + + public void setPaymentLabel(String paymentLabel) { + this.paymentLabel.set(paymentLabel); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/account/AccountView.fxml b/desktop/src/main/java/bisq/desktop/main/account/AccountView.fxml index e7b9c5b0fed..c9929dfe861 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/AccountView.fxml +++ b/desktop/src/main/java/bisq/desktop/main/account/AccountView.fxml @@ -28,13 +28,16 @@ xmlns:fx="http://javafx.com/fxml"> + + + diff --git a/desktop/src/main/java/bisq/desktop/main/account/AccountView.java b/desktop/src/main/java/bisq/desktop/main/account/AccountView.java index 6e880fd65ee..43e18b97f75 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/AccountView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/AccountView.java @@ -31,6 +31,7 @@ import bisq.desktop.main.account.content.notifications.MobileNotificationsView; import bisq.desktop.main.account.content.password.PasswordView; import bisq.desktop.main.account.content.seedwords.SeedWordsView; +import bisq.desktop.main.account.content.wallet.AltCoinWalletsView; import bisq.desktop.main.overlays.popups.Popup; import bisq.core.locale.Res; @@ -61,7 +62,7 @@ public class AccountView extends ActivatableView { @FXML - Tab fiatAccountsTab, altcoinAccountsTab, notificationTab, + Tab fiatAccountsTab, altcoinAccountsTab, altcoinWalletsTab, notificationTab, passwordTab, seedwordsTab, backupTab; private Navigation.Listener navigationListener; @@ -89,6 +90,7 @@ public void initialize() { fiatAccountsTab.setText(Res.get("account.menu.paymentAccount").toUpperCase()); altcoinAccountsTab.setText(Res.get("account.menu.altCoinsAccountView").toUpperCase()); + altcoinWalletsTab.setText(Res.get("account.menu.altCoinsWalletView").toUpperCase()); notificationTab.setText(Res.get("account.menu.notifications").toUpperCase()); passwordTab.setText(Res.get("account.menu.password").toUpperCase()); seedwordsTab.setText(Res.get("account.menu.seedWords").toUpperCase()); @@ -123,6 +125,8 @@ public void initialize() { navigation.navigateTo(MainView.class, AccountView.class, FiatAccountsView.class); } else if (newValue == altcoinAccountsTab && selectedTab != altcoinAccountsTab) { navigation.navigateTo(MainView.class, AccountView.class, AltCoinAccountsView.class); + } else if (newValue == altcoinWalletsTab && selectedTab != altcoinWalletsTab) { + navigation.navigateTo(MainView.class, AccountView.class, AltCoinWalletsView.class); } else if (newValue == notificationTab && selectedTab != notificationTab) { navigation.navigateTo(MainView.class, AccountView.class, MobileNotificationsView.class); } else if (newValue == passwordTab && selectedTab != passwordTab) { @@ -167,6 +171,8 @@ else if (root.getSelectionModel().getSelectedItem() == fiatAccountsTab) navigation.navigateTo(MainView.class, AccountView.class, FiatAccountsView.class); else if (root.getSelectionModel().getSelectedItem() == altcoinAccountsTab) navigation.navigateTo(MainView.class, AccountView.class, AltCoinAccountsView.class); + else if (root.getSelectionModel().getSelectedItem() == altcoinWalletsTab) + navigation.navigateTo(MainView.class, AccountView.class, AltCoinWalletsView.class); else if (root.getSelectionModel().getSelectedItem() == notificationTab) navigation.navigateTo(MainView.class, AccountView.class, MobileNotificationsView.class); else if (root.getSelectionModel().getSelectedItem() == passwordTab) @@ -214,6 +220,8 @@ private void loadView(Class viewClass) { selectedTab = fiatAccountsTab; } else if (view instanceof AltCoinAccountsView) { selectedTab = altcoinAccountsTab; + } else if (view instanceof AltCoinWalletsView) { + selectedTab = altcoinWalletsTab; } else if (view instanceof MobileNotificationsView) { selectedTab = notificationTab; } else if (view instanceof PasswordView) { diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/AltCoinWalletsView.fxml b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/AltCoinWalletsView.fxml new file mode 100644 index 00000000000..410b08f7fc1 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/AltCoinWalletsView.fxml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/AltCoinWalletsView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/AltCoinWalletsView.java new file mode 100644 index 00000000000..5f82d6fa443 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/AltCoinWalletsView.java @@ -0,0 +1,126 @@ +package bisq.desktop.main.account.content.wallet; + +import javax.inject.Inject; + +import bisq.core.locale.Res; +import bisq.core.user.Preferences; +import bisq.desktop.Navigation; +import bisq.desktop.common.view.ActivatableView; +import bisq.desktop.common.view.CachingViewLoader; +import bisq.desktop.common.view.FxmlView; +import bisq.desktop.common.view.View; +import bisq.desktop.common.view.ViewLoader; +import bisq.desktop.main.MainView; +import bisq.desktop.main.account.AccountView; +import bisq.desktop.main.account.content.wallet.monero.XmrWalletView; +import javafx.beans.value.ChangeListener; +import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; + +@FxmlView +public class AltCoinWalletsView extends ActivatableView { + + @FXML + Tab moneroWalletTab; + + private Navigation.Listener navigationListener; + private ChangeListener tabChangeListener; + + private Tab selectedTab; + private final ViewLoader viewLoader; + private final Navigation navigation; + private Class currentTabView; + private Preferences preferences; + + @Inject + private AltCoinWalletsView(CachingViewLoader viewLoader, Navigation navigation, Preferences preferences) { + this.navigation = navigation; + this.viewLoader = viewLoader; + this.preferences = preferences; + } + + @Override + public void initialize() { + log.info("initialize()"); + //TODO Update as altcoinWalletsTab.setVisible(Preferences.PreferencesPayload.useBisqXmrWallet) + + root.setPadding(new Insets(20)); + navigationListener = viewPath -> { + log.info("navigationListener.viewPath1=" + viewPath); + if(selectedTab == null) { + selectedTab = moneroWalletTab; + } + if(selectedTab == moneroWalletTab) { + currentTabView = XmrWalletView.class; + }//TODO Tabs for other altcoins added here + navigation.navigateTo(MainView.class, AccountView.class, AltCoinWalletsView.class, currentTabView); + log.info("navigationListener.viewPath2=" + viewPath); + if (viewPath.size() == 4 && viewPath.indexOf(AccountView.class) == 1) { + loadView(viewPath.tip()); + } else { + loadView(XmrWalletView.class); + } + }; + + tabChangeListener = (ov, oldValue, newValue) -> { + log.info("tabChangeListener.oldValue=" + oldValue); + log.info("tabChangeListener.newValue=" + newValue); + if (newValue == moneroWalletTab && selectedTab != moneroWalletTab) { + loadView(XmrWalletView.class); + } + }; + root.setTabClosingPolicy(TabPane.TabClosingPolicy.ALL_TABS); + moneroWalletTab.setText(Res.get("account.menu.wallets.moneroWalletView").toUpperCase()); + } + + + @Override + protected void activate() { + log.info("activate()"); + if(preferences.isUseBisqXmrWallet()) { + navigation.addListener(navigationListener); + + root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener); + + if (navigation.getCurrentPath().size() == 3 && navigation.getCurrentPath().get(1) == AccountView.class && navigation.getCurrentPath().get(2) == AltCoinWalletsView.class) { + if (root.getSelectionModel().getSelectedItem() == moneroWalletTab && selectedTab != moneroWalletTab) { + loadView(XmrWalletView.class); + } + } + } + } + + @Override + protected void deactivate() { + if(preferences.isUseBisqXmrWallet()) { + navigation.removeListener(navigationListener); + root.getSelectionModel().selectedItemProperty().removeListener(tabChangeListener); + } + } + + private void loadView(Class viewClass) { + log.info("loadView({})", viewClass); + + + if(preferences.isUseBisqXmrWallet()) { + if (selectedTab != null && selectedTab.getContent() != null) { + if (selectedTab.getContent() instanceof ScrollPane) { + ((ScrollPane) selectedTab.getContent()).setContent(null); + } else { + selectedTab.setContent(null); + } + } + + View view = viewLoader.load(viewClass); + if (view instanceof XmrWalletView) { + selectedTab = moneroWalletTab; + } + + selectedTab.setContent(view.getRoot()); + root.getSelectionModel().select(selectedTab); + } + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrBalanceUtil.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrBalanceUtil.java new file mode 100644 index 00000000000..68d46f72f67 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrBalanceUtil.java @@ -0,0 +1,106 @@ +package bisq.desktop.main.account.content.wallet.monero; + +import static bisq.desktop.util.FormBuilder.addTitledGroupBg; + +import java.math.BigInteger; +import java.util.HashMap; + +import javax.inject.Inject; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import bisq.common.UserThread; +import bisq.core.locale.Res; +import bisq.core.xmr.XmrFormatter; +import bisq.core.xmr.wallet.XmrWalletRpcWrapper; +import bisq.core.xmr.wallet.listeners.WalletBalanceListener; +import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.util.FormBuilder; +import bisq.desktop.util.Layout; +import javafx.scene.control.TextField; +import javafx.scene.layout.GridPane; + +public class XmrBalanceUtil implements WalletBalanceListener { + // Displaying general XMR info + private TextField actualBalanceTextField, unlockedBalanceTextField; + private XmrWalletRpcWrapper walletWrapper; + private XmrFormatter xmrFormatter; + protected final Logger log = LoggerFactory.getLogger(this.getClass()); + + @Inject + private XmrBalanceUtil(XmrWalletRpcWrapper walletWrapper, XmrFormatter xmrFormatter) { + this.walletWrapper = walletWrapper; + this.xmrFormatter = xmrFormatter; + } + + public int addGroup(GridPane gridPane, int gridRow) { + addTitledGroupBg(gridPane, gridRow, 4, Res.get("shared.account.wallet.dashboard.myBalance")); + actualBalanceTextField = FormBuilder.addTopLabelReadOnlyTextField(gridPane, gridRow, + Res.get("shared.account.wallet.dashboard.actualBalance"), Layout.FIRST_ROW_DISTANCE).second; + unlockedBalanceTextField = FormBuilder.addTopLabelReadOnlyTextField(gridPane, ++gridRow, + Res.get("shared.account.wallet.dashboard.unlockedBalance")).second; + + return gridRow; + } + + public void postSendUpdate(HashMap walletRpcData) { + log.info("postSendUpdate => {}", walletRpcData); + actualBalanceTextField.setText(xmrFormatter.formatBigInteger(walletWrapper.getWalletRpc().getBalance())); + unlockedBalanceTextField.setText(xmrFormatter.formatBigInteger(walletWrapper.getWalletRpc().getUnlockedBalance())); + } + + @Override + public void onUpdateBalances(HashMap walletRpcData) { + log.info("onUpdateBalances => {}", walletRpcData); + if(walletRpcData.get("getBalance") != null) { + BigInteger balance = (BigInteger) walletRpcData.get("getBalance"); + actualBalanceTextField.setText(xmrFormatter.formatBigInteger(balance)); + } + if(walletRpcData.get("getUnlockedBalance") != null) { + BigInteger unlockedBalance = (BigInteger) walletRpcData.get("getUnlockedBalance"); + unlockedBalanceTextField.setText(xmrFormatter.formatBigInteger(unlockedBalance)); + } + } + + public void activate() { + HashMap data = new HashMap<>(); + data.put("getBalance", null); + data.put("getUnlockedBalance", null); + try { + walletWrapper.update(this, data); + triggerUpdate(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + new Popup<>().error(Res.get("mainView.networkWarning.localhostLost", "Monero")).show(); + } catch (Exception e) { + e.printStackTrace(); + new Popup<>().error(Res.get("shared.account.wallet.popup.error.startupFailed")).show(); + } + } + + public void deactivate() { + } + + private void triggerUpdate() { + } + + @Override + public void playAnimation() { + //Do nothing for now + + } + + @Override + public void stopAnimation() { + //Do nothing for now + } + + @Override + public void popupErrorWindow(String resourceMessage) { + UserThread.execute(() -> { + new Popup<>().error(resourceMessage).show(); + }); + } + +} diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrWalletView.fxml b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrWalletView.fxml new file mode 100644 index 00000000000..5b916e814c2 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrWalletView.fxml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrWalletView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrWalletView.java new file mode 100644 index 00000000000..c759fdb61be --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrWalletView.java @@ -0,0 +1,160 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.main.account.content.wallet.monero; + +import javax.inject.Inject; + +import bisq.core.locale.Res; +import bisq.core.user.Preferences; +import bisq.desktop.Navigation; +import bisq.desktop.common.model.Activatable; +import bisq.desktop.common.view.ActivatableViewAndModel; +import bisq.desktop.common.view.CachingViewLoader; +import bisq.desktop.common.view.FxmlView; +import bisq.desktop.common.view.View; +import bisq.desktop.common.view.ViewLoader; +import bisq.desktop.main.MainView; +import bisq.desktop.main.account.AccountView; +import bisq.desktop.main.account.content.wallet.AltCoinWalletsView; +import bisq.desktop.main.account.content.wallet.monero.receive.XmrReceiveView; +import bisq.desktop.main.account.content.wallet.monero.send.XmrSendView; +import bisq.desktop.main.account.content.wallet.monero.tx.XmrTxView; +import javafx.beans.value.ChangeListener; +import javafx.fxml.FXML; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; + +@FxmlView +public class XmrWalletView extends ActivatableViewAndModel { + + @FXML + private Tab xmrSendTab, xmrReceiveTab, xmrTxTab; + + private Navigation.Listener navigationListener; + private ChangeListener tabChangeListener; + + private final ViewLoader viewLoader; + private final Navigation navigation; + private Tab selectedTab; + private Preferences preferences; + + @Inject + private XmrWalletView(CachingViewLoader viewLoader, Navigation navigation, Preferences preferences) { + this.viewLoader = viewLoader; + this.navigation = navigation; + this.preferences = preferences; + } + + @Override + public void initialize() { + log.info("XmrWalletView.initialize({})", selectedTab); + root.setTabClosingPolicy(TabPane.TabClosingPolicy.ALL_TABS); + xmrSendTab.setText(Res.get("shared.account.wallet.menuItem.send").toUpperCase()); + xmrReceiveTab.setText(Res.get("shared.account.wallet.menuItem.receive").toUpperCase()); + xmrTxTab.setText(Res.get("shared.account.wallet.menuItem.transactions").toUpperCase()); + + if(selectedTab == null) { + selectedTab = xmrSendTab; + } + selectView(); + navigationListener = viewPath -> { + log.info("XmrWalletView.viewPath={}, size={}", viewPath, viewPath.size()); + if (viewPath.size() == 4 && navigation.getCurrentPath().get(3) == XmrWalletView.class && + navigation.getCurrentPath().get(2) == AltCoinWalletsView.class && navigation.getCurrentPath().get(1) == AccountView.class) { + selectView(); + } + }; + + tabChangeListener = (ov, oldValue, newValue) -> { + selectedTab = newValue; + if (newValue == xmrSendTab) { + loadView(XmrSendView.class); + } else if (newValue == xmrReceiveTab) { + loadView(XmrReceiveView.class); + } else if (newValue == xmrTxTab) { + loadView(XmrTxView.class); + } else { + loadView(XmrSendView.class); + } + }; + } + + private void selectView() { + if (selectedTab == xmrSendTab) { + loadView(XmrSendView.class); + } else if (selectedTab == xmrReceiveTab) { + loadView(XmrReceiveView.class); + } else if (selectedTab == xmrTxTab) { + loadView(XmrTxView.class); + } else { + loadView(XmrSendView.class); + } + } + + @Override + protected void activate() { + log.info("XmrWalletView.activate({})", selectedTab); + navigation.addListener(navigationListener); + root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener); + + if (navigation.getCurrentPath().size() == 5 && navigation.getCurrentPath().get(3) == XmrWalletView.class && + navigation.getCurrentPath().get(2) == AltCoinWalletsView.class && navigation.getCurrentPath().get(1) == AccountView.class) { + Tab selectedItem = root.getSelectionModel().getSelectedItem(); + if (selectedItem == xmrSendTab) + navigation.navigateTo(MainView.class, AccountView.class, AltCoinWalletsView.class, XmrWalletView.class, XmrSendView.class); + else if (selectedItem == xmrReceiveTab) + navigation.navigateTo(MainView.class, AccountView.class, AltCoinWalletsView.class, XmrWalletView.class, XmrReceiveView.class); + else if (selectedItem == xmrTxTab) + navigation.navigateTo(MainView.class, AccountView.class, AltCoinWalletsView.class, XmrWalletView.class, XmrTxView.class); + loadView(navigation.getCurrentPath().get(4)); + } + //TODO(niyid) Use preferences to determine which wallet to load in XmrWalletRpcViewHelper + } + + @Override + protected void deactivate() { + log.info("XmrWalletView.deactivate()"); + navigation.removeListener(navigationListener); + root.getSelectionModel().selectedItemProperty().removeListener(tabChangeListener); + } + + private void loadView(Class viewClass) { + log.info("XmrWalletView.loadView: " + viewClass); + if (selectedTab != null && selectedTab.getContent() != null) { + if (selectedTab.getContent() instanceof ScrollPane) { + ((ScrollPane) selectedTab.getContent()).setContent(null); + } else { + selectedTab.setContent(null); + } + } + + View view = viewLoader.load(viewClass); + if (view instanceof XmrSendView) { + selectedTab = xmrSendTab; + } else if (view instanceof XmrReceiveView) { + selectedTab = xmrReceiveTab; + } else if (view instanceof XmrTxView) { + selectedTab = xmrTxTab; + } + + selectedTab.setContent(view.getRoot()); + root.getSelectionModel().select(selectedTab); + } +} + diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/receive/XmrReceiveView.fxml b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/receive/XmrReceiveView.fxml new file mode 100644 index 00000000000..d4c94d720cd --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/receive/XmrReceiveView.fxml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/receive/XmrReceiveView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/receive/XmrReceiveView.java new file mode 100644 index 00000000000..6944189d205 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/receive/XmrReceiveView.java @@ -0,0 +1,139 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.main.account.content.wallet.monero.receive; + +import static bisq.desktop.util.FormBuilder.addLabelWalletAddressTextField; +import static bisq.desktop.util.FormBuilder.addTitledGroupBg; + +import java.util.HashMap; + +import javax.inject.Inject; + +import bisq.common.UserThread; +import bisq.common.util.Tuple3; +import bisq.core.locale.Res; +import bisq.core.xmr.wallet.XmrWalletRpcWrapper; +import bisq.core.xmr.wallet.listeners.WalletBalanceListener; +import bisq.desktop.common.view.ActivatableView; +import bisq.desktop.common.view.FxmlView; +import bisq.desktop.components.BusyAnimation; +import bisq.desktop.components.TitledGroupBg; +import bisq.desktop.components.WalletAddressTextField; +import bisq.desktop.main.account.content.wallet.monero.XmrBalanceUtil; +import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.util.Layout; +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.VBox; + +@FxmlView +public class XmrReceiveView extends ActivatableView implements WalletBalanceListener { + + private WalletAddressTextField addressTextField; + private final XmrWalletRpcWrapper walletWrapper; + private int gridRow = 0; + private final XmrBalanceUtil xmrBalanceUtil; + private BusyAnimation busyAnimation = new BusyAnimation(false); + private VBox walletTuple3VBox; + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + private XmrReceiveView(XmrWalletRpcWrapper walletWrapper, XmrBalanceUtil xmrBalanceUtil) { + this.walletWrapper = walletWrapper; + this.xmrBalanceUtil = xmrBalanceUtil; + } + + @Override + public void initialize() { + if(!walletWrapper.isXmrWalletRpcRunning()) { + walletWrapper.openWalletRpcInstance(this); + } + + root.setPadding(new Insets(10)); + gridRow = xmrBalanceUtil.addGroup(root, gridRow); + + TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 1, + Res.get("shared.account.wallet.receive.fundYourWallet"), Layout.GROUP_DISTANCE); + titledGroupBg.getStyleClass().add("last"); + GridPane.setColumnSpan(titledGroupBg, 3); + Tuple3 tuple = addLabelWalletAddressTextField(root, gridRow, + Res.get("shared.account.wallet.receive.walletAddress"), + Layout.FIRST_ROW_AND_GROUP_DISTANCE); + addressTextField = tuple.second; + walletTuple3VBox = tuple.third; + GridPane.setColumnSpan(walletTuple3VBox, 3); + walletTuple3VBox.getChildren().add(busyAnimation); + HashMap data = new HashMap<>(); + data.put("getBalance", null); + data.put("getUnlockedBalance", null); + data.put("getPrimaryAddress", null); + + try { + walletWrapper.update(this, data); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + new Popup<>().error(Res.get("mainView.networkWarning.localhostLost", "Monero")).show(); + } catch (Exception e) { + e.printStackTrace(); + new Popup<>().error(Res.get("shared.account.wallet.popup.error.startupFailed")).show(); + } + } + + @Override + public void onUpdateBalances(HashMap walletRpcData) { + log.info("onUpdateBalances => {}", walletRpcData); + xmrBalanceUtil.onUpdateBalances(walletRpcData); + String address = (String) walletRpcData.get("getPrimaryAddress"); + addressTextField.setAddress(address); + } + + @Override + public void activate() { + } + + @Override + public void deactivate() { + } + + @Override + public void playAnimation() { + UserThread.execute(() -> { + busyAnimation.setVisible(true); + busyAnimation.play(); + }); + } + + @Override + public void stopAnimation() { + UserThread.execute(() -> { + busyAnimation.setVisible(false); + busyAnimation.stop(); + }); + } + + @Override + public void popupErrorWindow(String resourceMessage) { + UserThread.execute(() -> { + new Popup<>().error(resourceMessage).show(); + }); + } +} + diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.fxml b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.fxml new file mode 100644 index 00000000000..c287b5708f1 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.fxml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java new file mode 100644 index 00000000000..8bc8007a1ef --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java @@ -0,0 +1,294 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.main.account.content.wallet.monero.send; + +import static bisq.desktop.util.FormBuilder.addButtonBusyAnimationLabelAfterGroup; +import static bisq.desktop.util.FormBuilder.addInputTextField; +import static bisq.desktop.util.FormBuilder.addTitledGroupBg; +import static bisq.desktop.util.FormBuilder.addTopLabelComboBox; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.HashMap; + +import javax.inject.Inject; + +import bisq.asset.CryptoNoteAddressValidator; +import bisq.common.UserThread; +import bisq.common.handlers.ResultHandler; +import bisq.common.util.Tuple2; +import bisq.common.util.Tuple4; +import bisq.core.locale.Res; +import bisq.core.xmr.XmrFormatter; +import bisq.core.xmr.wallet.XmrWalletRpcWrapper; +import bisq.core.xmr.wallet.listeners.WalletBalanceListener; +import bisq.desktop.Navigation; +import bisq.desktop.common.view.ActivatableView; +import bisq.desktop.common.view.FxmlView; +import bisq.desktop.components.BusyAnimation; +import bisq.desktop.components.InputTextField; +import bisq.desktop.components.TitledGroupBg; +import bisq.desktop.main.MainView; +import bisq.desktop.main.account.AccountView; +import bisq.desktop.main.account.content.wallet.AltCoinWalletsView; +import bisq.desktop.main.account.content.wallet.monero.XmrBalanceUtil; +import bisq.desktop.main.account.content.wallet.monero.XmrWalletView; +import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.util.Layout; +import bisq.desktop.util.validation.XmrValidator; +import javafx.beans.value.ChangeListener; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import monero.wallet.model.MoneroSendPriority; +import monero.wallet.model.MoneroTxWallet; + +@FxmlView +public class XmrSendView extends ActivatableView implements WalletBalanceListener { + private final XmrWalletRpcWrapper walletWrapper; + private final XmrFormatter xmrFormatter; + private final Navigation navigation; + private final XmrBalanceUtil xmrBalanceUtil; + private final XmrValidator xmrValidator;//TODO(niyid) Replace with XMR equivalent + private final CryptoNoteAddressValidator addressValidator;//TODO(niyid) Replace with XMR equivalent + + private int gridRow = 0; + private InputTextField amountInputTextField; + private InputTextField feeInputTextField; + private Button sendXmrButton; + private InputTextField receiversAddressInputTextField; + private ChangeListener focusOutListener; + private ChangeListener inputTextFieldListener; + private ComboBox priorityComboBox; + private BusyAnimation busyAnimation; + + @Inject + private XmrSendView(XmrWalletRpcWrapper walletWrapper, + XmrFormatter xmrFormatter, Navigation navigation, + XmrBalanceUtil xmrBalanceUtil, XmrValidator xmrValidator) { + this.walletWrapper = walletWrapper; + this.xmrFormatter = xmrFormatter; + this.navigation = navigation; + this.xmrBalanceUtil = xmrBalanceUtil; + this.xmrValidator = xmrValidator; + this.addressValidator = new CryptoNoteAddressValidator(true, 24, 36, 53, 63);//TODO(niyid) Only allow testnet/stagenet addresses +// this.addressValidator = new CryptoNoteAddressValidator(true, 18, 42);//TODO(niyid) Only allow mainnet addresses + } + + @Override + public void initialize() { + if(!walletWrapper.isXmrWalletRpcRunning()) { + walletWrapper.openWalletRpcInstance(this); + } + + root.setPadding(new Insets(10)); + gridRow = xmrBalanceUtil.addGroup(root, gridRow); + + addSendXmrGroup(); + + focusOutListener = (observable, oldValue, newValue) -> { + if (!newValue) { + onUpdateBalances(); + } + }; + inputTextFieldListener = (observable, oldValue, newValue) -> onUpdateBalances(); + } + + @Override + protected void activate() { + xmrBalanceUtil.activate(); + + receiversAddressInputTextField.focusedProperty().addListener(focusOutListener); + amountInputTextField.focusedProperty().addListener(focusOutListener); + + receiversAddressInputTextField.textProperty().addListener(inputTextFieldListener); + amountInputTextField.textProperty().addListener(inputTextFieldListener); + } + + @Override + protected void deactivate() { + xmrBalanceUtil.deactivate(); + + receiversAddressInputTextField.focusedProperty().removeListener(focusOutListener); + amountInputTextField.focusedProperty().removeListener(focusOutListener); + + receiversAddressInputTextField.textProperty().removeListener(inputTextFieldListener); + amountInputTextField.textProperty().removeListener(inputTextFieldListener); + } + + @Override + public void onUpdateBalances(HashMap walletRpcData) { + log.info("onUpdateBalances => {}", walletRpcData); + BigInteger fee = walletRpcData.get("getFee") != null ? (BigInteger) walletRpcData.get("getFee") : BigInteger.ZERO; + BigInteger unlockedBalance = walletRpcData.get("getUnlockedBalance") != null ? (BigInteger) walletRpcData.get("getUnlockedBalance") : BigInteger.ZERO; + Integer size = walletRpcData.get("getSize") != null ? (Integer) walletRpcData.get("getSize") : 0; + Double sizeKbs = size != null ? size.doubleValue() / 1024.0 : 0.0; + feeInputTextField.setText(xmrFormatter.formatBigInteger(fee)); + BigInteger amountToSend = walletRpcData.get("getOutgoingAmount") != null + ? (BigInteger) walletRpcData.get("getOutgoingAmount") + : BigInteger.ZERO; + if(unlockedBalance.subtract(amountToSend).subtract(fee).compareTo(BigInteger.ZERO) < 0) { + handleError(new Exception("Balance too low.")); + } else { + showPublishTxPopup(amountToSend, receiversAddressInputTextField.getText(), fee, size, sizeKbs, amountToSend, + xmrFormatter, () -> { + MoneroTxWallet txToRelay = (MoneroTxWallet) walletRpcData.get("txToRelay"); + HashMap dataToRelay = new HashMap<>(); + dataToRelay.put("txToRelay", txToRelay); + walletWrapper.relayTx(XmrSendView.this, dataToRelay); + receiversAddressInputTextField.setText(""); + amountInputTextField.setText(""); + }); + } + onUpdateBalances(); + xmrBalanceUtil.onUpdateBalances(walletRpcData); + } + + public void onUpdateBalances() { + //TODO(niyid) Replace with XmrAddressValidator + boolean isValid = addressValidator.validate(receiversAddressInputTextField.getText()).isValid() && + xmrValidator.validate(amountInputTextField.getText()).isValid; + sendXmrButton.setDisable(!isValid); + + } + + private void addSendXmrGroup() { + TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 2, Res.get("shared.account.wallet.send.sendFunds"), Layout.GROUP_DISTANCE); + GridPane.setColumnSpan(titledGroupBg, 3); + + receiversAddressInputTextField = addInputTextField(root, gridRow, + Res.get("shared.account.wallet.send.receiverAddress"), Layout.FIRST_ROW_AND_GROUP_DISTANCE); + GridPane.setColumnSpan(receiversAddressInputTextField, 3); + + amountInputTextField = addInputTextField(root, ++gridRow, Res.get("shared.account.wallet.send.setAmount", xmrFormatter.formatBigInteger(XmrFormatter.MINIMUM_SENDABLE_AMOUNT))); + amountInputTextField.setValidator(xmrValidator); + GridPane.setColumnSpan(amountInputTextField, 3); + + feeInputTextField = addInputTextField(root, ++gridRow, Res.get("shared.account.wallet.send.feeAmount")); + GridPane.setColumnSpan(feeInputTextField, 3); + feeInputTextField.setEditable(false); + + //TODO(niyid) Add priority field list + Tuple2> topLabelComboBox = addTopLabelComboBox(root, ++gridRow, Res.get("shared.account.wallet.send.priorityLabel"), Res.get("shared.account.wallet.send.priority"), (int) Layout.FIRST_ROW_AND_GROUP_DISTANCE); + ObservableList priorityOptions = FXCollections.observableArrayList(); + priorityOptions.addAll(MoneroSendPriority.values()); + priorityComboBox = topLabelComboBox.second; + priorityComboBox.setItems(priorityOptions); + GridPane.setColumnSpan(priorityComboBox, 3); + + focusOutListener = (observable, oldValue, newValue) -> { + if (!newValue) { + onUpdateBalances(); + } + }; + + Tuple4 actionButtonTuple4 = addButtonBusyAnimationLabelAfterGroup(root, ++gridRow, Res.get("shared.account.wallet.send.send")); + + sendXmrButton = actionButtonTuple4.first; + busyAnimation = actionButtonTuple4.second; + + sendXmrButton.setOnAction((event) -> { + Integer accountIndex = 0; + String address = receiversAddressInputTextField.getText(); + BigDecimal amt = new BigDecimal(amountInputTextField.getText()); + BigInteger amount = amt.movePointRight(12).toBigInteger();//Convert input number to scale compatible with XmrWalletRpc + MoneroSendPriority priority = priorityComboBox.getSelectionModel().getSelectedItem(); + HashMap data = new HashMap<>(); + data.put("getBalance", null); + data.put("getUnlockedBalance", null); + data.put("getFee", null); + data.put("getMixin", null); + data.put("getOutgoingAmount", null); + data.put("getNumConfirmations", null); + data.put("getDoNotRelay", null); + data.put("getId", null); + data.put("getTimestamp", null); + data.put("getPaymentId", null); + data.put("getReceivedTimestamp", null); + data.put("getUnlockTime", null); + data.put("getVersion", null); + data.put("getOutgoingTransfer", null); + data.put("getExtra", null); + + try { + walletWrapper.createTx(XmrSendView.this, accountIndex, address, amount, priority != null ? priority : MoneroSendPriority.NORMAL, true, data); + xmrBalanceUtil.postSendUpdate(data); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + new Popup<>().error(Res.get("mainView.networkWarning.localhostLost", "Monero")).show(); + } catch (Exception e) { + e.printStackTrace(); + new Popup<>().error(Res.get("shared.account.wallet.popup.error.startupFailed")).show(); + } + }); + } + + private void handleError(Throwable t) { + new Popup<>().error(Res.get("shared.account.wallet.popup.error.transactionFailed", t.getLocalizedMessage())) + .actionButtonTextWithGoTo("account.menu.wallets.monero.navigation.funds.depositFunds") + .onAction(() -> navigation.navigateTo(MainView.class, AccountView.class, + AltCoinWalletsView.class, XmrWalletView.class)).show(); + log.error(t.toString()); + t.printStackTrace(); + } + + private void showPublishTxPopup(BigInteger outgoingAmount, String address, + BigInteger fee, Integer size, Double sizeKbs, + BigInteger amountReceived, XmrFormatter amountFormatter, + ResultHandler resultHandler) { + new Popup<>().headLine(Res.get("shared.account.wallet.send.sendFunds.headline")) + .confirmation(Res.get("shared.account.wallet.send.sendFunds.details", + xmrFormatter.formatBigInteger(outgoingAmount), + address, xmrFormatter.formatBigInteger(fee), size != 0 ? (fee.doubleValue() / size) : 0, sizeKbs, xmrFormatter.formatBigInteger(amountReceived))) + .actionButtonText(Res.get("shared.yes")) + .onAction(() -> { + resultHandler.handleResult(); + }) + .closeButtonText(Res.get("shared.cancel")) + .show(); + } + + @Override + public void playAnimation() { + UserThread.execute(() -> { + busyAnimation.setVisible(true); + busyAnimation.play(); + }); + } + + @Override + public void stopAnimation() { + UserThread.execute(() -> { + busyAnimation.setVisible(false); + busyAnimation.stop(); + }); + } + + @Override + public void popupErrorWindow(String resourceMessage) { + UserThread.execute(() -> { + new Popup<>().error(resourceMessage).show(); + }); + } +} + diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxProofWindow.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxProofWindow.java new file mode 100644 index 00000000000..974abd524f8 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxProofWindow.java @@ -0,0 +1,129 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.main.account.content.wallet.monero.tx; + +import static bisq.desktop.util.FormBuilder.addLabelWalletAddressTextField; + +import bisq.common.UserThread; +import bisq.common.util.Tuple3; +import bisq.core.locale.Res; +import bisq.core.xmr.wallet.TxProofHandler; +import bisq.desktop.components.BusyAnimation; +import bisq.desktop.components.WalletAddressTextField; +import bisq.desktop.main.overlays.Overlay; +import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.util.Layout; +import javafx.scene.control.Label; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.VBox; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class XmrTxProofWindow extends Overlay implements TxProofHandler { + private WalletAddressTextField messageTextField; + private WalletAddressTextField txIdTextField; + private WalletAddressTextField signatureTextField; + private BusyAnimation busyAnimation = new BusyAnimation(false); + private int gridRow = 0; + + /////////////////////////////////////////////////////////////////////////////////////////// + // Interface + /////////////////////////////////////////////////////////////////////////////////////////// + + XmrTxProofWindow() { + type = Type.Attention; + width = 900; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Public API + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void show() { + if (gridPane != null) { + rowIndex = -1; + gridPane.getChildren().clear(); + } + + if (headLine == null) + headLine = Res.get("shared.account.wallet.tx.proof"); + + createGridPane(); + addHeadLine(); + addInputFields(); + addButtons(); + applyStyles(); + display(); + } + + private void addInputFields() { + Tuple3 tupleAddress = addLabelWalletAddressTextField(gridPane, ++gridRow, + Res.get("shared.account.wallet.tx.item.message"), + Layout.FIRST_ROW_AND_GROUP_DISTANCE); + messageTextField = tupleAddress.second; + VBox addressVBox = tupleAddress.third; + GridPane.setColumnSpan(addressVBox, 3); + + Tuple3 tupleTxId = addLabelWalletAddressTextField(gridPane, ++gridRow, + Res.get("shared.account.wallet.tx.item.txId"), + Layout.GROUP_DISTANCE); + txIdTextField = tupleTxId.second; + VBox txIdVBox = tupleTxId.third; + GridPane.setColumnSpan(txIdVBox, 3); + + Tuple3 tupleSignature = addLabelWalletAddressTextField(gridPane, ++gridRow, + Res.get("shared.account.wallet.tx.item.signature"), + Layout.GROUP_DISTANCE); + signatureTextField = tupleSignature.second; + VBox signatureVBox = tupleSignature.third; + GridPane.setColumnSpan(signatureVBox, 3); + } + + + @Override + public void update(String txId, String message, String signature) { + txIdTextField.setAddress(txId); + messageTextField.setAddress(message); + signatureTextField.setAddress(signature); + } + + @Override + public void playAnimation() { + UserThread.execute(() -> { + busyAnimation.setVisible(true); + busyAnimation.play(); + }); + } + + @Override + public void stopAnimation() { + UserThread.execute(() -> { + busyAnimation.setVisible(false); + busyAnimation.stop(); + }); + } + + @Override + public void popupErrorWindow(String resourceMessage) { + UserThread.execute(() -> { + new Popup<>().error(resourceMessage).show(); + }); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.fxml b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.fxml new file mode 100644 index 00000000000..0879726a1b0 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.fxml @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java new file mode 100644 index 00000000000..d61eec1a694 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java @@ -0,0 +1,482 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.main.account.content.wallet.monero.tx; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +import javax.inject.Inject; + +import bisq.common.UserThread; +import bisq.core.locale.Res; +import bisq.core.xmr.XmrFormatter; +import bisq.core.xmr.wallet.XmrTxListItem; +import bisq.core.xmr.wallet.XmrWalletRpcWrapper; +import bisq.core.xmr.wallet.listeners.WalletBalanceListener; +import bisq.desktop.common.view.ActivatableView; +import bisq.desktop.common.view.FxmlView; +import bisq.desktop.components.AutoTooltipTableColumn; +import bisq.desktop.components.BusyAnimation; +import bisq.desktop.components.HyperlinkWithIcon; +import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.util.GUIUtil; +import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.SortedList; +import javafx.event.EventHandler; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.geometry.VPos; +import javafx.scene.control.Button; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.TextField; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.util.Callback; +import monero.wallet.MoneroWalletRpc; +import monero.wallet.model.MoneroTxWallet; + +@FxmlView +public class XmrTxView extends ActivatableView implements WalletBalanceListener { + + private TableView tableView; + private final XmrWalletRpcWrapper walletWrapper; + private final XmrFormatter xmrFormatter; + private final ObservableList observableList = FXCollections.observableArrayList(); + private final SortedList sortedList = new SortedList<>(observableList); + private Button searchButton; + private TextField searchTextField; + private BusyAnimation busyAnimation = new BusyAnimation(false); + private XmrTxProofWindow txProofWindow; + + @Inject + private XmrTxView(XmrWalletRpcWrapper walletWrapper, XmrFormatter xmrFormatter) { + this.walletWrapper = walletWrapper; + this.xmrFormatter = xmrFormatter; + } + + @Override + public void initialize() { + if(!walletWrapper.isXmrWalletRpcRunning()) { + walletWrapper.openWalletRpcInstance(this); + } + + root.setPadding(new Insets(10)); + searchButton = new Button(Res.get("shared.search")); + searchButton.setOnAction((event) -> { + try { + if(searchTextField.getText() != null && !searchTextField.getText().isEmpty()) { + walletWrapper.searchTx(XmrTxView.this, searchTextField.getText()); + } + } catch (IllegalArgumentException e) { + e.printStackTrace(); + new Popup<>().error(Res.get("mainView.networkWarning.localhostLost", "Monero")).show(); + } catch (Exception e) { + e.printStackTrace(); + new Popup<>().error(Res.get("shared.account.wallet.popup.error.startupFailed")).show(); + } + }); + + VBox vbox = new VBox(5); + + HBox hbox = new HBox(5); + searchTextField = new TextField(); + searchTextField.setPromptText(Res.get("shared.account.wallet.tx.searchPrompt")); + searchTextField.setMinWidth(950); + hbox.getChildren().addAll(searchTextField, searchButton, busyAnimation); + GridPane.setColumnSpan(vbox, 3); + + root.add(vbox, 1, 1); + GridPane.setConstraints(hbox, 1, 1, 1, 2, HPos.CENTER, VPos.CENTER); + tableView = new TableView<>(); + tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + tableView.setEditable(false); + + addDateColumn(); + addTxIdColumn(); + addPaymentIdColumn(); + addAmountColumn(); + addDestinationColumn(); + addDirectionColumn(); + addConfirmedColumn(); + + vbox.getChildren().addAll(hbox, tableView); + } + + @Override + protected void activate() { + try { + HashMap data = new HashMap<>(); + data.put("getTxs", null); + data.put("getBalance", null); + data.put("getFee", null); + walletWrapper.update(this, data); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + new Popup<>().error(Res.get("mainView.networkWarning.localhostLost", "Monero")).show(); + } catch (Exception e) { + e.printStackTrace(); + new Popup<>().error(Res.get("shared.account.wallet.tx.item.account.wallet.popup.error.startupFailed")).show(); + } + + sortedList.comparatorProperty().bind(tableView.comparatorProperty()); + tableView.setItems(observableList); + } + + @Override + protected void deactivate() { + sortedList.comparatorProperty().unbind(); + } + + @SuppressWarnings("unchecked") + @Override + public void onUpdateBalances(HashMap walletRpcData) { + log.info("onUpdateBalances => {}", walletRpcData.keySet()); + List txList = (List) walletRpcData.get("getTxs"); + if(txList != null) { + observableList.setAll(txList); + } + } + + private void openTxInBlockExplorer(XmrTxListItem item) { + if (item.getTxId() != null) + GUIUtil.openWebPage("https://testnet.xmrchain.com/search?value=" + item.getTxId(), false);//TODO(niyid) Change from hardcoded URL + } + + private void addDateColumn() { + TableColumn column = new AutoTooltipTableColumn<>(Res.get("shared.account.wallet.tx.item.datetime")); + column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setMinWidth(180); + column.setMaxWidth(column.getMinWidth() + 20); + column.getStyleClass().add("first-column"); + + column.setCellFactory( + new Callback<>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell<>() { + + @Override + public void updateItem(final XmrTxListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + setText(xmrFormatter.formatDateTime(item.getDate())); + } else { + setText(""); + } + } + }; + } + }); + tableView.getColumns().add(column); + column.setComparator(Comparator.comparing(XmrTxListItem::getDate)); + column.setSortType(TableColumn.SortType.DESCENDING); + tableView.getSortOrder().add(column); + } + + private void addTxIdColumn() { + TableColumn column = new AutoTooltipTableColumn<>(Res.get("shared.account.wallet.tx.item.txId")); + + column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setMinWidth(200); + column.setCellFactory( + new Callback<>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell<>() { + private HyperlinkWithIcon hyperlinkWithIcon; + + @Override + public void updateItem(final XmrTxListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + String transactionId = item.getTxId(); + hyperlinkWithIcon = new HyperlinkWithIcon(transactionId, MaterialDesignIcon.LINK); + hyperlinkWithIcon.setOnAction(event -> openTxInBlockExplorer(item)); + hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForTx", transactionId))); + setGraphic(hyperlinkWithIcon); + } else { + setGraphic(null); + if (hyperlinkWithIcon != null) + hyperlinkWithIcon.setOnAction(null); + } + } + }; + } + }); + tableView.getColumns().add(column); + } + + private void addPaymentIdColumn() { + TableColumn column = new AutoTooltipTableColumn<>(Res.get("shared.account.wallet.tx.item.paymentId")); + + column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setMinWidth(200); + column.setCellFactory( + new Callback<>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final XmrTxListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + String paymentId = item.getPaymentId(); + setText(paymentId); + } else { + setText(""); + } + } + }; + } + }); + tableView.getColumns().add(column); + } + + private void addDestinationColumn() { + TableColumn column = new AutoTooltipTableColumn<>(Res.get("shared.account.wallet.tx.item.destination")); + + column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setMinWidth(150); + column.setCellFactory( + new Callback<>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final XmrTxListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + String address = item.getDestinationAddress(); + setText(address); + } else { + setText(""); + } + } + }; + } + }); + tableView.getColumns().add(column); + } + + private void addDirectionColumn() { + TableColumn column = new AutoTooltipTableColumn<>(Res.get("shared.account.wallet.tx.item.direction")); + + column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setMinWidth(60); + column.setCellFactory( + new Callback<>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final XmrTxListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + String direction = item.getDirection(); + setText(direction); + } else { + setText(""); + } + } + }; + } + }); + tableView.getColumns().add(column); + } + + private void addMixinColumn() { + TableColumn column = new AutoTooltipTableColumn<>(Res.get("shared.account.wallet.tx.item.mixin")); + + column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setMinWidth(60); + column.setCellFactory( + new Callback<>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final XmrTxListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty && item.getMixin() != null) { + String mixin = Integer.toString(item.getMixin()); + setText(mixin); + } else { + setText(""); + } + } + }; + } + }); + tableView.getColumns().add(column); + } + + private void addConfirmedColumn() { + TableColumn column = new AutoTooltipTableColumn<>(Res.get("shared.account.wallet.tx.item.confirmed")); + + column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setMinWidth(60); + column.setCellFactory( + new Callback<>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell<>() { + private HyperlinkWithIcon hyperlinkWithIcon; + + @Override + public void updateItem(final XmrTxListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + String confirmed = item.isConfirmed() ? Res.get("shared.yes") : Res.get("shared.no"); + hyperlinkWithIcon = new HyperlinkWithIcon(confirmed, MaterialDesignIcon.TICKET_CONFIRMATION); + hyperlinkWithIcon.setOnAction(e -> showTxProof(item.getTxId(), "Transaction at " + xmrFormatter.formatDateTime(item.getDate()))); + hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openTxProof", item.getTxId()))); + setGraphic(hyperlinkWithIcon); + } else { + setGraphic(null); + if (hyperlinkWithIcon != null) + hyperlinkWithIcon.setOnAction(null); + } + } + }; + } + }); + tableView.getColumns().add(column); + } + + private void addConfirmationsColumn() { + TableColumn column = new AutoTooltipTableColumn<>(Res.get("shared.account.wallet.tx.item.confirmations")); + + column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setMinWidth(60); + column.setCellFactory( + new Callback<>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final XmrTxListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + String paymentId = Integer.toString(item.getConfirmations()); + setText(paymentId); + } else { + setText(""); + } + } + }; + } + }); + tableView.getColumns().add(column); + } + + private void addAmountColumn() { + TableColumn column = new AutoTooltipTableColumn<>(Res.get("shared.account.wallet.tx.item.amount", "XMR")); + column.setMinWidth(120); + column.setMaxWidth(column.getMinWidth()); + + column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setCellFactory(new Callback<>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell<>() { + + @Override + public void updateItem(final XmrTxListItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) { + String xmrAmount = Res.get("shared.account.wallet.tx.item.na"); + + if (item.getConfirmations() > 0) { + xmrAmount = xmrFormatter.formatBigInteger(item.getAmount()); + } + + setText(xmrAmount); + } else { + setText(""); + } + } + }; + } + }); + tableView.getColumns().add(column); + } + + @Override + public void playAnimation() { + UserThread.execute(() -> { + busyAnimation.setVisible(true); + busyAnimation.play(); + }); + } + + @Override + public void stopAnimation() { + UserThread.execute(() -> { + busyAnimation.setVisible(false); + busyAnimation.stop(); + }); + } + + @Override + public void popupErrorWindow(String resourceMessage) { + UserThread.execute(() -> { + new Popup<>().error(resourceMessage).show(); + }); + } + + private void showTxProof(String txId, String message) { + this.txProofWindow = new XmrTxProofWindow(); + this.txProofWindow.show(); + walletWrapper.handleTxProof(XmrTxView.this.txProofWindow, txId, message); + } +} + diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 1dc02d8bea4..26a44b71ac3 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -103,16 +103,17 @@ public class PreferencesView extends ActivatableViewAndModel userLanguageComboBox; private ComboBox userCountryComboBox; private ComboBox preferredTradeCurrencyComboBox; + private ComboBox xmrHostComboBox; //private ComboBox selectBaseCurrencyNetworkComboBox; private ToggleButton showOwnOffersInOfferBook, useAnimations, sortMarketCurrenciesNumerically, avoidStandbyMode, - useCustomFee; + useCustomFee, useBisqXmrWallet; private int gridRow = 0; private InputTextField transactionFeeInputTextField, ignoreTradersListInputTextField, ignoreDustThresholdInputTextField, /*referralIdInputTextField,*/ - rpcUserTextField, blockNotifyPortTextField; + rpcUserTextField, blockNotifyPortTextField, xmrHostPortTextField, xmrRpcUserTextField; private ToggleButton isDaoFullNodeToggleButton; - private PasswordTextField rpcPwTextField; + private PasswordTextField rpcPwTextField, xmrRpcPwdTextField; private TitledGroupBg daoOptionsTitledGroupBg; private ChangeListener transactionFeeFocusedListener; @@ -140,7 +141,8 @@ public class PreferencesView extends ActivatableViewAndModel tradeCurrencies; private InputTextField deviationInputTextField; private ChangeListener deviationListener, ignoreTradersListListener, ignoreDustThresholdListener, - /*referralIdListener,*/ rpcUserListener, rpcPwListener, blockNotifyPortListener; + /*referralIdListener,*/ rpcUserListener, rpcPwListener, blockNotifyPortListener, + xmrHostPortListener, xmrRpcUserListener, xmrRpcPwdListener; private ChangeListener deviationFocusedListener; private ChangeListener useCustomFeeCheckboxListener; private ChangeListener transactionFeeChangeListener; @@ -258,7 +260,7 @@ public BaseCurrencyNetwork fromString(String string) { Res.get("setting.preferences.explorer")); blockChainExplorerComboBox.setButtonCell(GUIUtil.getComboBoxButtonCell(Res.get("setting.preferences.explorer"), blockChainExplorerComboBox, false)); - + Tuple3 tuple = addTopLabelInputTextFieldSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.txFee"), Res.get("setting.preferences.useCustomValue")); transactionFeeInputTextField = tuple.second; @@ -370,6 +372,39 @@ public BaseCurrencyNetwork fromString(String string) { // AvoidStandbyModeService avoidStandbyMode = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.avoidStandbyMode")); + + useBisqXmrWallet = addSlideToggleButton(root, ++gridRow, + Res.get("setting.preferences.useBisqXmrWallet")); + //TODO(niyid) Only show those below if useBisqXmrWallet toggled on + xmrHostComboBox = addComboBox(root, ++gridRow, Res.get("shared.account.wallet.host", "Monero")); + xmrHostComboBox.setButtonCell(GUIUtil.getComboBoxButtonCell(Res.get("shared.account.wallet.host", "Monero"), xmrHostComboBox, + false)); + xmrHostPortTextField = addInputTextField(root, ++gridRow, Res.get("shared.account.wallet.port", "Monero")); + xmrRpcUserTextField = addInputTextField(root, ++gridRow, Res.get("shared.account.wallet.user", "Monero")); + xmrRpcPwdTextField = addPasswordTextField(root, ++gridRow, Res.get("shared.account.wallet.pass", "Monero")); + + GridPane.setColumnSpan(xmrHostPortTextField, 1); + GridPane.setMargin(xmrHostPortTextField, new Insets(20, 0, 0, 0)); + + GridPane.setColumnSpan(xmrRpcUserTextField, 1); + GridPane.setMargin(xmrRpcUserTextField, new Insets(20, 0, 0, 0)); + + GridPane.setColumnSpan(xmrRpcPwdTextField, 1); + GridPane.setMargin(xmrRpcPwdTextField, new Insets(20, 0, 0, 0)); + + xmrHostPortListener = (observable, oldValue, newValue) -> { + preferences.setXmrHostPortDelegate(xmrHostPortTextField.getText()); + try { + int port = Integer.parseInt(xmrHostPortTextField.getText()); + preferences.setXmrHostPortDelegate(String.valueOf(port)); + } catch (Throwable exception) { + new Popup<>().error(Res.get("validation.notAnInteger")).show(); + } + }; + + xmrRpcUserListener = (observable, oldValue, newValue) -> preferences.setXmrRpcUserDelegate(xmrRpcUserTextField.getText()); + xmrRpcPwdListener = (observable, oldValue, newValue) -> preferences.setXmrRpcPwdDelegate(xmrRpcPwdTextField.getText()); + } private void initializeSeparator() { @@ -730,6 +765,29 @@ public Country fromString(String string) { } }); + xmrHostComboBox.setItems(preferences.getXmrHostsAsObservable()); + xmrHostComboBox.getSelectionModel().select(preferences.getXmrUserHostDelegate()); + xmrHostComboBox.setOnAction(e -> { + String host = xmrHostComboBox.getSelectionModel().getSelectedItem(); + if (host != null) { + preferences.setXmrUserHostDelegate(host); + } + }); + + //TODO(niyid) here... + rpcUserListener = (observable, oldValue, newValue) -> preferences.setRpcUser(rpcUserTextField.getText()); + rpcPwListener = (observable, oldValue, newValue) -> preferences.setRpcPw(rpcPwTextField.getText()); + + rpcUserListener = (observable, oldValue, newValue) -> preferences.setRpcUser(rpcUserTextField.getText()); + rpcPwListener = (observable, oldValue, newValue) -> preferences.setRpcPw(rpcPwTextField.getText()); + + rpcUserListener = (observable, oldValue, newValue) -> preferences.setRpcUser(rpcUserTextField.getText()); + rpcPwListener = (observable, oldValue, newValue) -> preferences.setRpcPw(rpcPwTextField.getText()); + + xmrHostPortTextField.setText(preferences.getXmrHostPortDelegate()); + xmrRpcUserTextField.setText(preferences.getXmrRpcUserDelegate()); + xmrRpcPwdTextField.setText(preferences.getXmrRpcPwdDelegate()); + blockChainExplorerComboBox.setItems(blockExplorers); blockChainExplorerComboBox.getSelectionModel().select(preferences.getBlockChainExplorer()); blockChainExplorerComboBox.setConverter(new StringConverter<>() { @@ -825,7 +883,20 @@ private void activateDisplayPreferences() { // so users who update gets set avoidStandbyMode=true (useStandbyMode=false) avoidStandbyMode.setSelected(!preferences.isUseStandbyMode()); avoidStandbyMode.setOnAction(e -> preferences.setUseStandbyMode(!avoidStandbyMode.isSelected())); - } + + useBisqXmrWallet.setSelected(preferences.isUseBisqXmrWallet()); + useBisqXmrWallet.setOnAction(e -> { + preferences.setUseBisqXmrWallet(useBisqXmrWallet.isSelected()); + xmrHostComboBox.setVisible(useBisqXmrWallet.isSelected()); + xmrHostPortTextField.setVisible(useBisqXmrWallet.isSelected()); + xmrRpcUserTextField.setVisible(useBisqXmrWallet.isSelected()); + xmrRpcPwdTextField.setVisible(useBisqXmrWallet.isSelected()); + }); + + xmrHostPortTextField.textProperty().addListener(xmrHostPortListener); + xmrRpcUserTextField.textProperty().addListener(xmrRpcUserListener); + xmrRpcPwdTextField.textProperty().addListener(xmrRpcPwdListener); +} private void activateDaoPreferences() { boolean daoFullNode = preferences.isDaoFullNode(); @@ -834,8 +905,8 @@ private void activateDaoPreferences() { String rpcPw = preferences.getRpcPw(); int blockNotifyPort = preferences.getBlockNotifyPort(); if (daoFullNode && (rpcUser == null || rpcUser.isEmpty() || - rpcPw == null || rpcPw.isEmpty() || - blockNotifyPort <= 0)) { + rpcPw == null || rpcPw.isEmpty()) || + blockNotifyPort <= 0) { log.warn("You have full DAO node selected but have not provided the rpc username, password and " + "block notify port. We reset daoFullNode to false"); isDaoFullNodeToggleButton.setSelected(false); diff --git a/desktop/src/main/java/bisq/desktop/util/FormBuilder.java b/desktop/src/main/java/bisq/desktop/util/FormBuilder.java index 09ce2e8eb56..34076e3ad8b 100644 --- a/desktop/src/main/java/bisq/desktop/util/FormBuilder.java +++ b/desktop/src/main/java/bisq/desktop/util/FormBuilder.java @@ -39,7 +39,7 @@ import bisq.desktop.components.TextFieldWithIcon; import bisq.desktop.components.TitledGroupBg; import bisq.desktop.components.TxIdTextField; - +import bisq.desktop.components.WalletAddressTextField; import bisq.core.locale.Res; import bisq.common.util.Tuple2; @@ -1300,6 +1300,15 @@ public static Tuple3 addLabelBsqAddressTextFie return new Tuple3<>(topLabelWithVBox.first, addressTextField, topLabelWithVBox.second); } + public static Tuple3 addLabelWalletAddressTextField(GridPane gridPane, int rowIndex, String title, double top) { + WalletAddressTextField addressTextField = new WalletAddressTextField(); + addressTextField.setFocusTraversable(false); + + Tuple2 topLabelWithVBox = addTopLabelWithVBox(gridPane, rowIndex, title, addressTextField, top - 15); + + return new Tuple3<>(topLabelWithVBox.first, addressTextField, topLabelWithVBox.second); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Label + BalanceTextField diff --git a/desktop/src/main/java/bisq/desktop/util/validation/XmrValidator.java b/desktop/src/main/java/bisq/desktop/util/validation/XmrValidator.java new file mode 100644 index 00000000000..e21965354e6 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/util/validation/XmrValidator.java @@ -0,0 +1,157 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.util.validation; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import org.jetbrains.annotations.NotNull; + +import static bisq.core.xmr.XmrFormatter.formatAsScaled; + +import bisq.core.locale.Res; +import bisq.core.xmr.XmrFormatter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class XmrValidator extends AltcoinValidator { + private final XmrFormatter xmrFormatter; + + @Nullable + private BigInteger maxValue; + @Nullable + private BigInteger availableBalance; + private BigInteger minValue = XmrFormatter.MINIMUM_SENDABLE_AMOUNT; // dust + private BigDecimal minValueScaled = formatAsScaled(minValue); + private BigDecimal maxValueScaled; + private BigDecimal availableBalanceScaled; + + @Override + protected double getMinValue() { + return minValue.doubleValue(); + } + + @Inject + public XmrValidator(XmrFormatter xmrFormatter) { + this.xmrFormatter = xmrFormatter; + // Limit to avoid overflows + setMaxValue(new BigInteger("1840000000000000000")); + maxValueScaled = formatAsScaled(maxValue); + } + + public void setMinValue(@NotNull BigInteger minValue) { + this.minValue = XmrFormatter.MINIMUM_SENDABLE_AMOUNT; + } + + public void setMaxValue(@NotNull BigInteger maxValue) { + this.maxValue = maxValue; + } + + public void setAvailableBalance(@NotNull BigInteger availableBalance) { + this.availableBalance = availableBalance; + this.availableBalanceScaled = formatAsScaled(availableBalance); + } + + @Override + public ValidationResult validate(String input) { + ValidationResult result = validateIfNotEmpty(input); + if (result.isValid) { + input = cleanInput(input); + result = validateIfNumber(input); + } + + if (result.isValid) { + result = validateIfNotZero(input) + .and(validateIfNotNegative(input)) + .and(validateIfNotExceedsMaxValue(input)); + } + + if (result.isValid) { + result = validateIfNotFractionalXmrValue(input) + .and(validateIfNotExceedsMaxXmrValue(input)) + .and(validateIfSufficientAvailableBalance(input)) + .and(validateIfAboveDust(input)) + .and(validateIfNotBelowMinValue(input)); + } + + return result; + } + + private ValidationResult validateIfAboveDust(String input) { + final BigDecimal coin = new BigDecimal(input); + if (coin.compareTo(minValueScaled) > 0) { + return new ValidationResult(true); + } else { + return new ValidationResult(false, Res.get("validation.amountBelowDust", + xmrFormatter.formatBigInteger(minValue))); + } + } + + private ValidationResult validateIfNotFractionalXmrValue(String input) { + BigDecimal bd = new BigDecimal(input); + final BigDecimal satoshis = bd.movePointRight(2); + if (satoshis.scale() > 0) { + return new ValidationResult(false, Res.get("shared.account.wallet.validation.fraction")); + } else { + return new ValidationResult(true); + } + } + + private ValidationResult validateIfNotExceedsMaxXmrValue(String input) { + try { + final BigDecimal coin = new BigDecimal(input); + if (maxValueScaled != null && coin.compareTo(maxValueScaled) > 0) { + return new ValidationResult(false, Res.get("shared.account.wallet.validation.toLarge", xmrFormatter.formatBigInteger(maxValue))); + } else { + return new ValidationResult(true); + } + } catch (Throwable t) { + return new ValidationResult(false, Res.get("shared.account.wallet.validation.invalidInput", t.getMessage())); + } + } + + private ValidationResult validateIfSufficientAvailableBalance(String input) { + try { + final BigDecimal coin = new BigDecimal(input); + if (availableBalanceScaled != null && availableBalanceScaled.compareTo(coin) < 0) + return new ValidationResult(false, Res.get("shared.account.wallet.validation.insufficientBalance", + xmrFormatter.formatBigInteger(availableBalance))); + else + return new ValidationResult(true); + } catch (Throwable t) { + return new ValidationResult(false, Res.get("validation.invalidInput", t.getMessage())); + } + } + + private ValidationResult validateIfNotBelowMinValue(String input) { + try { + final BigDecimal coin = new BigDecimal(input); + if (minValueScaled != null && coin.compareTo(minValueScaled) < 0) { + return new ValidationResult(false, Res.get("shared.account.wallet.validation.amountBelowMinAmount", + xmrFormatter.formatBigInteger(minValue))); + } else { + return new ValidationResult(true); + } + } catch (Throwable t) { + return new ValidationResult(false, Res.get("validation.invalidInput", t.getMessage())); + } + } +} diff --git a/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java b/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java index 482ab242333..e7630148868 100644 --- a/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java +++ b/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java @@ -40,7 +40,7 @@ import bisq.core.user.User; import bisq.core.util.BSFormatter; import bisq.core.util.BsqFormatter; - +import bisq.core.xmr.wallet.XmrWalletRpcWrapper; import bisq.network.p2p.network.BridgeAddressProvider; import bisq.network.p2p.seed.SeedNodeRepository; @@ -59,6 +59,7 @@ import com.google.inject.Injector; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import static junit.framework.TestCase.assertNotSame; @@ -133,6 +134,7 @@ public void testGuiceSetup() { assertSingleton(PriceAlert.class); assertSingleton(MarketAlerts.class); assertSingleton(ChargeBackRisk.class); + assertSingleton(XmrWalletRpcWrapper.class); assertNotSingleton(Storage.class); } From c8791218cdf9a96ced20dbe49fc6c0d2dd1e890b Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Mon, 16 Sep 2019 21:11:08 +0100 Subject: [PATCH 02/22] Merging messages from proposal #110 messages with HEAD. --- .../resources/i18n/displayStrings.properties | 102 +++++++++++++++--- 1 file changed, 85 insertions(+), 17 deletions(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 94b1e1fe162..41467f07aaa 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -62,6 +62,10 @@ shared.priceWithCur=Price in {0} shared.priceInCurForCur=Price in {0} for 1 {1} shared.fixedPriceInCurForCur=Fixed price in {0} for 1 {1} shared.amount=Amount +shared.txFee=Transaction Fee +shared.makerFee=Maker Fee +shared.buyerSecurityDeposit=Buyer Deposit +shared.sellerSecurityDeposit=Seller Deposit shared.amountWithCur=Amount in {0} shared.volumeWithCur=Volume in {0} shared.currency=Currency @@ -324,7 +328,7 @@ mainView.footer.btcInfo.synchronizingWith=Synchronizing with mainView.footer.btcInfo.synchronizedWith=Synchronized with mainView.footer.btcInfo.connectingTo=Connecting to mainView.footer.btcInfo.connectionFailed=connection failed -mainView.footer.p2pInfo=P2P network peers: {0} +mainView.footer.p2pInfo=Bisq network peers: {0} mainView.footer.daoFullNode=DAO full node mainView.bootstrapState.connectionToTorNetwork=(1/4) Connecting to Tor network... @@ -334,10 +338,10 @@ mainView.bootstrapState.initialDataReceived=(4/4) Initial data received mainView.bootstrapWarning.noSeedNodesAvailable=No seed nodes available mainView.bootstrapWarning.noNodesAvailable=No seed nodes and peers available -mainView.bootstrapWarning.bootstrappingToP2PFailed=Bootstrapping to P2P network failed +mainView.bootstrapWarning.bootstrappingToP2PFailed=Bootstrapping to Bisq network failed mainView.p2pNetworkWarnMsg.noNodesAvailable=There are no seed nodes or persisted peers available for requesting data.\nPlease check your internet connection or try to restart the application. -mainView.p2pNetworkWarnMsg.connectionToP2PFailed=Connecting to the P2P network failed (reported error: {0}).\nPlease check your internet connection or try to restart the application. +mainView.p2pNetworkWarnMsg.connectionToP2PFailed=Connecting to the Bisq network failed (reported error: {0}).\nPlease check your internet connection or try to restart the application. mainView.walletServiceErrorMsg.timeout=Connecting to the Bitcoin network failed because of a timeout. mainView.walletServiceErrorMsg.connectionError=Connection to the Bitcoin network failed because of an error: {0} @@ -580,7 +584,7 @@ takeOffer.failed.offerTaken=You cannot take that offer because the offer was alr takeOffer.failed.offerRemoved=You cannot take that offer because the offer has been removed in the meantime. takeOffer.failed.offererNotOnline=Take offer request failed because maker is not online anymore. takeOffer.failed.offererOffline=You cannot take that offer because the maker is offline. -takeOffer.warning.connectionToPeerLost=You lost connection to the maker.\nHe might have gone offline or has closed the connection to you because of too many open connections.\n\nIf you can still see his offer in the offerbook you can try to take the offer again. +takeOffer.warning.connectionToPeerLost=You lost connection to the maker.\nThey might have gone offline or has closed the connection to you because of too many open connections.\n\nIf you can still see their offer in the offerbook you can try to take the offer again. takeOffer.error.noFundsLost=\n\nNo funds have left your wallet yet.\nPlease try to restart your application and check your network connection to see if you can resolve the issue. takeOffer.error.feePaid=\n\nPlease try to restart your application and check your network connection to see if you can resolve the issue. @@ -683,7 +687,21 @@ portfolio.pending.step2_seller.waitPayment.headline=Wait for payment portfolio.pending.step2_seller.f2fInfo.headline=Buyer's contact information portfolio.pending.step2_seller.waitPayment.msg=The deposit transaction has at least one blockchain confirmation.\nYou need to wait until the BTC buyer starts the {0} payment. portfolio.pending.step2_seller.warn=The BTC buyer still has not done the {0} payment.\nYou need to wait until they have started the payment.\nIf the trade has not been completed on {1} the arbitrator will investigate. -portfolio.pending.step2_seller.openForDispute=The BTC buyer has not started his payment!\nThe max. allowed period for the trade has elapsed.\nYou can wait longer and give the trading peer more time or contact the arbitrator for opening a dispute. +portfolio.pending.step2_seller.openForDispute=The BTC buyer has not started their payment!\nThe max. allowed period for the trade has elapsed.\nYou can wait longer and give the trading peer more time or contact the arbitrator for opening a dispute. + +tradeChat.chatWindowTitle=Chat window for trade with ID ''{0}'' +tradeChat.openChat=Open chat window +tradeChat.rules=You can communicate with your trade peer to resolve potential problems with this trade.\n\ + It is not mandatory to reply in the chat.\n\ + If a trader violates the below rules, open a dispute with 'Cmd/Ctrl + o' and report it to the arbitrator.\n\n\ + Chat rules:\n\ + \t● Do not send any links (risk of malware). You can send the transaction ID and the name of a block explorer.\n\ + \t● Do not send your seed words, private keys, passwords or other sensitive information!\n\ + \t● Do not encourage trading outside of Bisq (no security).\n\ + \t● Do not engage in any form of social engineering scam attempts.\n\ + \t● If a peer is not responding and prefers to not communicate via chat, respect their decision.\n\ + \t● Keep conversation scope limited to the trade. This chat is not a messenger replacement or trollbox.\n\ + \t● Keep conversation friendly and respectful. # suppress inspection "UnusedProperty" message.state.UNDEFINED=Undefined @@ -805,7 +823,7 @@ portfolio.pending.disputeOpened=Dispute opened portfolio.pending.openSupport=Open support ticket portfolio.pending.supportTicketOpened=Support ticket opened portfolio.pending.requestSupport=Request support -portfolio.pending.error.requestSupport=Please report the problem to your arbitrator.\n\nHe will forward the information to the developers to investigate the problem.\nAfter the problem has been analyzed you will get back all locked funds. +portfolio.pending.error.requestSupport=Please report the problem to your arbitrator.\n\nThey will forward the information to the developers to investigate the problem.\nAfter the problem has been analyzed you will get back all locked funds. portfolio.pending.communicateWithArbitrator=Please communicate in the \"Support\" screen with the arbitrator. portfolio.pending.supportTicketOpenedMyUser=You opened already a support ticket.\n{0} portfolio.pending.disputeOpenedMyUser=You opened already a dispute.\n{0} @@ -911,7 +929,7 @@ support.filter=Filter list support.filter.prompt=Enter trade ID, date, onion address or account data support.noTickets=There are no open tickets support.sendingMessage=Sending Message... -support.receiverNotOnline=Receiver is not online. Message is saved to his mailbox. +support.receiverNotOnline=Receiver is not online. Message is saved to their mailbox. support.sendMessageError=Sending message failed. Error: {0} support.wrongVersion=The offer in that dispute has been created with an older version of Bisq.\n\ You cannot close that dispute with your version of the application.\n\n\ @@ -923,7 +941,7 @@ support.attachment=Attachment support.tooManyAttachments=You cannot send more than 3 attachments in one message. support.save=Save file to disk support.messages=Messages -support.input.prompt=Please enter here your message to the arbitrator +support.input.prompt=Enter message... support.send=Send support.addAttachments=Add attachments support.closeTicket=Close ticket @@ -1014,6 +1032,7 @@ setting.preferences.addAltcoin=Add altcoin setting.preferences.displayOptions=Display options setting.preferences.showOwnOffers=Show my own offers in offer book setting.preferences.useAnimations=Use animations +setting.preferences.useDarkMode=Use dark mode (beta) setting.preferences.sortWithNumOffers=Sort market lists with no. of offers/trades setting.preferences.resetAllFlags=Reset all \"Don't show again\" flags setting.preferences.reset=Reset @@ -1023,7 +1042,7 @@ settings.preferences.selectCurrencyNetwork=Select network setting.preferences.daoOptions=DAO options setting.preferences.dao.resync.label=Rebuild DAO state from genesis tx setting.preferences.dao.resync.button=Resync -setting.preferences.dao.resync.popup=After an application restart the P2P network governance data will be reloaded from \ +setting.preferences.dao.resync.popup=After an application restart the Bisq network governance data will be reloaded from \ the seed nodes and the BSQ consensus state will be rebuilt from the genesis transaction. setting.preferences.dao.isDaoFullNode=Run Bisq as DAO full node setting.preferences.dao.rpcUser=RPC username @@ -1036,7 +1055,7 @@ setting.preferences.dao.fullNodeInfo.ok=Open docs page setting.preferences.dao.fullNodeInfo.cancel=No, I stick with lite node mode settings.net.btcHeader=Bitcoin network -settings.net.p2pHeader=P2P network +settings.net.p2pHeader=Bisq network settings.net.onionAddressLabel=My onion address settings.net.btcNodesLabel=Use custom Bitcoin Core nodes settings.net.bitcoinPeersLabel=Connected peers @@ -1133,9 +1152,9 @@ account.arbitratorRegistration.register=Register arbitrator account.arbitratorRegistration.revoke=Revoke registration account.arbitratorRegistration.info.msg=Please note that you need to stay available for 15 days after revoking as there might be trades which are using you as arbitrator. The max. allowed trade period is 8 days and the dispute process might take up to 7 days. account.arbitratorRegistration.warn.min1Language=You need to set at least 1 language.\nWe added the default language for you. -account.arbitratorRegistration.removedSuccess=You have successfully removed your arbitrator from the P2P network. +account.arbitratorRegistration.removedSuccess=You have successfully removed your arbitrator from the Bisq network. account.arbitratorRegistration.removedFailed=Could not remove arbitrator.{0} -account.arbitratorRegistration.registerSuccess=You have successfully registered your arbitrator to the P2P network. +account.arbitratorRegistration.registerSuccess=You have successfully registered your arbitrator to the Bisq network. account.arbitratorRegistration.registerFailed=Could not register arbitrator.{0} account.arbitratorSelection.minOneArbitratorRequired=You need to set at least 1 language.\nWe added the default language for you. @@ -1144,7 +1163,7 @@ account.arbitratorSelection.whichDoYouAccept=Which arbitrators do you accept account.arbitratorSelection.autoSelect=Auto select all arbitrators with matching language account.arbitratorSelection.regDate=Registration date account.arbitratorSelection.languages=Languages -account.arbitratorSelection.cannotSelectHimself=An arbitrator cannot select himself for trading. +account.arbitratorSelection.cannotSelectHimself=An arbitrator cannot select themselves for trading. account.arbitratorSelection.noMatchingLang=No matching language. account.arbitratorSelection.noLang=You can only select arbitrators who are speaking at least 1 common language. account.arbitratorSelection.minOne=You need to have at least one arbitrator selected. @@ -1194,6 +1213,29 @@ arbitrator in case of a dispute.\n\n\ There is no payment ID required, just the normal public address.\n\ If you are not sure about that process visit (https://www.getmonero.org/resources/user-guides/prove-payment.html) \ or the Monero forum (https://forum.getmonero.org) to find more information. +account.altcoin.popup.msr.msg=Trading MSR on Bisq requires that you understand and fulfill \ +the following requirements:\n\n\ +For sending MSR, you need to use either the official Masari GUI wallet, Masari CLI wallet with the \ +store-tx-info flag enabled (enabled by default) or the Masari web wallet (https://wallet.getmasari.org). Please be sure you can access the tx key as \ +that would be required in case of a dispute.\n\ +masari-wallet-cli (use the command get_tx_key)\n\ +masari-wallet-gui (go to history tab and click on the (P) button for payment proof)\n\n\ +Masari Web Wallet (goto Account -> transaction history and view details on your sent transaction)\n\n +Verification can be accomplished in-wallet.\n\ +masari-wallet-cli : using command (check_tx_key).\n\ +masari-wallet-gui : on the Advanced > Prove/Check page.\n\ +Verification can be accomplished in the block explorer \n\ +Open block explorer (https://explorer.getmasari.org), use the search bar to find your transaction hash.\n\ +Once transaction is found, scroll to bottom to the 'Prove Sending' area and fill in details as needed.\n\ +You need to provide the arbitrator the following data in case of a dispute:\n\ +- The tx private key\n\ +- The transaction hash\n\ +- The recipient's public address\n\n\ +Failure to provide the above data, or if you used an incompatible wallet, will result in losing the \ +dispute case. The MSR sender is responsible for providing verification of the MSR transfer to the \ +arbitrator in case of a dispute.\n\n\ +There is no payment ID required, just the normal public address.\n\ +If you are not sure about that process, ask for help on the Official Masari Discord (https://discord.gg/sMCwMqs). account.altcoin.popup.blur.msg=Trading BLUR on Bisq requires that you understand and fulfill \ the following requirements:\n\n\ To send BLUR you must use the Blur Network CLI or GUI Wallet. \n\n\ @@ -1209,6 +1251,18 @@ transfer using the Blur Transaction Viewer (https://blur.cash/#tx-viewer).\n\n\ Failure to provide the required information to the arbitrator will result in losing the dispute case. In all cases of dispute, the \ BLUR sender bears 100% of the burden of responsibility in verifying transactions to an arbitrator. \n\n\ If you do not understand these requirements, do not trade on Bisq. First, seek help at the Blur Network Discord (https://discord.gg/dMWaqVW). +account.altcoin.popup.solo.msg=Trading Solo on Bisq requires that you understand and fulfill \ +the following requirements:\n\n\ +To send Solo you must use the Solo Network CLI Wallet. \n\n\ +If you are using the CLI wallet, a transaction hash (tx ID) will be displayed after a transfer is sent. You must save \ +this information. Immediately after sending the transfer, you must use the command 'get_tx_key' to retrieve the \ +transaction private key. If you fail to perform this step, you may not be able to retrieve the key later. \n\n\ +In the event that arbitration is necessary, you must present the following to an arbitrator: 1.) the transaction ID, \ +2.) the transaction private key, and 3.) the recipient's address. The arbitrator will then verify the Solo \ +transfer using the Solo Block Explorer by searching for the transaction and then using the "Prove sending" function (https://explorer.minesolo.com/).\n\n\ +failure to provide the required information to the arbitrator will result in losing the dispute case. In all cases of dispute, the \ +Solo sender bears 100% of the burden of responsibility in verifying transactions to an arbitrator. \n\n\ +If you do not understand these requirements, do not trade on Bisq. First, seek help at the Solo Network Discord (https://discord.minesolo.com/). account.altcoin.popup.cash2.msg=Trading CASH2 on Bisq requires that you understand and fulfill \ the following requirements:\n\n\ To send CASH2 you must use the Cash2 Wallet version 3 or higher. \n\n\ @@ -1568,7 +1622,7 @@ dao.results.cycle.value.postFix.isDefaultValue=(default value) dao.results.cycle.value.postFix.hasChanged=(has been changed in voting) dao.results.invalidVotes=We had invalid votes in that voting cycle. That can happen if a vote was \ - not distributed well in the P2P network.\n{0} + not distributed well in the Bisq network.\n{0} # suppress inspection "UnusedProperty" dao.phase.PHASE_UNDEFINED=Undefined @@ -2057,6 +2111,8 @@ dao.monitor.daoState.utxoConflicts=UTXO conflicts dao.monitor.daoState.utxoConflicts.blockHeight=Block height: {0} dao.monitor.daoState.utxoConflicts.sumUtxo=Sum of all UTXO: {0} BSQ dao.monitor.daoState.utxoConflicts.sumBsq=Sum of all BSQ: {0} BSQ +dao.monitor.daoState.checkpoint.popup=DAO state is not in sync with the network. \ + After restart the DAO state will resync. dao.monitor.proposal.headline=Proposals state dao.monitor.proposal.table.headline=Chain of proposal state hashes @@ -2530,6 +2586,7 @@ list.currency.editList=Edit currency list table.placeholder.noItems=Currently there are no {0} available table.placeholder.noData=Currently there is no data available +table.placeholder.processingData=Processing data... peerInfoIcon.tooltip.tradePeer=Trading peer's @@ -2647,8 +2704,6 @@ password.forgotPassword=Forgot password? password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\n\ It is highly recommended to make a backup of the application directory and write down your seed words before setting a password! password.backupWasDone=I have already done a backup -confirm.tooLong=Password confirmation must be less than 500 characters. -confirm.noMatch=Password confirmation did not match. seed.seedWords=Wallet seed words seed.enterSeedWords=Enter wallet seed words @@ -2836,9 +2891,13 @@ payment.f2f.info='Face to Face' trades have different rules and come with differ To be sure you fully understand the differences with 'Face to Face' trades please read the instructions and \ recommendations at: 'https://docs.bisq.network/trading-rules.html#f2f-trading' payment.f2f.info.openURL=Open web page -payment.f2f.offerbook.tooltip.countryAndCity=County and city: {0} / {1} +payment.f2f.offerbook.tooltip.countryAndCity=Country and city: {0} / {1} payment.f2f.offerbook.tooltip.extra=Additional information: {0} +payment.japan.bank=Bank +payment.japan.branch=Branch +payment.japan.account=Account +payment.japan.recipient=Name # We use constants from the code so we do not use our normal naming convention # dynamic values are not recognized by IntelliJ @@ -2852,6 +2911,7 @@ CASH_DEPOSIT=Cash Deposit MONEY_GRAM=MoneyGram WESTERN_UNION=Western Union F2F=Face to face (in person) +JAPAN_BANK=Japan Zengin Furikomi # suppress inspection "UnusedProperty" NATIONAL_BANK_SHORT=National banks @@ -2869,6 +2929,8 @@ MONEY_GRAM_SHORT=MoneyGram WESTERN_UNION_SHORT=Western Union # suppress inspection "UnusedProperty" F2F_SHORT=F2F +# suppress inspection "UnusedProperty" +JAPAN_BANK_SHORT=Japan Furikomi # Do not translate brand names # suppress inspection "UnusedProperty" @@ -3034,3 +3096,9 @@ validation.mustBeDifferent=Your input must be different from the current value validation.cannotBeChanged=Parameter cannot be changed validation.numberFormatException=Number format exception {0} validation.mustNotBeNegative=Input must not be negative +validation.phone.missingCountryCode=Need two letter country code to validate phone number +validation.phone.invalidCharacters=Phone number {0} contains invalid characters +validation.phone.insufficientDigits=Not enough digits in {0} for a valid phone number +validation.phone.tooManyDigits=Too many digits in {0} to be a valid phone number +validation.phone.invalidDialingCode=Country dialing code in number {0} is invalid for country {1}. \ + The correct dialing code is {2}. From 3383f71e03cfbc5a5c2179ede50c067197926f5c Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Mon, 16 Sep 2019 21:13:56 +0100 Subject: [PATCH 03/22] Cleaning up. --- desktop/src/test/java/bisq/desktop/GuiceSetupTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java b/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java index e7630148868..1a1c73a85c9 100644 --- a/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java +++ b/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java @@ -59,7 +59,6 @@ import com.google.inject.Injector; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import static junit.framework.TestCase.assertNotSame; From 39f89b5e6bf59c4dde7f9c831ef4c631439b3ba0 Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Mon, 16 Sep 2019 21:15:49 +0100 Subject: [PATCH 04/22] Cleaning up. --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index a1407627ce2..0a68aa585c3 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,3 @@ deploy /monitor/TorHiddenServiceStartupTimeTests/* /monitor/monitor-tor/* .java-version -/.metadata/ From 7a2e54fd20162c34809df36206f67b4b3274a598 Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Mon, 16 Sep 2019 21:24:50 +0100 Subject: [PATCH 05/22] Cleaning up formatting. --- .../bisq/core/user/PreferencesPayload.java | 273 +++++++++--------- 1 file changed, 136 insertions(+), 137 deletions(-) diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index 9c52152ea5c..bd7b8890804 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -148,144 +148,143 @@ public PreferencesPayload() { /////////////////////////////////////////////////////////////////////////////////////////// @Override - public Message toProtoMessage() { - protobuf.PreferencesPayload.Builder builder = protobuf.PreferencesPayload.newBuilder() - .setUserLanguage(userLanguage) - .setUserCountry((protobuf.Country) userCountry.toProtoMessage()) - .addAllFiatCurrencies(fiatCurrencies.stream() - .map(fiatCurrency -> ((protobuf.TradeCurrency) fiatCurrency.toProtoMessage())) - .collect(Collectors.toList())) - .addAllCryptoCurrencies(cryptoCurrencies.stream() - .map(cryptoCurrency -> ((protobuf.TradeCurrency) cryptoCurrency.toProtoMessage())) - .collect(Collectors.toList())) - .setBlockChainExplorerMainNet((protobuf.BlockChainExplorer) blockChainExplorerMainNet.toProtoMessage()) - .setBlockChainExplorerTestNet((protobuf.BlockChainExplorer) blockChainExplorerTestNet.toProtoMessage()) - .setBsqBlockChainExplorer((protobuf.BlockChainExplorer) bsqBlockChainExplorer.toProtoMessage()) - .setAutoSelectArbitrators(autoSelectArbitrators) - .putAllDontShowAgainMap(dontShowAgainMap) - .setTacAccepted(tacAccepted) - .setUseTorForBitcoinJ(useTorForBitcoinJ) - .setShowOwnOffersInOfferBook(showOwnOffersInOfferBook) - .setWithdrawalTxFeeInBytes(withdrawalTxFeeInBytes) - .setUseCustomWithdrawalTxFee(useCustomWithdrawalTxFee) - .setMaxPriceDistanceInPercent(maxPriceDistanceInPercent) - .setTradeStatisticsTickUnitIndex(tradeStatisticsTickUnitIndex) - .setResyncSpvRequested(resyncSpvRequested) - .setSortMarketCurrenciesNumerically(sortMarketCurrenciesNumerically) - .setUsePercentageBasedPrice(usePercentageBasedPrice) - .putAllPeerTagMap(peerTagMap) - .setBitcoinNodes(bitcoinNodes) - .addAllIgnoreTradersList(ignoreTradersList) - .setDirectoryChooserPath(directoryChooserPath) - .setBuyerSecurityDepositAsLong(buyerSecurityDepositAsLong) - .setUseAnimations(useAnimations) - .setPayFeeInBtc(payFeeInBtc) - .setBridgeOptionOrdinal(bridgeOptionOrdinal) - .setTorTransportOrdinal(torTransportOrdinal) - .setBitcoinNodesOptionOrdinal(bitcoinNodesOptionOrdinal) - .setUseSoundForMobileNotifications(useSoundForMobileNotifications) - .setUseTradeNotifications(useTradeNotifications) - .setUseMarketNotifications(useMarketNotifications) - .setUsePriceNotifications(usePriceNotifications) - .setUseStandbyMode(useStandbyMode) - .setIsDaoFullNode(isDaoFullNode) - .setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent) - .setIgnoreDustThreshold(ignoreDustThreshold) - .setBuyerSecurityDepositAsPercentForCrypto(buyerSecurityDepositAsPercentForCrypto) - .setBlockNotifyPort(blockNotifyPort) - .setUseBisqXmrWallet(useBisqXmrWallet) - .setXmrUserHost(xmrUserHost) - .setXmrHostPort(xmrHostPort) - .setXmrRpcUser(xmrRpcUser) - .setXmrRpcPwd(xmrRpcPwd) - .addAllXmrHosts(xmrHosts) - .setXmrHostOptionOrdinal(xmrHostOptionOrdinal); + public Message toProtoMessage() { + protobuf.PreferencesPayload.Builder builder = protobuf.PreferencesPayload.newBuilder() + .setUserLanguage(userLanguage) + .setUserCountry((protobuf.Country) userCountry.toProtoMessage()) + .addAllFiatCurrencies(fiatCurrencies.stream() + .map(fiatCurrency -> ((protobuf.TradeCurrency) fiatCurrency.toProtoMessage())) + .collect(Collectors.toList())) + .addAllCryptoCurrencies(cryptoCurrencies.stream() + .map(cryptoCurrency -> ((protobuf.TradeCurrency) cryptoCurrency.toProtoMessage())) + .collect(Collectors.toList())) + .setBlockChainExplorerMainNet((protobuf.BlockChainExplorer) blockChainExplorerMainNet.toProtoMessage()) + .setBlockChainExplorerTestNet((protobuf.BlockChainExplorer) blockChainExplorerTestNet.toProtoMessage()) + .setBsqBlockChainExplorer((protobuf.BlockChainExplorer) bsqBlockChainExplorer.toProtoMessage()) + .setAutoSelectArbitrators(autoSelectArbitrators) + .putAllDontShowAgainMap(dontShowAgainMap) + .setTacAccepted(tacAccepted) + .setUseTorForBitcoinJ(useTorForBitcoinJ) + .setShowOwnOffersInOfferBook(showOwnOffersInOfferBook) + .setWithdrawalTxFeeInBytes(withdrawalTxFeeInBytes) + .setUseCustomWithdrawalTxFee(useCustomWithdrawalTxFee) + .setMaxPriceDistanceInPercent(maxPriceDistanceInPercent) + .setTradeStatisticsTickUnitIndex(tradeStatisticsTickUnitIndex) + .setResyncSpvRequested(resyncSpvRequested) + .setSortMarketCurrenciesNumerically(sortMarketCurrenciesNumerically) + .setUsePercentageBasedPrice(usePercentageBasedPrice) + .putAllPeerTagMap(peerTagMap) + .setBitcoinNodes(bitcoinNodes) + .addAllIgnoreTradersList(ignoreTradersList) + .setDirectoryChooserPath(directoryChooserPath) + .setBuyerSecurityDepositAsLong(buyerSecurityDepositAsLong) + .setUseAnimations(useAnimations) + .setPayFeeInBtc(payFeeInBtc) + .setBridgeOptionOrdinal(bridgeOptionOrdinal) + .setTorTransportOrdinal(torTransportOrdinal) + .setBitcoinNodesOptionOrdinal(bitcoinNodesOptionOrdinal) + .setUseSoundForMobileNotifications(useSoundForMobileNotifications) + .setUseTradeNotifications(useTradeNotifications) + .setUseMarketNotifications(useMarketNotifications) + .setUsePriceNotifications(usePriceNotifications) + .setUseStandbyMode(useStandbyMode) + .setIsDaoFullNode(isDaoFullNode) + .setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent) + .setIgnoreDustThreshold(ignoreDustThreshold) + .setBuyerSecurityDepositAsPercentForCrypto(buyerSecurityDepositAsPercentForCrypto) + .setBlockNotifyPort(blockNotifyPort) + .setUseBisqXmrWallet(useBisqXmrWallet) + .setXmrUserHost(xmrUserHost) + .setXmrHostPort(xmrHostPort) + .setXmrRpcUser(xmrRpcUser) + .setXmrRpcPwd(xmrRpcPwd) + .addAllXmrHosts(xmrHosts) + .setXmrHostOptionOrdinal(xmrHostOptionOrdinal); + Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory); + Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage())); + Optional.ofNullable(offerBookChartScreenCurrencyCode).ifPresent(builder::setOfferBookChartScreenCurrencyCode); + Optional.ofNullable(tradeChartsScreenCurrencyCode).ifPresent(builder::setTradeChartsScreenCurrencyCode); + Optional.ofNullable(buyScreenCurrencyCode).ifPresent(builder::setBuyScreenCurrencyCode); + Optional.ofNullable(sellScreenCurrencyCode).ifPresent(builder::setSellScreenCurrencyCode); + Optional.ofNullable(selectedPaymentAccountForCreateOffer).ifPresent( + account -> builder.setSelectedPaymentAccountForCreateOffer(selectedPaymentAccountForCreateOffer.toProtoMessage())); + Optional.ofNullable(bridgeAddresses).ifPresent(builder::addAllBridgeAddresses); + Optional.ofNullable(customBridges).ifPresent(builder::setCustomBridges); + Optional.ofNullable(referralId).ifPresent(builder::setReferralId); + Optional.ofNullable(phoneKeyAndToken).ifPresent(builder::setPhoneKeyAndToken); + Optional.ofNullable(rpcUser).ifPresent(builder::setRpcUser); + Optional.ofNullable(rpcPw).ifPresent(builder::setRpcPw); + Optional.ofNullable(takeOfferSelectedPaymentAccountId).ifPresent(builder::setTakeOfferSelectedPaymentAccountId); - Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory); - Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage())); - Optional.ofNullable(offerBookChartScreenCurrencyCode).ifPresent(builder::setOfferBookChartScreenCurrencyCode); - Optional.ofNullable(tradeChartsScreenCurrencyCode).ifPresent(builder::setTradeChartsScreenCurrencyCode); - Optional.ofNullable(buyScreenCurrencyCode).ifPresent(builder::setBuyScreenCurrencyCode); - Optional.ofNullable(sellScreenCurrencyCode).ifPresent(builder::setSellScreenCurrencyCode); - Optional.ofNullable(selectedPaymentAccountForCreateOffer).ifPresent( - account -> builder.setSelectedPaymentAccountForCreateOffer(selectedPaymentAccountForCreateOffer.toProtoMessage())); - Optional.ofNullable(bridgeAddresses).ifPresent(builder::addAllBridgeAddresses); - Optional.ofNullable(customBridges).ifPresent(builder::setCustomBridges); - Optional.ofNullable(referralId).ifPresent(builder::setReferralId); - Optional.ofNullable(phoneKeyAndToken).ifPresent(builder::setPhoneKeyAndToken); - Optional.ofNullable(rpcUser).ifPresent(builder::setRpcUser); - Optional.ofNullable(rpcPw).ifPresent(builder::setRpcPw); - Optional.ofNullable(takeOfferSelectedPaymentAccountId).ifPresent(builder::setTakeOfferSelectedPaymentAccountId); - - return protobuf.PersistableEnvelope.newBuilder().setPreferencesPayload(builder).build(); - } + return protobuf.PersistableEnvelope.newBuilder().setPreferencesPayload(builder).build(); + } public static PersistableEnvelope fromProto(protobuf.PreferencesPayload proto, CoreProtoResolver coreProtoResolver) { - final protobuf.Country userCountry = proto.getUserCountry(); - PaymentAccount paymentAccount = null; - if (proto.hasSelectedPaymentAccountForCreateOffer() && proto.getSelectedPaymentAccountForCreateOffer().hasPaymentMethod()) - paymentAccount = PaymentAccount.fromProto(proto.getSelectedPaymentAccountForCreateOffer(), coreProtoResolver); - - return new PreferencesPayload( - proto.getUserLanguage(), - Country.fromProto(userCountry), - proto.getFiatCurrenciesList().isEmpty() ? new ArrayList<>() : - new ArrayList<>(proto.getFiatCurrenciesList().stream() - .map(FiatCurrency::fromProto) - .collect(Collectors.toList())), - proto.getCryptoCurrenciesList().isEmpty() ? new ArrayList<>() : - new ArrayList<>(proto.getCryptoCurrenciesList().stream() - .map(CryptoCurrency::fromProto) - .collect(Collectors.toList())), - BlockChainExplorer.fromProto(proto.getBlockChainExplorerMainNet()), - BlockChainExplorer.fromProto(proto.getBlockChainExplorerTestNet()), - BlockChainExplorer.fromProto(proto.getBsqBlockChainExplorer()), - ProtoUtil.stringOrNullFromProto(proto.getBackupDirectory()), - proto.getAutoSelectArbitrators(), - Maps.newHashMap(proto.getDontShowAgainMapMap()), - proto.getTacAccepted(), - proto.getUseTorForBitcoinJ(), - proto.getShowOwnOffersInOfferBook(), - proto.hasPreferredTradeCurrency() ? TradeCurrency.fromProto(proto.getPreferredTradeCurrency()) : null, - proto.getWithdrawalTxFeeInBytes(), - proto.getUseCustomWithdrawalTxFee(), - proto.getMaxPriceDistanceInPercent(), - ProtoUtil.stringOrNullFromProto(proto.getOfferBookChartScreenCurrencyCode()), - ProtoUtil.stringOrNullFromProto(proto.getTradeChartsScreenCurrencyCode()), - ProtoUtil.stringOrNullFromProto(proto.getBuyScreenCurrencyCode()), - ProtoUtil.stringOrNullFromProto(proto.getSellScreenCurrencyCode()), - proto.getTradeStatisticsTickUnitIndex(), - proto.getResyncSpvRequested(), - proto.getSortMarketCurrenciesNumerically(), - proto.getUsePercentageBasedPrice(), - Maps.newHashMap(proto.getPeerTagMapMap()), - proto.getBitcoinNodes(), - proto.getIgnoreTradersListList(), - proto.getDirectoryChooserPath(), - proto.getBuyerSecurityDepositAsLong(), - proto.getUseAnimations(), - paymentAccount, - proto.getPayFeeInBtc(), - proto.getBridgeAddressesList().isEmpty() ? null : new ArrayList<>(proto.getBridgeAddressesList()), - proto.getBridgeOptionOrdinal(), - proto.getTorTransportOrdinal(), - ProtoUtil.stringOrNullFromProto(proto.getCustomBridges()), - proto.getBitcoinNodesOptionOrdinal(), - proto.getReferralId().isEmpty() ? null : proto.getReferralId(), - proto.getPhoneKeyAndToken().isEmpty() ? null : proto.getPhoneKeyAndToken(), - proto.getUseSoundForMobileNotifications(), - proto.getUseTradeNotifications(), - proto.getUseMarketNotifications(), - proto.getUsePriceNotifications(), - proto.getUseStandbyMode(), - proto.getIsDaoFullNode(), - proto.getRpcUser().isEmpty() ? null : proto.getRpcUser(), - proto.getRpcPw().isEmpty() ? null : proto.getRpcPw(), - proto.getTakeOfferSelectedPaymentAccountId().isEmpty() ? null : proto.getTakeOfferSelectedPaymentAccountId(), - proto.getBuyerSecurityDepositAsPercent(), - proto.getIgnoreDustThreshold(), - proto.getBuyerSecurityDepositAsPercentForCrypto(), - proto.getBlockNotifyPort(), + final protobuf.Country userCountry = proto.getUserCountry(); + PaymentAccount paymentAccount = null; + if (proto.hasSelectedPaymentAccountForCreateOffer() && proto.getSelectedPaymentAccountForCreateOffer().hasPaymentMethod()) + paymentAccount = PaymentAccount.fromProto(proto.getSelectedPaymentAccountForCreateOffer(), coreProtoResolver); + + return new PreferencesPayload( + proto.getUserLanguage(), + Country.fromProto(userCountry), + proto.getFiatCurrenciesList().isEmpty() ? new ArrayList<>() : + new ArrayList<>(proto.getFiatCurrenciesList().stream() + .map(FiatCurrency::fromProto) + .collect(Collectors.toList())), + proto.getCryptoCurrenciesList().isEmpty() ? new ArrayList<>() : + new ArrayList<>(proto.getCryptoCurrenciesList().stream() + .map(CryptoCurrency::fromProto) + .collect(Collectors.toList())), + BlockChainExplorer.fromProto(proto.getBlockChainExplorerMainNet()), + BlockChainExplorer.fromProto(proto.getBlockChainExplorerTestNet()), + BlockChainExplorer.fromProto(proto.getBsqBlockChainExplorer()), + ProtoUtil.stringOrNullFromProto(proto.getBackupDirectory()), + proto.getAutoSelectArbitrators(), + Maps.newHashMap(proto.getDontShowAgainMapMap()), + proto.getTacAccepted(), + proto.getUseTorForBitcoinJ(), + proto.getShowOwnOffersInOfferBook(), + proto.hasPreferredTradeCurrency() ? TradeCurrency.fromProto(proto.getPreferredTradeCurrency()) : null, + proto.getWithdrawalTxFeeInBytes(), + proto.getUseCustomWithdrawalTxFee(), + proto.getMaxPriceDistanceInPercent(), + ProtoUtil.stringOrNullFromProto(proto.getOfferBookChartScreenCurrencyCode()), + ProtoUtil.stringOrNullFromProto(proto.getTradeChartsScreenCurrencyCode()), + ProtoUtil.stringOrNullFromProto(proto.getBuyScreenCurrencyCode()), + ProtoUtil.stringOrNullFromProto(proto.getSellScreenCurrencyCode()), + proto.getTradeStatisticsTickUnitIndex(), + proto.getResyncSpvRequested(), + proto.getSortMarketCurrenciesNumerically(), + proto.getUsePercentageBasedPrice(), + Maps.newHashMap(proto.getPeerTagMapMap()), + proto.getBitcoinNodes(), + proto.getIgnoreTradersListList(), + proto.getDirectoryChooserPath(), + proto.getBuyerSecurityDepositAsLong(), + proto.getUseAnimations(), + paymentAccount, + proto.getPayFeeInBtc(), + proto.getBridgeAddressesList().isEmpty() ? null : new ArrayList<>(proto.getBridgeAddressesList()), + proto.getBridgeOptionOrdinal(), + proto.getTorTransportOrdinal(), + ProtoUtil.stringOrNullFromProto(proto.getCustomBridges()), + proto.getBitcoinNodesOptionOrdinal(), + proto.getReferralId().isEmpty() ? null : proto.getReferralId(), + proto.getPhoneKeyAndToken().isEmpty() ? null : proto.getPhoneKeyAndToken(), + proto.getUseSoundForMobileNotifications(), + proto.getUseTradeNotifications(), + proto.getUseMarketNotifications(), + proto.getUsePriceNotifications(), + proto.getUseStandbyMode(), + proto.getIsDaoFullNode(), + proto.getRpcUser().isEmpty() ? null : proto.getRpcUser(), + proto.getRpcPw().isEmpty() ? null : proto.getRpcPw(), + proto.getTakeOfferSelectedPaymentAccountId().isEmpty() ? null : proto.getTakeOfferSelectedPaymentAccountId(), + proto.getBuyerSecurityDepositAsPercent(), + proto.getIgnoreDustThreshold(), + proto.getBuyerSecurityDepositAsPercentForCrypto(), + proto.getBlockNotifyPort()), proto.getUseBisqXmrWallet(), proto.getXmrUserHost(), proto.getXmrHostPort(), @@ -293,6 +292,6 @@ public static PersistableEnvelope fromProto(protobuf.PreferencesPayload proto, C proto.getXmrRpcPwd(), proto.getXmrHostsList(), proto.getXmrHostOptionOrdinal()); - - } + + } } From 1ac19a21f1af479222c918b167da57b8f89871b1 Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Mon, 16 Sep 2019 21:33:07 +0100 Subject: [PATCH 06/22] Cleaning up formatting. --- .../src/main/java/bisq/desktop/main/account/AccountView.fxml | 3 +++ desktop/src/test/java/bisq/desktop/GuiceSetupTest.java | 2 ++ 2 files changed, 5 insertions(+) diff --git a/desktop/src/main/java/bisq/desktop/main/account/AccountView.fxml b/desktop/src/main/java/bisq/desktop/main/account/AccountView.fxml index c9929dfe861..7b33f901204 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/AccountView.fxml +++ b/desktop/src/main/java/bisq/desktop/main/account/AccountView.fxml @@ -28,16 +28,19 @@ xmlns:fx="http://javafx.com/fxml"> diff --git a/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java b/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java index 1a1c73a85c9..890be60f48e 100644 --- a/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java +++ b/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java @@ -40,7 +40,9 @@ import bisq.core.user.User; import bisq.core.util.BSFormatter; import bisq.core.util.BsqFormatter; + import bisq.core.xmr.wallet.XmrWalletRpcWrapper; + import bisq.network.p2p.network.BridgeAddressProvider; import bisq.network.p2p.seed.SeedNodeRepository; From 4100fe4136b4d56de4c103ed53a5f0889bd6394b Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Mon, 16 Sep 2019 21:43:06 +0100 Subject: [PATCH 07/22] Quick fix; misplaced bracket. --- core/src/main/java/bisq/core/user/PreferencesPayload.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index bd7b8890804..e64300ad1d5 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -284,7 +284,7 @@ public static PersistableEnvelope fromProto(protobuf.PreferencesPayload proto, C proto.getBuyerSecurityDepositAsPercent(), proto.getIgnoreDustThreshold(), proto.getBuyerSecurityDepositAsPercentForCrypto(), - proto.getBlockNotifyPort()), + proto.getBlockNotifyPort(), proto.getUseBisqXmrWallet(), proto.getXmrUserHost(), proto.getXmrHostPort(), From f06f1710f0b20459c955d77f82930c068565b62a Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Mon, 16 Sep 2019 21:56:03 +0100 Subject: [PATCH 08/22] Switching mode to debug from info. --- .../trade/XmrTradeAutomationInterceptor.java | 2 +- .../core/xmr/wallet/XmrWalletRpcWrapper.java | 42 +++++++++---------- .../content/wallet/AltCoinWalletsView.java | 14 +++---- .../content/wallet/monero/XmrBalanceUtil.java | 4 +- .../content/wallet/monero/XmrWalletView.java | 10 ++--- .../wallet/monero/receive/XmrReceiveView.java | 2 +- .../wallet/monero/send/XmrSendView.java | 2 +- .../content/wallet/monero/tx/XmrTxView.java | 2 +- 8 files changed, 39 insertions(+), 39 deletions(-) diff --git a/core/src/main/java/bisq/core/xmr/trade/XmrTradeAutomationInterceptor.java b/core/src/main/java/bisq/core/xmr/trade/XmrTradeAutomationInterceptor.java index b6b41f606a0..1b60d64d9bf 100644 --- a/core/src/main/java/bisq/core/xmr/trade/XmrTradeAutomationInterceptor.java +++ b/core/src/main/java/bisq/core/xmr/trade/XmrTradeAutomationInterceptor.java @@ -43,7 +43,7 @@ public XmrTradeAutomationInterceptor(Preferences preferences) { /* @Around("@annotation(LogExecutionTime)") public Object transferFundsToSeller(ProceedingJoinPoint joinPoint) throws Throwable { - log.info("transferFundsToSeller({})", joinPoint.getArgs()); + log.debug("transferFundsToSeller({})", joinPoint.getArgs()); //TODO Get trade object (an instance of SellerAsMakerTrade or BuyerAsMakerTrade) and check the //TODO Check if trade automation is active - PreferencesPayload.useBisqXmrWallet = true diff --git a/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java b/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java index 14d88e784e3..be361e06f96 100644 --- a/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java +++ b/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java @@ -56,7 +56,7 @@ public class XmrWalletRpcWrapper { @Inject public XmrWalletRpcWrapper(Preferences preferences) { - log.info("instantiating MoneroWalletRpc..."); + log.debug("instantiating MoneroWalletRpc..."); HOST = preferences.getXmrUserHostDelegate(); PORT = Integer.parseInt(preferences.getXmrHostPortDelegate()); //TODO(niyid) Use preferences to determine which wallet to load in XmrWalletRpcWrapper @@ -74,7 +74,7 @@ public XmrWalletRpcWrapper(Preferences preferences) { validator = new CryptoNoteAddressValidator(true, validPrefixes); } if(!validator.validate(primaryAddress).isValid()) { - log.info("Wallet RPC Connection not valid (MAINNET/TESTNET mix-up); shutting down..."); + log.debug("Wallet RPC Connection not valid (MAINNET/TESTNET mix-up); shutting down..."); xmrWalletRpcRunning = false; walletRpc.close(); walletRpc = null; @@ -96,26 +96,26 @@ public void run() { time0 = System.currentTimeMillis(); BigInteger balance = walletRpc.getBalance(); walletRpcData.put("getBalance", balance); - log.info("listen -time: {}ms - balance: {}", (System.currentTimeMillis() - time0), balance); + log.debug("listen -time: {}ms - balance: {}", (System.currentTimeMillis() - time0), balance); } if(walletRpcData.containsKey("getUnlockedBalance")) { time0 = System.currentTimeMillis(); BigInteger unlockedBalance = walletRpc.getUnlockedBalance(); walletRpcData.put("getUnlockedBalance", unlockedBalance); - log.info("listen -time: {}ms - unlockedBalance: {}", (System.currentTimeMillis() - time0)); + log.debug("listen -time: {}ms - unlockedBalance: {}", (System.currentTimeMillis() - time0)); } if(walletRpcData.containsKey("getPrimaryAddress")) { time0 = System.currentTimeMillis(); primaryAddress = walletRpc.getPrimaryAddress(); walletRpcData.put("getPrimaryAddress", primaryAddress); - log.info("listen -time: {}ms - address: {}", (System.currentTimeMillis() - time0), primaryAddress); + log.debug("listen -time: {}ms - address: {}", (System.currentTimeMillis() - time0), primaryAddress); } if(walletRpcData.containsKey("getTxs")) { time0 = System.currentTimeMillis(); List txList = walletRpc.getTxs(); if(txList != null && !txList.isEmpty()) { walletRpcData.put("getTxs", transformTxWallet(txList)); - log.info("listen -time: {}ms - transactions: {}", (System.currentTimeMillis() - time0), txList.size()); + log.debug("listen -time: {}ms - transactions: {}", (System.currentTimeMillis() - time0), txList.size()); } else { List list = Collections.emptyList(); walletRpcData.put("getTxs", list); @@ -143,7 +143,7 @@ public boolean test(MoneroTxWallet t) { }; List list = new ArrayList<>(); txList.stream().filter(predicate).forEach(txWallet -> list.add(new XmrTxListItem(txWallet))); - log.info("transformTxWallet => {}", list.size()); + log.debug("transformTxWallet => {}", list.size()); return list.size() > 100 ? list.subList(0, 100) : list;//Reduce transactions to no more than 100. } @@ -163,7 +163,7 @@ public void run() { long time0 = System.currentTimeMillis(); List txs = walletRpc.getTxs(Arrays.asList(searchParam.split(","))); walletRpcData.put("getTxs", transformTxWallet(txs)); - log.info("listen -time: {}ms - searchTx: {}", (System.currentTimeMillis() - time0), txs.size()); + log.debug("listen -time: {}ms - searchTx: {}", (System.currentTimeMillis() - time0), txs.size()); } listener.onUpdateBalances(walletRpcData); listener.stopAnimation(); @@ -208,8 +208,8 @@ public void run() { if(doNotRelay) { walletRpcData.put("txToRelay", tx); } - log.info("MoneroTxWallet => {}", walletRpcData); - log.info("createTx -time: {}ms - createTx: {}", (System.currentTimeMillis() - time0), tx.getSize()); + log.debug("MoneroTxWallet => {}", walletRpcData); + log.debug("createTx -time: {}ms - createTx: {}", (System.currentTimeMillis() - time0), tx.getSize()); listener.onUpdateBalances(walletRpcData); listener.stopAnimation(); } @@ -235,8 +235,8 @@ public void run() { String txId = walletRpc.relayTx(txToRelay); walletRpcData.put("txId", txId); walletRpcData.put("getMetadata", txToRelay.getMetadata()); - log.info("relayTx metadata: {}", txToRelay.getMetadata()); - log.info("relayTx -time: {}ms - txId: {}", (System.currentTimeMillis() - time0), txId); + log.debug("relayTx metadata: {}", txToRelay.getMetadata()); + log.debug("relayTx -time: {}ms - txId: {}", (System.currentTimeMillis() - time0), txId); } listener.stopAnimation(); } @@ -249,7 +249,7 @@ public void run() { } public void fetchLanguages(WalletBalanceListener listener, ComboBox languageComboBox) { - log.info("createWalletRpcInstance - {}, {}"); + log.debug("createWalletRpcInstance - {}, {}"); ObservableList languageOptions = FXCollections.observableArrayList(); Runnable command = new Runnable() { @@ -268,20 +268,20 @@ public void run() { } public void openWalletRpcInstance(WalletBalanceListener listener) { - log.info("openWalletRpcInstance - {}, {}", HOST, PORT); + log.debug("openWalletRpcInstance - {}, {}", HOST, PORT); Thread checkIfXmrLocalHostNodeIsRunningThread = new Thread(() -> { Thread.currentThread().setName("checkIfXmrLocalHostNodeIsRunningThread"); Socket socket = null; try { socket = new Socket(); socket.connect(new InetSocketAddress(InetAddresses.forString(HOST), PORT), 5000); - log.info("Localhost Monero Wallet RPC detected."); + log.debug("Localhost Monero Wallet RPC detected."); UserThread.execute(() -> { xmrWalletRpcRunning = true; walletRpc = new MoneroWalletRpc(new MoneroRpcConnection("http://" + HOST + ":" + PORT)); }); } catch (Throwable e) { - log.info("createWalletRpcInstance - {}", e.getMessage()); + log.debug("createWalletRpcInstance - {}", e.getMessage()); e.printStackTrace(); if(listener != null) { listener.popupErrorWindow(Res.get("shared.account.wallet.popup.error.startupFailed", "Monero", e.getLocalizedMessage())); @@ -303,14 +303,14 @@ public boolean isXmrWalletRpcRunning() { } public void createWalletRpcInstance(WalletBalanceListener listener, String walletFile, String password, String language, TextArea seedWordsTextArea) { - log.info("createWalletRpcInstance - {}, {}, {}, {}", HOST, PORT, walletFile, password); + log.debug("createWalletRpcInstance - {}, {}, {}, {}", HOST, PORT, walletFile, password); Thread checkIfXmrLocalHostNodeIsRunningThread = new Thread(() -> { Thread.currentThread().setName("checkIfXmrLocalHostNodeIsRunningThread"); Socket socket = null; try { socket = new Socket(); socket.connect(new InetSocketAddress(InetAddresses.forString(HOST), PORT), 5000); - log.info("Localhost Monero Wallet RPC detected."); + log.debug("Localhost Monero Wallet RPC detected."); UserThread.execute(() -> { xmrWalletRpcRunning = true; walletRpc = new MoneroWalletRpc(new MoneroRpcConnection("http://" + HOST + ":" + PORT)); @@ -319,7 +319,7 @@ public void createWalletRpcInstance(WalletBalanceListener listener, String walle seedWordsTextArea.setText(mnemonic); }); } catch (Throwable e) { - log.info("createWalletRpcInstance - {}", e.getMessage()); + log.debug("createWalletRpcInstance - {}", e.getMessage()); e.printStackTrace(); if(listener != null) { listener.popupErrorWindow(Res.get("shared.account.wallet.popup.error.startupFailed", "Monero", e.getLocalizedMessage())); @@ -346,8 +346,8 @@ public void run() { if(handler != null) { long time0 = System.currentTimeMillis(); String signature = walletRpc.getSpendProof(txId, message); - log.info("relayTx -time: {}ms - txId: {}", (System.currentTimeMillis() - time0), txId); - log.info("relayTx signature: {}", signature); + log.debug("relayTx -time: {}ms - txId: {}", (System.currentTimeMillis() - time0), txId); + log.debug("relayTx signature: {}", signature); handler.update(txId, message, signature); } handler.stopAnimation(); diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/AltCoinWalletsView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/AltCoinWalletsView.java index 5f82d6fa443..8389d8ae25f 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/AltCoinWalletsView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/AltCoinWalletsView.java @@ -44,12 +44,12 @@ private AltCoinWalletsView(CachingViewLoader viewLoader, Navigation navigation, @Override public void initialize() { - log.info("initialize()"); + log.debug("initialize()"); //TODO Update as altcoinWalletsTab.setVisible(Preferences.PreferencesPayload.useBisqXmrWallet) root.setPadding(new Insets(20)); navigationListener = viewPath -> { - log.info("navigationListener.viewPath1=" + viewPath); + log.debug("navigationListener.viewPath1=" + viewPath); if(selectedTab == null) { selectedTab = moneroWalletTab; } @@ -57,7 +57,7 @@ public void initialize() { currentTabView = XmrWalletView.class; }//TODO Tabs for other altcoins added here navigation.navigateTo(MainView.class, AccountView.class, AltCoinWalletsView.class, currentTabView); - log.info("navigationListener.viewPath2=" + viewPath); + log.debug("navigationListener.viewPath2=" + viewPath); if (viewPath.size() == 4 && viewPath.indexOf(AccountView.class) == 1) { loadView(viewPath.tip()); } else { @@ -66,8 +66,8 @@ public void initialize() { }; tabChangeListener = (ov, oldValue, newValue) -> { - log.info("tabChangeListener.oldValue=" + oldValue); - log.info("tabChangeListener.newValue=" + newValue); + log.debug("tabChangeListener.oldValue=" + oldValue); + log.debug("tabChangeListener.newValue=" + newValue); if (newValue == moneroWalletTab && selectedTab != moneroWalletTab) { loadView(XmrWalletView.class); } @@ -79,7 +79,7 @@ public void initialize() { @Override protected void activate() { - log.info("activate()"); + log.debug("activate()"); if(preferences.isUseBisqXmrWallet()) { navigation.addListener(navigationListener); @@ -102,7 +102,7 @@ protected void deactivate() { } private void loadView(Class viewClass) { - log.info("loadView({})", viewClass); + log.debug("loadView({})", viewClass); if(preferences.isUseBisqXmrWallet()) { diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrBalanceUtil.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrBalanceUtil.java index 68d46f72f67..8d854721d35 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrBalanceUtil.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrBalanceUtil.java @@ -45,14 +45,14 @@ public int addGroup(GridPane gridPane, int gridRow) { } public void postSendUpdate(HashMap walletRpcData) { - log.info("postSendUpdate => {}", walletRpcData); + log.debug("postSendUpdate => {}", walletRpcData); actualBalanceTextField.setText(xmrFormatter.formatBigInteger(walletWrapper.getWalletRpc().getBalance())); unlockedBalanceTextField.setText(xmrFormatter.formatBigInteger(walletWrapper.getWalletRpc().getUnlockedBalance())); } @Override public void onUpdateBalances(HashMap walletRpcData) { - log.info("onUpdateBalances => {}", walletRpcData); + log.debug("onUpdateBalances => {}", walletRpcData); if(walletRpcData.get("getBalance") != null) { BigInteger balance = (BigInteger) walletRpcData.get("getBalance"); actualBalanceTextField.setText(xmrFormatter.formatBigInteger(balance)); diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrWalletView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrWalletView.java index c759fdb61be..3b4b0bf7f9c 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrWalletView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrWalletView.java @@ -63,7 +63,7 @@ private XmrWalletView(CachingViewLoader viewLoader, Navigation navigation, Prefe @Override public void initialize() { - log.info("XmrWalletView.initialize({})", selectedTab); + log.debug("XmrWalletView.initialize({})", selectedTab); root.setTabClosingPolicy(TabPane.TabClosingPolicy.ALL_TABS); xmrSendTab.setText(Res.get("shared.account.wallet.menuItem.send").toUpperCase()); xmrReceiveTab.setText(Res.get("shared.account.wallet.menuItem.receive").toUpperCase()); @@ -74,7 +74,7 @@ public void initialize() { } selectView(); navigationListener = viewPath -> { - log.info("XmrWalletView.viewPath={}, size={}", viewPath, viewPath.size()); + log.debug("XmrWalletView.viewPath={}, size={}", viewPath, viewPath.size()); if (viewPath.size() == 4 && navigation.getCurrentPath().get(3) == XmrWalletView.class && navigation.getCurrentPath().get(2) == AltCoinWalletsView.class && navigation.getCurrentPath().get(1) == AccountView.class) { selectView(); @@ -109,7 +109,7 @@ private void selectView() { @Override protected void activate() { - log.info("XmrWalletView.activate({})", selectedTab); + log.debug("XmrWalletView.activate({})", selectedTab); navigation.addListener(navigationListener); root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener); @@ -129,13 +129,13 @@ else if (selectedItem == xmrTxTab) @Override protected void deactivate() { - log.info("XmrWalletView.deactivate()"); + log.debug("XmrWalletView.deactivate()"); navigation.removeListener(navigationListener); root.getSelectionModel().selectedItemProperty().removeListener(tabChangeListener); } private void loadView(Class viewClass) { - log.info("XmrWalletView.loadView: " + viewClass); + log.debug("XmrWalletView.loadView: " + viewClass); if (selectedTab != null && selectedTab.getContent() != null) { if (selectedTab.getContent() instanceof ScrollPane) { ((ScrollPane) selectedTab.getContent()).setContent(null); diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/receive/XmrReceiveView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/receive/XmrReceiveView.java index 6944189d205..d9426f83fff 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/receive/XmrReceiveView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/receive/XmrReceiveView.java @@ -99,7 +99,7 @@ public void initialize() { @Override public void onUpdateBalances(HashMap walletRpcData) { - log.info("onUpdateBalances => {}", walletRpcData); + log.debug("onUpdateBalances => {}", walletRpcData); xmrBalanceUtil.onUpdateBalances(walletRpcData); String address = (String) walletRpcData.get("getPrimaryAddress"); addressTextField.setAddress(address); diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java index 8bc8007a1ef..c51d1ae49dd 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java @@ -138,7 +138,7 @@ protected void deactivate() { @Override public void onUpdateBalances(HashMap walletRpcData) { - log.info("onUpdateBalances => {}", walletRpcData); + log.debug("onUpdateBalances => {}", walletRpcData); BigInteger fee = walletRpcData.get("getFee") != null ? (BigInteger) walletRpcData.get("getFee") : BigInteger.ZERO; BigInteger unlockedBalance = walletRpcData.get("getUnlockedBalance") != null ? (BigInteger) walletRpcData.get("getUnlockedBalance") : BigInteger.ZERO; Integer size = walletRpcData.get("getSize") != null ? (Integer) walletRpcData.get("getSize") : 0; diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java index d61eec1a694..6007d6f13ca 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java @@ -153,7 +153,7 @@ protected void deactivate() { @SuppressWarnings("unchecked") @Override public void onUpdateBalances(HashMap walletRpcData) { - log.info("onUpdateBalances => {}", walletRpcData.keySet()); + log.debug("onUpdateBalances => {}", walletRpcData.keySet()); List txList = (List) walletRpcData.get("getTxs"); if(txList != null) { observableList.setAll(txList); From 35643bc71b3ecc900407d985c33d9feb99a44ce6 Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Tue, 17 Sep 2019 17:57:45 +0100 Subject: [PATCH 09/22] Keeping abreast of new changes. --- common/src/main/proto/pb.proto | 15 +++++----- .../bisq/core/user/PreferencesPayload.java | 5 +++- .../resources/i18n/displayStrings.properties | 28 ++++++++----------- .../desktop/main/account/AccountView.java | 2 ++ .../settings/preferences/PreferencesView.java | 4 +-- .../java/bisq/desktop/GuiceSetupTest.java | 9 ++++++ 6 files changed, 37 insertions(+), 26 deletions(-) diff --git a/common/src/main/proto/pb.proto b/common/src/main/proto/pb.proto index ee4d5931154..c048f8e1e62 100644 --- a/common/src/main/proto/pb.proto +++ b/common/src/main/proto/pb.proto @@ -1378,13 +1378,14 @@ message PreferencesPayload { int32 ignore_dust_threshold = 51; double buyer_security_deposit_as_percent_for_crypto = 52; int32 block_notify_port = 53; - bool use_bisq_xmr_wallet = 54; - string xmr_user_host = 55; - string xmr_host_port = 56; - string xmr_rpc_user = 57; - string xmr_rpc_pwd = 58; - repeated string xmr_hosts = 59; - int32 xmr_host_option_ordinal = 60; + int32 css_theme = 54; + bool use_bisq_xmr_wallet = 55; + string xmr_user_host = 56; + string xmr_host_port = 57; + string xmr_rpc_user = 58; + string xmr_rpc_pwd = 59; + repeated string xmr_hosts = 60; + int32 xmr_host_option_ordinal = 61; } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index e64300ad1d5..8c18078373b 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -124,7 +124,8 @@ public final class PreferencesPayload implements PersistableEnvelope { private int ignoreDustThreshold = 600; private double buyerSecurityDepositAsPercentForCrypto = getDefaultBuyerSecurityDepositAsPercent(new CryptoCurrencyAccount()); private int blockNotifyPort; - + private int cssTheme; + private boolean useBisqXmrWallet = false; private String xmrUserHost = "127.0.0.1"; @@ -193,6 +194,7 @@ public Message toProtoMessage() { .setIgnoreDustThreshold(ignoreDustThreshold) .setBuyerSecurityDepositAsPercentForCrypto(buyerSecurityDepositAsPercentForCrypto) .setBlockNotifyPort(blockNotifyPort) + .setCssTheme(cssTheme) .setUseBisqXmrWallet(useBisqXmrWallet) .setXmrUserHost(xmrUserHost) .setXmrHostPort(xmrHostPort) @@ -285,6 +287,7 @@ public static PersistableEnvelope fromProto(protobuf.PreferencesPayload proto, C proto.getIgnoreDustThreshold(), proto.getBuyerSecurityDepositAsPercentForCrypto(), proto.getBlockNotifyPort(), + proto.getCssTheme(), proto.getUseBisqXmrWallet(), proto.getXmrUserHost(), proto.getXmrHostPort(), diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 41467f07aaa..b50f6c7c264 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -287,6 +287,11 @@ shared.account.wallet.validation.insufficientBalance=Your available balance is { shared.account.wallet.validation.exceedsMaxTradeLimit=Your trade limit is {0}. shared.account.wallet.validation.amountBelowMinAmount=Min. amount is {0} +shared.selectedArbitrator=Selected arbitrator +shared.selectedMediator=Selected mediator +shared.mediator=Mediator +shared.arbitrator2=Arbitrator + #################################################################### # UI views #################################################################### @@ -687,20 +692,19 @@ portfolio.pending.step2_seller.waitPayment.headline=Wait for payment portfolio.pending.step2_seller.f2fInfo.headline=Buyer's contact information portfolio.pending.step2_seller.waitPayment.msg=The deposit transaction has at least one blockchain confirmation.\nYou need to wait until the BTC buyer starts the {0} payment. portfolio.pending.step2_seller.warn=The BTC buyer still has not done the {0} payment.\nYou need to wait until they have started the payment.\nIf the trade has not been completed on {1} the arbitrator will investigate. -portfolio.pending.step2_seller.openForDispute=The BTC buyer has not started their payment!\nThe max. allowed period for the trade has elapsed.\nYou can wait longer and give the trading peer more time or contact the arbitrator for opening a dispute. - +portfolio.pending.step2_seller.openForDispute=The BTC buyer has not started their payment!\nThe max. allowed period for the trade has elapsed.\nYou can wait longer and give the trading peer more time or contact the mediator for assistance. tradeChat.chatWindowTitle=Chat window for trade with ID ''{0}'' tradeChat.openChat=Open chat window tradeChat.rules=You can communicate with your trade peer to resolve potential problems with this trade.\n\ It is not mandatory to reply in the chat.\n\ - If a trader violates the below rules, open a dispute with 'Cmd/Ctrl + o' and report it to the arbitrator.\n\n\ + If a trader violates any of the rules below, open a dispute and report it to the mediator or arbitrator.\n\n\ Chat rules:\n\ \t● Do not send any links (risk of malware). You can send the transaction ID and the name of a block explorer.\n\ \t● Do not send your seed words, private keys, passwords or other sensitive information!\n\ \t● Do not encourage trading outside of Bisq (no security).\n\ \t● Do not engage in any form of social engineering scam attempts.\n\ \t● If a peer is not responding and prefers to not communicate via chat, respect their decision.\n\ - \t● Keep conversation scope limited to the trade. This chat is not a messenger replacement or trollbox.\n\ + \t● Keep conversation scope limited to the trade. This chat is not a messenger replacement or troll-box.\n\ \t● Keep conversation friendly and respectful. # suppress inspection "UnusedProperty" @@ -823,7 +827,9 @@ portfolio.pending.disputeOpened=Dispute opened portfolio.pending.openSupport=Open support ticket portfolio.pending.supportTicketOpened=Support ticket opened portfolio.pending.requestSupport=Request support -portfolio.pending.error.requestSupport=Please report the problem to your arbitrator.\n\nThey will forward the information to the developers to investigate the problem.\nAfter the problem has been analyzed you will get back all locked funds. +portfolio.pending.error.requestSupport=Please report the problem to your mediator or arbitrator.\n\nThey will forward the \ + information to the developers to investigate the problem.\nAfter the problem has been analyzed you will \ + get back all locked funds. portfolio.pending.communicateWithArbitrator=Please communicate in the \"Support\" screen with the arbitrator. portfolio.pending.supportTicketOpenedMyUser=You opened already a support ticket.\n{0} portfolio.pending.disputeOpenedMyUser=You opened already a dispute.\n{0} @@ -1146,6 +1152,7 @@ account.menu.notifications=Notifications account.menu.wallets.moneroWalletView=Monero account.menu.wallets.monero.navigation.funds.depositFunds=\"Funds/Receive funds\" +## TODO should we rename the following to a gereric name? account.arbitratorRegistration.pubKey=Public key account.arbitratorRegistration.register=Register arbitrator @@ -1157,17 +1164,6 @@ account.arbitratorRegistration.removedFailed=Could not remove arbitrator.{0} account.arbitratorRegistration.registerSuccess=You have successfully registered your arbitrator to the Bisq network. account.arbitratorRegistration.registerFailed=Could not register arbitrator.{0} -account.arbitratorSelection.minOneArbitratorRequired=You need to set at least 1 language.\nWe added the default language for you. -account.arbitratorSelection.whichLanguages=Which languages do you speak? -account.arbitratorSelection.whichDoYouAccept=Which arbitrators do you accept -account.arbitratorSelection.autoSelect=Auto select all arbitrators with matching language -account.arbitratorSelection.regDate=Registration date -account.arbitratorSelection.languages=Languages -account.arbitratorSelection.cannotSelectHimself=An arbitrator cannot select themselves for trading. -account.arbitratorSelection.noMatchingLang=No matching language. -account.arbitratorSelection.noLang=You can only select arbitrators who are speaking at least 1 common language. -account.arbitratorSelection.minOne=You need to have at least one arbitrator selected. - account.altcoin.yourAltcoinAccounts=Your altcoin accounts account.altcoin.popup.wallet.msg=Please be sure that you follow the requirements for the usage of {0} wallets as \ described on the {1} web page.\nUsing wallets from centralized exchanges where (a) you don''t control your keys or \ diff --git a/desktop/src/main/java/bisq/desktop/main/account/AccountView.java b/desktop/src/main/java/bisq/desktop/main/account/AccountView.java index 43e18b97f75..82ce4a3b086 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/AccountView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/AccountView.java @@ -32,6 +32,8 @@ import bisq.desktop.main.account.content.password.PasswordView; import bisq.desktop.main.account.content.seedwords.SeedWordsView; import bisq.desktop.main.account.content.wallet.AltCoinWalletsView; +//import bisq.desktop.main.account.register.arbitrator.ArbitratorRegistrationView; +//import bisq.desktop.main.account.register.mediator.MediatorRegistrationView; import bisq.desktop.main.overlays.popups.Popup; import bisq.core.locale.Res; diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 26a44b71ac3..0cff4f740ac 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -106,8 +106,8 @@ public class PreferencesView extends ActivatableViewAndModel xmrHostComboBox; //private ComboBox selectBaseCurrencyNetworkComboBox; - private ToggleButton showOwnOffersInOfferBook, useAnimations, sortMarketCurrenciesNumerically, avoidStandbyMode, - useCustomFee, useBisqXmrWallet; + private ToggleButton showOwnOffersInOfferBook, useAnimations, useDarkMode, sortMarketCurrenciesNumerically, + avoidStandbyMode, useCustomFee, useBisqXmrWallet; private int gridRow = 0; private InputTextField transactionFeeInputTextField, ignoreTradersListInputTextField, ignoreDustThresholdInputTextField, /*referralIdInputTextField,*/ diff --git a/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java b/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java index 890be60f48e..3ecb351d018 100644 --- a/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java +++ b/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java @@ -135,6 +135,15 @@ public void testGuiceSetup() { assertSingleton(PriceAlert.class); assertSingleton(MarketAlerts.class); assertSingleton(ChargeBackRisk.class); +// assertSingleton(ArbitratorService.class); +// assertSingleton(ArbitratorManager.class); +// assertSingleton(ArbitrationManager.class); +// assertSingleton(ArbitrationDisputeListService.class); +// assertSingleton(MediatorService.class); +// assertSingleton(MediatorManager.class); +// assertSingleton(MediationManager.class); +// assertSingleton(MediationDisputeListService.class); +// assertSingleton(TraderChatManager.class); assertSingleton(XmrWalletRpcWrapper.class); assertNotSingleton(Storage.class); From 471960cf8517867abea0bde3ec96f0afcd34dae7 Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Tue, 17 Sep 2019 20:15:09 +0100 Subject: [PATCH 10/22] Refixing merged items. --- core/src/main/java/bisq/core/user/PreferencesPayload.java | 4 ---- .../src/main/java/bisq/desktop/main/account/AccountView.java | 5 ----- 2 files changed, 9 deletions(-) diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index 090015b76d9..2c4d6111558 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -94,7 +94,6 @@ public final class PreferencesPayload implements PersistableEnvelope { private long buyerSecurityDepositAsLong; private boolean useAnimations; - private int cssTheme; @Nullable private PaymentAccount selectedPaymentAccountForCreateOffer; private boolean payFeeInBtc = true; @@ -126,7 +125,6 @@ public final class PreferencesPayload implements PersistableEnvelope { private double buyerSecurityDepositAsPercentForCrypto = getDefaultBuyerSecurityDepositAsPercent(new CryptoCurrencyAccount()); private int blockNotifyPort; private int cssTheme; - private boolean useBisqXmrWallet = false; private String xmrUserHost = "127.0.0.1"; @@ -181,7 +179,6 @@ public Message toProtoMessage() { .setDirectoryChooserPath(directoryChooserPath) .setBuyerSecurityDepositAsLong(buyerSecurityDepositAsLong) .setUseAnimations(useAnimations) - .setCssTheme(cssTheme) .setPayFeeInBtc(payFeeInBtc) .setBridgeOptionOrdinal(bridgeOptionOrdinal) .setTorTransportOrdinal(torTransportOrdinal) @@ -267,7 +264,6 @@ public static PersistableEnvelope fromProto(protobuf.PreferencesPayload proto, C proto.getDirectoryChooserPath(), proto.getBuyerSecurityDepositAsLong(), proto.getUseAnimations(), - proto.getCssTheme(), paymentAccount, proto.getPayFeeInBtc(), proto.getBridgeAddressesList().isEmpty() ? null : new ArrayList<>(proto.getBridgeAddressesList()), diff --git a/desktop/src/main/java/bisq/desktop/main/account/AccountView.java b/desktop/src/main/java/bisq/desktop/main/account/AccountView.java index 2b0713243f4..30026390781 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/AccountView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/AccountView.java @@ -30,14 +30,9 @@ import bisq.desktop.main.account.content.notifications.MobileNotificationsView; import bisq.desktop.main.account.content.password.PasswordView; import bisq.desktop.main.account.content.seedwords.SeedWordsView; -<<<<<<< HEAD import bisq.desktop.main.account.content.wallet.AltCoinWalletsView; -//import bisq.desktop.main.account.register.arbitrator.ArbitratorRegistrationView; -//import bisq.desktop.main.account.register.mediator.MediatorRegistrationView; -======= import bisq.desktop.main.account.register.arbitrator.ArbitratorRegistrationView; import bisq.desktop.main.account.register.mediator.MediatorRegistrationView; ->>>>>>> 736da2a0062735a2dc3ab076587c88f8af481489 import bisq.desktop.main.overlays.popups.Popup; import bisq.core.locale.Res; From 2825e23ca7ec5b88cf910f2b46f384f1bcdda3ee Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Tue, 17 Sep 2019 21:38:48 +0100 Subject: [PATCH 11/22] Upgraded to monero-wallet-java v0.1.1 --- build.gradle | 2 +- .../bisq/core/xmr/wallet/XmrTxListItem.java | 4 +- .../core/xmr/wallet/XmrWalletRpcWrapper.java | 49 ++++++++++--------- .../content/wallet/monero/tx/XmrTxView.java | 4 +- 4 files changed, 32 insertions(+), 27 deletions(-) diff --git a/build.gradle b/build.gradle index e4f2bca762a..5ea324095bf 100644 --- a/build.gradle +++ b/build.gradle @@ -82,7 +82,7 @@ configure(subprojects) { dependencies { testCompile "junit:junit:$junitVersion" - compile 'woodser:monero-wallet-java:0.0.2-SNAPSHOT' + compile 'woodser:monero-wallet-java:0.1.1-SNAPSHOT' } tasks.withType(JavaCompile) { diff --git a/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java b/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java index 4c917aa5f99..46f8ab1d5ca 100644 --- a/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java +++ b/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java @@ -46,13 +46,13 @@ public class XmrTxListItem { @Getter private boolean confirmed; @Getter - private int confirmations = 0; + private long confirmations = 0; @Getter private String key; @Getter private Integer mixin; @Getter - private Integer unlockTime; + private Long unlockTime; @Getter private String destinationAddress; diff --git a/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java b/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java index be361e06f96..ac633564677 100644 --- a/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java +++ b/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java @@ -38,6 +38,7 @@ import monero.wallet.MoneroWalletRpc; import monero.wallet.model.MoneroSendPriority; import monero.wallet.model.MoneroSendRequest; +import monero.wallet.model.MoneroTxSet; import monero.wallet.model.MoneroTxWallet; @Slf4j @@ -187,29 +188,33 @@ public void run() { long time0 = System.currentTimeMillis(); MoneroSendRequest request = new MoneroSendRequest(accountIndex, address, amount, priority); request.setDoNotRelay(doNotRelay); - MoneroTxWallet tx = walletRpc.send(request); - walletRpcData.put("getBalance", walletRpc.getBalance()); - walletRpcData.put("getUnlockedBalance", walletRpc.getUnlockedBalance()); - walletRpcData.put("getDoNotRelay", tx.getDoNotRelay()); - walletRpcData.put("getExtra", tx.getExtra()); - walletRpcData.put("getFee", tx.getFee()); - walletRpcData.put("getId", tx.getId()); - walletRpcData.put("getKey", tx.getKey()); - walletRpcData.put("getLastRelayedTimestamp", tx.getLastRelayedTimestamp() != null ? new Date(tx.getLastRelayedTimestamp()) : null); - walletRpcData.put("getMixin", tx.getMixin()); - walletRpcData.put("getNumConfirmations", tx.getNumConfirmations()); - walletRpcData.put("getOutgoingAmount", tx.getOutgoingAmount()); - walletRpcData.put("getOutgoingTransfer", tx.getOutgoingTransfer()); - walletRpcData.put("getPaymentId", tx.getPaymentId()); - walletRpcData.put("getTimestamp", tx.getBlock().getTimestamp() != null ? new Date(tx.getBlock().getTimestamp()) : null); - walletRpcData.put("getSize", tx.getSize()); - walletRpcData.put("getUnlockTime", tx.getUnlockTime()); - walletRpcData.put("getVersion", tx.getVersion()); - if(doNotRelay) { - walletRpcData.put("txToRelay", tx); + MoneroTxSet txSet = walletRpc.send(request); + + if(txSet != null && txSet.getTxs() != null && !txSet.getTxs().isEmpty()) { + MoneroTxWallet tx = txSet.getTxs().get(0); + walletRpcData.put("getBalance", walletRpc.getBalance()); + walletRpcData.put("getUnlockedBalance", walletRpc.getUnlockedBalance()); + walletRpcData.put("getDoNotRelay", tx.getDoNotRelay()); + walletRpcData.put("getExtra", tx.getExtra()); + walletRpcData.put("getFee", tx.getFee()); + walletRpcData.put("getId", tx.getId()); + walletRpcData.put("getKey", tx.getKey()); + walletRpcData.put("getLastRelayedTimestamp", tx.getLastRelayedTimestamp() != null ? new Date(tx.getLastRelayedTimestamp()) : null); + walletRpcData.put("getMixin", tx.getMixin()); + walletRpcData.put("getNumConfirmations", tx.getNumConfirmations()); + walletRpcData.put("getOutgoingAmount", tx.getOutgoingAmount()); + walletRpcData.put("getOutgoingTransfer", tx.getOutgoingTransfer()); + walletRpcData.put("getPaymentId", tx.getPaymentId()); + walletRpcData.put("getTimestamp", tx.getBlock().getTimestamp() != null ? new Date(tx.getBlock().getTimestamp()) : null); + walletRpcData.put("getSize", tx.getSize()); + walletRpcData.put("getUnlockTime", tx.getUnlockTime()); + walletRpcData.put("getVersion", tx.getVersion()); + if(doNotRelay) { + walletRpcData.put("txToRelay", tx); + } + log.debug("MoneroTxWallet => {}", walletRpcData); + log.debug("createTx -time: {}ms - createTx: {}", (System.currentTimeMillis() - time0), tx.getSize()); } - log.debug("MoneroTxWallet => {}", walletRpcData); - log.debug("createTx -time: {}ms - createTx: {}", (System.currentTimeMillis() - time0), tx.getSize()); listener.onUpdateBalances(walletRpcData); listener.stopAnimation(); } diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java index 6007d6f13ca..af75504d99d 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java @@ -404,8 +404,8 @@ public void updateItem(final XmrTxListItem item, boolean empty) { super.updateItem(item, empty); if (item != null && !empty) { - String paymentId = Integer.toString(item.getConfirmations()); - setText(paymentId); + String confirmations = Long.toString(item.getConfirmations()); + setText(confirmations); } else { setText(""); } From 3c7f1d2cd1886c2d63ce1332d22dcc9d64547cd6 Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Tue, 17 Sep 2019 22:24:31 +0100 Subject: [PATCH 12/22] Maven plugin resolved the problem with monero-wallet-rpc. --- build.gradle | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5ea324095bf..27f2de149c0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,4 @@ + buildscript { repositories { jcenter() @@ -25,6 +26,7 @@ configure(subprojects) { apply plugin: 'kotlin' apply plugin: 'java' apply plugin: 'com.google.osdetector' + apply plugin: 'maven' sourceCompatibility = 1.10 @@ -78,10 +80,10 @@ configure(subprojects) { mavenLocal() mavenCentral() maven { url 'https://jitpack.io' } + maven { url "http://repo.maven.apache.org/maven2" } } dependencies { - testCompile "junit:junit:$junitVersion" compile 'woodser:monero-wallet-java:0.1.1-SNAPSHOT' } @@ -233,6 +235,7 @@ configure(project(':core')) { dependencies { compile project(':assets') compile project(':p2p') + compile "woodser:monero-wallet-java:0.1.1-SNAPSHOT" compile "net.sf.jopt-simple:jopt-simple:$joptVersion" compile("network.bisq.btcd-cli4j:btcd-cli4j-core:$btcdCli4jVersion") { exclude(module: 'slf4j-api') From 4545b01a9e9d449d8230f92d5c08b5b12491d254 Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Tue, 17 Sep 2019 23:40:59 +0100 Subject: [PATCH 13/22] Temporary fix; adding monero-wallet-rpc library source as part of core. --- build.gradle | 10 +- core/src/main/java/common/types/Filter.java | 43 + .../main/java/common/types/HttpException.java | 44 + .../main/java/common/types/JsonException.java | 23 + core/src/main/java/common/types/Pair.java | 37 + .../java/common/utils/FieldDeserializer.java | 72 + .../src/main/java/common/utils/FileUtils.java | 28 + core/src/main/java/common/utils/GenUtils.java | 47 + .../src/main/java/common/utils/JsonUtils.java | 145 ++ .../src/main/java/common/utils/MathUtils.java | 25 + .../main/java/common/utils/StreamUtils.java | 49 + .../main/java/common/utils/StringUtils.java | 19 + .../main/java/monero/daemon/MoneroDaemon.java | 678 +++++ .../monero/daemon/MoneroDaemonDefault.java | 123 + .../java/monero/daemon/MoneroDaemonRpc.java | 1573 ++++++++++++ .../monero/daemon/model/MoneroAltChain.java | 56 + .../java/monero/daemon/model/MoneroBan.java | 47 + .../java/monero/daemon/model/MoneroBlock.java | 276 +++ .../daemon/model/MoneroBlockHeader.java | 368 +++ .../daemon/model/MoneroBlockTemplate.java | 73 + .../daemon/model/MoneroDaemonConnection.java | 165 ++ .../model/MoneroDaemonConnectionSpan.java | 71 + .../monero/daemon/model/MoneroDaemonInfo.java | 266 ++ .../daemon/model/MoneroDaemonListener.java | 27 + .../monero/daemon/model/MoneroDaemonPeer.java | 83 + .../daemon/model/MoneroDaemonSyncInfo.java | 64 + .../model/MoneroDaemonUpdateCheckResult.java | 68 + .../MoneroDaemonUpdateDownloadResult.java | 21 + .../daemon/model/MoneroHardForkInfo.java | 83 + .../monero/daemon/model/MoneroKeyImage.java | 98 + .../model/MoneroKeyImageSpentStatus.java | 19 + .../monero/daemon/model/MoneroMinerTxSum.java | 28 + .../daemon/model/MoneroMiningStatus.java | 57 + .../daemon/model/MoneroNetworkType.java | 10 + .../monero/daemon/model/MoneroOutput.java | 167 ++ .../model/MoneroOutputDistributionEntry.java | 47 + .../model/MoneroOutputHistogramEntry.java | 46 + .../daemon/model/MoneroSubmitTxResult.java | 127 + .../java/monero/daemon/model/MoneroTx.java | 843 +++++++ .../daemon/model/MoneroTxBacklogEntry.java | 8 + .../daemon/model/MoneroTxPoolStats.java | 125 + .../java/monero/rpc/MoneroRpcConnection.java | 296 +++ .../java/monero/rpc/MoneroRpcException.java | 35 + .../java/monero/utils/MoneroCppUtils.java | 82 + .../java/monero/utils/MoneroException.java | 52 + .../main/java/monero/utils/MoneroUtils.java | 312 +++ .../main/java/monero/wallet/MoneroWallet.java | 1161 +++++++++ .../monero/wallet/MoneroWalletDefault.java | 386 +++ .../java/monero/wallet/MoneroWalletJni.java | 1635 ++++++++++++ .../java/monero/wallet/MoneroWalletRpc.java | 2207 +++++++++++++++++ .../monero/wallet/model/MoneroAccount.java | 147 ++ .../monero/wallet/model/MoneroAccountTag.java | 76 + .../wallet/model/MoneroAddressBookEntry.java | 52 + .../java/monero/wallet/model/MoneroCheck.java | 20 + .../wallet/model/MoneroCheckReserve.java | 28 + .../monero/wallet/model/MoneroCheckTx.java | 37 + .../wallet/model/MoneroDestination.java | 90 + .../wallet/model/MoneroIncomingTransfer.java | 143 ++ .../wallet/model/MoneroIntegratedAddress.java | 78 + .../model/MoneroKeyImageImportResult.java | 37 + .../wallet/model/MoneroMultisigInfo.java | 44 + .../model/MoneroMultisigInitResult.java | 28 + .../model/MoneroMultisigSignResult.java | 52 + .../wallet/model/MoneroOutgoingTransfer.java | 173 ++ .../wallet/model/MoneroOutputQuery.java | 164 ++ .../wallet/model/MoneroOutputWallet.java | 175 ++ .../wallet/model/MoneroSendPriority.java | 11 + .../wallet/model/MoneroSendRequest.java | 330 +++ .../monero/wallet/model/MoneroSubaddress.java | 184 ++ .../wallet/model/MoneroSyncListener.java | 18 + .../monero/wallet/model/MoneroSyncResult.java | 35 + .../monero/wallet/model/MoneroTransfer.java | 162 ++ .../wallet/model/MoneroTransferQuery.java | 226 ++ .../monero/wallet/model/MoneroTxQuery.java | 496 ++++ .../java/monero/wallet/model/MoneroTxSet.java | 140 ++ .../monero/wallet/model/MoneroTxWallet.java | 514 ++++ .../wallet/model/MoneroWalletListener.java | 19 + .../wallet/model/MoneroWalletListenerI.java | 28 + 78 files changed, 15825 insertions(+), 7 deletions(-) create mode 100644 core/src/main/java/common/types/Filter.java create mode 100644 core/src/main/java/common/types/HttpException.java create mode 100644 core/src/main/java/common/types/JsonException.java create mode 100644 core/src/main/java/common/types/Pair.java create mode 100644 core/src/main/java/common/utils/FieldDeserializer.java create mode 100644 core/src/main/java/common/utils/FileUtils.java create mode 100644 core/src/main/java/common/utils/GenUtils.java create mode 100644 core/src/main/java/common/utils/JsonUtils.java create mode 100644 core/src/main/java/common/utils/MathUtils.java create mode 100644 core/src/main/java/common/utils/StreamUtils.java create mode 100644 core/src/main/java/common/utils/StringUtils.java create mode 100644 core/src/main/java/monero/daemon/MoneroDaemon.java create mode 100644 core/src/main/java/monero/daemon/MoneroDaemonDefault.java create mode 100644 core/src/main/java/monero/daemon/MoneroDaemonRpc.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroAltChain.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroBan.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroBlock.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroBlockHeader.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroBlockTemplate.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroDaemonConnection.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroDaemonConnectionSpan.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroDaemonInfo.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroDaemonListener.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroDaemonPeer.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroDaemonSyncInfo.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroDaemonUpdateCheckResult.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroDaemonUpdateDownloadResult.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroHardForkInfo.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroKeyImage.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroKeyImageSpentStatus.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroMinerTxSum.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroMiningStatus.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroNetworkType.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroOutput.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroOutputDistributionEntry.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroOutputHistogramEntry.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroSubmitTxResult.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroTx.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroTxBacklogEntry.java create mode 100644 core/src/main/java/monero/daemon/model/MoneroTxPoolStats.java create mode 100644 core/src/main/java/monero/rpc/MoneroRpcConnection.java create mode 100644 core/src/main/java/monero/rpc/MoneroRpcException.java create mode 100644 core/src/main/java/monero/utils/MoneroCppUtils.java create mode 100644 core/src/main/java/monero/utils/MoneroException.java create mode 100644 core/src/main/java/monero/utils/MoneroUtils.java create mode 100644 core/src/main/java/monero/wallet/MoneroWallet.java create mode 100644 core/src/main/java/monero/wallet/MoneroWalletDefault.java create mode 100644 core/src/main/java/monero/wallet/MoneroWalletJni.java create mode 100644 core/src/main/java/monero/wallet/MoneroWalletRpc.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroAccount.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroAccountTag.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroAddressBookEntry.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroCheck.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroCheckReserve.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroCheckTx.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroDestination.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroIncomingTransfer.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroIntegratedAddress.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroKeyImageImportResult.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroMultisigInfo.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroMultisigInitResult.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroMultisigSignResult.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroOutgoingTransfer.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroOutputQuery.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroOutputWallet.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroSendPriority.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroSendRequest.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroSubaddress.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroSyncListener.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroSyncResult.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroTransfer.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroTransferQuery.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroTxQuery.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroTxSet.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroTxWallet.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroWalletListener.java create mode 100644 core/src/main/java/monero/wallet/model/MoneroWalletListenerI.java diff --git a/build.gradle b/build.gradle index 27f2de149c0..c79126f985b 100644 --- a/build.gradle +++ b/build.gradle @@ -69,6 +69,7 @@ configure(subprojects) { reactfxVersion = '2.0-M3' sarxosVersion = '0.3.12' slf4jVersion = '1.7.22' + log4jVersion = '1.2.17' sparkVersion = '2.5.2' springBootVersion = '1.5.10.RELEASE' springVersion = '4.3.6.RELEASE' @@ -77,14 +78,8 @@ configure(subprojects) { } repositories { - mavenLocal() mavenCentral() maven { url 'https://jitpack.io' } - maven { url "http://repo.maven.apache.org/maven2" } - } - - dependencies { - compile 'woodser:monero-wallet-java:0.1.1-SNAPSHOT' } tasks.withType(JavaCompile) { @@ -235,7 +230,8 @@ configure(project(':core')) { dependencies { compile project(':assets') compile project(':p2p') - compile "woodser:monero-wallet-java:0.1.1-SNAPSHOT" + compile "junit:junit:$junitVersion" + compile "log4j:log4j:1.2.17" compile "net.sf.jopt-simple:jopt-simple:$joptVersion" compile("network.bisq.btcd-cli4j:btcd-cli4j-core:$btcdCli4jVersion") { exclude(module: 'slf4j-api') diff --git a/core/src/main/java/common/types/Filter.java b/core/src/main/java/common/types/Filter.java new file mode 100644 index 00000000000..e2a9ceddb57 --- /dev/null +++ b/core/src/main/java/common/types/Filter.java @@ -0,0 +1,43 @@ +package common.types; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Base filter. + */ +public interface Filter { + + /** + * Indicates if the given item meets the criteria of this filter. + * + * @param item is the item to test + * @return true if the item meets the criteria of this filter, false otherwise + */ + public boolean meetsCriteria(T item); + + /** + * Returns a new list comprised of elements from the given list that meet the + * filter's criteria. + * + * @param items are the items to filter + * @return the items that meet this filter's criteria + */ + public static List apply(Filter filter, List items) { + List filtered = new ArrayList(); + for (T item : items) if (filter.meetsCriteria(item)) filtered.add(item); + return filtered; + } + + /** + * Returns a new set comprised of elements from the given set that meet the + * filter's criteria. + * + * @param items are the items to filter + * @return the items that meet this filter's criteria + */ + public static Set apply(Filter filter, Set items) { + throw new RuntimeException("Not implemented"); + } +} diff --git a/core/src/main/java/common/types/HttpException.java b/core/src/main/java/common/types/HttpException.java new file mode 100644 index 00000000000..ca942338c0b --- /dev/null +++ b/core/src/main/java/common/types/HttpException.java @@ -0,0 +1,44 @@ +package common.types; + +/** + * Defines an HTTP exception for when HTTP responses are not in the 200s. + * + * @author woodser + */ +public class HttpException extends RuntimeException { + + private static final long serialVersionUID = -4603832308887633042L; + + private int code; + private String message; + + public HttpException(String message) { + super(message); + this.code = 500; + this.message = message; + } + + public HttpException(String message, Throwable e) { + this(500, message, e); + } + + public HttpException(int code, String message) { + super(message); + this.code = code; + this.message = message; + } + + public HttpException(int code, String message, Throwable e) { + super(message, e); + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } +} diff --git a/core/src/main/java/common/types/JsonException.java b/core/src/main/java/common/types/JsonException.java new file mode 100644 index 00000000000..8b2911219c1 --- /dev/null +++ b/core/src/main/java/common/types/JsonException.java @@ -0,0 +1,23 @@ +package common.types; + +/** + * Represents an exception handling JSON. + * + * @author woodser + */ +public class JsonException extends RuntimeException { + + private static final long serialVersionUID = -5238056297221576735L; + + public JsonException(String msg) { + super(msg); + } + + public JsonException(String msg, Throwable e) { + super(msg, e); + } + + public JsonException(Throwable e) { + super(e); + } +} diff --git a/core/src/main/java/common/types/Pair.java b/core/src/main/java/common/types/Pair.java new file mode 100644 index 00000000000..f94c8ee092e --- /dev/null +++ b/core/src/main/java/common/types/Pair.java @@ -0,0 +1,37 @@ +package common.types; + +/** + * Generic parameterized pair. + * + * @author woodser + * + * @param the type of the first element + * @param the type of the second element + */ +public class Pair { + + private F first; + private S second; + + public Pair(F first, S second) { + super(); + this.first = first; + this.second = second; + } + + public F getFirst() { + return first; + } + + public void setFirst(F first) { + this.first = first; + } + + public S getSecond() { + return second; + } + + public void setSecond(S second) { + this.second = second; + } +} diff --git a/core/src/main/java/common/utils/FieldDeserializer.java b/core/src/main/java/common/utils/FieldDeserializer.java new file mode 100644 index 00000000000..b6333297cee --- /dev/null +++ b/core/src/main/java/common/utils/FieldDeserializer.java @@ -0,0 +1,72 @@ +package common.utils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import common.types.JsonException; + +/** + * Deserializes specific fields to specified types. + * + * TODO: this does not properly deserialize field with e.g. List value + * + * @author woodser + */ +public class FieldDeserializer extends JsonDeserializer> { + + private Map fieldTypes; + + /** + * Constructs the deserializer with the given field names. + * + * @param fieldTypes specifies the names of fields to deserialize to specific types + */ + public FieldDeserializer(Map fieldTypes) { + super(); + this.fieldTypes = fieldTypes; + } + + @Override + public Map deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + Map result = new HashMap(); + jp.nextToken(); + while (!JsonToken.END_OBJECT.equals(jp.getCurrentToken())) { + String tokenText = jp.getText(); + jp.nextToken(); + Object type = fieldTypes.get(tokenText); + if (type != null) { + if (type instanceof Class) result.put(tokenText, jp.readValueAs((Class) type)); + else if (type instanceof TypeReference) result.put(tokenText, jp.readValueAs((TypeReference) type)); + else throw new JsonException("Invalid deserialization type " + type.getClass() + " for field '" + tokenText + "'"); + } else { + + + if (JsonToken.START_OBJECT.equals(jp.getCurrentToken())) { + result.put(tokenText, deserialize(jp, ctxt)); + } else if (JsonToken.START_ARRAY.equals(jp.getCurrentToken())) { + jp.nextToken(); + List list = new ArrayList(); + while (!JsonToken.END_ARRAY.equals(jp.getCurrentToken())) { + list.add(deserialize(jp, ctxt)); + jp.nextToken(); + } + result.put(tokenText, list); + } else { + result.put(tokenText, jp.readValueAs(Object.class)); + } + } + jp.nextToken(); + } + return result; + } +} diff --git a/core/src/main/java/common/utils/FileUtils.java b/core/src/main/java/common/utils/FileUtils.java new file mode 100644 index 00000000000..db503503e21 --- /dev/null +++ b/core/src/main/java/common/utils/FileUtils.java @@ -0,0 +1,28 @@ +package common.utils; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; + +/** + * Collection of utilities for working with files. + * + * @author woodser + */ +public class FileUtils { + + /** + * Writes string data to the given path. + * + * @param path is the path to write the data to + * @param data is the string data to write + * @throws IOException + */ + public static void write(String path, String data) { + try { + org.apache.commons.io.FileUtils.write(new File(path), data, Charset.defaultCharset()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/core/src/main/java/common/utils/GenUtils.java b/core/src/main/java/common/utils/GenUtils.java new file mode 100644 index 00000000000..115c411af0c --- /dev/null +++ b/core/src/main/java/common/utils/GenUtils.java @@ -0,0 +1,47 @@ +package common.utils; + +import java.util.ArrayList; +import java.util.List; + +/** + * Collection of general purpose utilities. + */ +public class GenUtils { + + /** + * Converts a templated array to a list. + * + * @param arr is an array of type T to convert to a list + * @return List is the array converted to a list + */ + public static List arrayToList(T[] arr) { + List list = new ArrayList(arr.length); + for (T elem : arr) list.add(elem); + return list; + } + + /** + * Converts a list of integers to an int array. + * + * @param list is the list ot convert + * @return the int array + */ + public static int[] listToIntArray(List list) { + if (list == null) return null; + int[] ints = new int[list.size()]; + for (int i = 0; i < list.size(); i++) ints[i] = list.get(i); + return ints; + } + + /** + * Returns a string indentation of the given length; + * + * @param length is the length of the indentation + * @returns {string} is an indentation string of the given length + */ + public static String getIndent(int length) { + String str = ""; + for (int i = 0; i < length; i++) str += " "; // two spaces + return str; + } +} diff --git a/core/src/main/java/common/utils/JsonUtils.java b/core/src/main/java/common/utils/JsonUtils.java new file mode 100644 index 00000000000..34676d2d6b5 --- /dev/null +++ b/core/src/main/java/common/utils/JsonUtils.java @@ -0,0 +1,145 @@ +package common.utils; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import common.types.JsonException; + +/** + * Collection of utilities for working with JSON. + * + * @author woodser + */ +public class JsonUtils { + + // set up jackson object mapper + private static final ObjectMapper DEFAULT_MAPPER; + static { + DEFAULT_MAPPER = new ObjectMapper(); + DEFAULT_MAPPER.setSerializationInclusion(Include.NON_NULL); + } + + /** + * Serializes an object to a JSON string. + * + * @param obj is the object to serialize + * @return String is the object serialized to a JSON string + */ + public static String serialize(Object obj) { + return serialize(DEFAULT_MAPPER, obj); + } + + /** + * Serializes an object to a JSON string. + * + * @param mapper is the jackson object mapper to use + * @param obj is the object to serialize + * @return String is the object serialized to a JSON string + */ + public static String serialize(ObjectMapper mapper, Object obj) { + try { + return mapper.writeValueAsString(obj); + } catch (Exception e) { + throw new JsonException("Error serializing object", e); + } + } + + /** + * Deserializes JSON to a specific class. + * + * @param json is the JSON to deserialize + * @param clazz specifies the class to deserialize to + * @return T is the object deserialized from JSON to the given class + */ + public static T deserialize(String json, Class clazz) { + return deserialize(DEFAULT_MAPPER, json, clazz); + } + + /** + * Deserializes JSON to a specific class. + * + * @param mapper is the jackson object mapper to use + * @param json is the JSON to deserialize + * @param clazz specifies the class to deserialize to + * @return T is the object deserialized from JSON to the given class + */ + public static T deserialize(ObjectMapper mapper, String json, Class clazz) { + try { + return mapper.readValue(json, clazz); + } catch (Exception e) { + throw new JsonException("Error deserializing json to class", e); + } + } + + /** + * Deserializes JSON to a parameterized type. + * + * @param json is the JSON to deserialize + * @param type is the parameterized type to deserialize to (e.g. new TypeReference>(){}) + * @return T is the object deserialized from JSON to the given parameterized type + */ + public static T deserialize(String json, TypeReference type) { + return deserialize(DEFAULT_MAPPER, json, type); + } + + /** + * Deserializes JSON to a parameterized type. + * + * @param mapper is the jackson object mapper to use + * @param json is the JSON to deserialize + * @param type is the parameterized type to deserialize to (e.g. new TypeReference>(){}) + * @return T is the object deserialized from JSON to the given parameterized type + */ + public static T deserialize(ObjectMapper mapper, String json, TypeReference type) { + try { + return (T) mapper.readValue(json, type); + } catch (Exception e) { + throw new JsonException("Error deserializing json to type " + type.getType(), e); + } + } + + /** + * Converts a JSON string to a map. + * + * @param json is the string to convert to a map + * @return Map is the json string converted to a map + */ + public static Map toMap(String json) { + return deserialize(DEFAULT_MAPPER, json, new TypeReference>(){}); + } + + /** + * Converts a JSON string to a map. + * + * @param mapper is the jackson object mapper to use + * @param json is the string to convert to a map + * @return Map is the json string converted to a map + */ + public static Map toMap(ObjectMapper mapper, String json) { + return deserialize(mapper, json, new TypeReference>(){}); + } + + /** + * Converts an object to a map. + * + * @param obj is the object to a convert to a map + * @return Map is the object converted to a map + */ + public static Map toMap(Object obj) { + return toMap(DEFAULT_MAPPER, serialize(obj)); + } + + /** + * Converts an object to a map. + * + * @param mapper is the jackson object mapper to use + * @param obj is the object to a convert to a map + * @return Map is the object converted to a map + */ + public static Map toMap(ObjectMapper mapper, Object obj) { + return toMap(mapper, serialize(obj)); + } +} diff --git a/core/src/main/java/common/utils/MathUtils.java b/core/src/main/java/common/utils/MathUtils.java new file mode 100644 index 00000000000..9de609ac911 --- /dev/null +++ b/core/src/main/java/common/utils/MathUtils.java @@ -0,0 +1,25 @@ +package common.utils; + +import static org.junit.Assert.assertTrue; + +import java.util.Random; + +/** + * Collection of math utilities. + */ +public class MathUtils { + + private static Random random = new Random(); + + /** + * Returns a random integer between the given integers, inclusive. + * + * @param start is the start of the range, inclusive + * @param end is the end of the range, inclusive + * @return int is a random integer between start and end, inclusive + */ + public static int random(int start, int end) { + assertTrue(start <= end); + return random.nextInt(end - start) + start; + } +} diff --git a/core/src/main/java/common/utils/StreamUtils.java b/core/src/main/java/common/utils/StreamUtils.java new file mode 100644 index 00000000000..27f7dd8cdf4 --- /dev/null +++ b/core/src/main/java/common/utils/StreamUtils.java @@ -0,0 +1,49 @@ +package common.utils; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.commons.io.IOUtils; + +/** + * Collection of utilities for working with streams. + * + * @author woodser + */ +public class StreamUtils { + + /** + * Converts an input stream to a byte array. + * + * @param is is the input stream + * @return byte[] are the contents of the input stream as a byte array + * @throws IOException + */ + public static byte[] streamToBytes(InputStream is) throws IOException { + byte[] bytes = IOUtils.toByteArray(is); + is.close(); + return bytes; + } + + /** + * Converts a byte array to an input stream. + * + * @param bytes is the byte[] to convert to an input stream + * @return InputStream is the input stream initialized from the byte array + */ + public static InputStream bytesToStream(byte[] bytes) { + return new ByteArrayInputStream(bytes); + } + + /** + * Converts an input stream to a string. + * + * @param is is the input stream to convert to a string + * @return String is the input stream converted to a string + * @throws IOException + */ + public static String streamToString(InputStream is) throws IOException { + return new String(streamToBytes(is)); + } +} diff --git a/core/src/main/java/common/utils/StringUtils.java b/core/src/main/java/common/utils/StringUtils.java new file mode 100644 index 00000000000..ce1dffcc3c1 --- /dev/null +++ b/core/src/main/java/common/utils/StringUtils.java @@ -0,0 +1,19 @@ +package common.utils; + +/** + * Collection of string utilities. + */ +public class StringUtils { + + /** + * Returns the given number of tabs as a string. + * + * @param numTabs is the number of tabs + * @return String is the number of tabs as a string + */ + public static String getTabs(int numTabs) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < numTabs; i++) sb.append("\t"); + return sb.toString(); + } +} diff --git a/core/src/main/java/monero/daemon/MoneroDaemon.java b/core/src/main/java/monero/daemon/MoneroDaemon.java new file mode 100644 index 00000000000..0445c76d41e --- /dev/null +++ b/core/src/main/java/monero/daemon/MoneroDaemon.java @@ -0,0 +1,678 @@ +/** + * Copyright (c) 2017-2019 woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package monero.daemon; + +import java.math.BigInteger; +import java.util.Collection; +import java.util.List; + +import monero.daemon.model.MoneroAltChain; +import monero.daemon.model.MoneroBan; +import monero.daemon.model.MoneroBlock; +import monero.daemon.model.MoneroBlockHeader; +import monero.daemon.model.MoneroBlockTemplate; +import monero.daemon.model.MoneroMinerTxSum; +import monero.daemon.model.MoneroDaemonConnection; +import monero.daemon.model.MoneroDaemonInfo; +import monero.daemon.model.MoneroDaemonListener; +import monero.daemon.model.MoneroDaemonPeer; +import monero.daemon.model.MoneroDaemonSyncInfo; +import monero.daemon.model.MoneroDaemonUpdateCheckResult; +import monero.daemon.model.MoneroDaemonUpdateDownloadResult; +import monero.daemon.model.MoneroHardForkInfo; +import monero.daemon.model.MoneroKeyImageSpentStatus; +import monero.daemon.model.MoneroMiningStatus; +import monero.daemon.model.MoneroNetworkType; +import monero.daemon.model.MoneroOutput; +import monero.daemon.model.MoneroOutputDistributionEntry; +import monero.daemon.model.MoneroOutputHistogramEntry; +import monero.daemon.model.MoneroSubmitTxResult; +import monero.daemon.model.MoneroTx; +import monero.daemon.model.MoneroTxBacklogEntry; +import monero.daemon.model.MoneroTxPoolStats; + +/** + * Monero daemon interface. + */ +public interface MoneroDaemon { + + /** + * Indicates if the daemon is trusted or untrusted. + * + * @return true if the daemon is trusted, false otherwise + */ + public boolean isTrusted(); + + /** + * Get the number of blocks in the longest chain known to the node. + * + * @return the number of blocks + */ + public long getHeight(); + + /** + * Get a block's id by its height. + * + * @param height is the height of the block id to get + * @return the block's id at the given height + */ + public String getBlockId(long height); + + /** + * Get a block template for mining a new block. + * + * @param walletAddress is the address of the wallet to receive miner transactions if block is successfully mined + * @return a block template for mining a new block + */ + public MoneroBlockTemplate getBlockTemplate(String walletAddress); + + /** + * Get a block template for mining a new block. + * + * @param walletAddress is the address of the wallet to receive miner transactions if block is successfully mined + * @param reserveSize is the reserve size (optional) + * @return a block template for mining a new block + */ + public MoneroBlockTemplate getBlockTemplate(String walletAddress, Integer reserveSize); + + /** + * Get the last block's header. + * + * @return the last block's header + */ + public MoneroBlockHeader getLastBlockHeader(); + + /** + * Get a block header by its id. + * + * @param blockId is the id of the block to get the header of + * @return the block's header + */ + public MoneroBlockHeader getBlockHeaderById(String blockId); + + /** + * Get a block header by its height. + * + * @param height is the height of the block to get the header of + * @return the block's header + */ + public MoneroBlockHeader getBlockHeaderByHeight(long height); + + /** + * Get block headers for the given range. + * + * @param startHeight is the start height lower bound inclusive (optional) + * @param endHeight is the end height upper bound inclusive (optional) + * @return block headers in the given range + */ + public List getBlockHeadersByRange(Long startHeight, Long endHeight); + + /** + * Get a block by id. + * + * @param blockId is the id of the block to get + * @return the block with the given id + */ + public MoneroBlock getBlockById(String blockId); + + /** + * Get blocks by id. + * + * @param blockIds are array of hashes; first 10 blocks id goes sequential, + * next goes in pow(2,n) offset, like 2, 4, 8, 16, 32, 64 and so on, + * and the last one is always genesis block + * @param startHeight is the start height to get blocks by id + * @param prune specifies if returned blocks should be pruned (defaults to false) // TODO: test default + * @return the retrieved blocks + */ + public List getBlocksById(List blockIds, Long startHeight, Boolean prune); + + /** + * Get a block by height. + * + * @param height is the height of the block to get + * @return the block at the given height + */ + public MoneroBlock getBlockByHeight(long height); + + /** + * Get blocks at the given heights. + * + * @param heights are the heights of the blocks to get + * @return blocks at the given heights + */ + public List getBlocksByHeight(List heights); + + /** + * Get blocks in the given height range. + * + * @param startHeight is the start height lower bound inclusive (optional) + * @param endHeight is the end height upper bound inclusive (optional) + * @return blocks in the given height range + */ + public List getBlocksByRange(Long startHeight, Long endHeight); + + /** + * Get blocks in the given height range as chunked requests so that each request is + * not too big. + * + * @param startHeight is the start height lower bound inclusive (optional) + * @param endHeight is the end height upper bound inclusive (optional) + * @return blocks in the given height range + */ + public List getBlocksByRangeChunked(Long startHeight, Long endHeight); + + /** + * Get blocks in the given height range as chunked requests so that each request is + * not too big. + * + * @param startHeight is the start height lower bound inclusive (optional) + * @param endHeight is the end height upper bound inclusive (optional) + * @param maxChunkSize is the maximum chunk size in any one request (default 3,000,000 bytes) + * @return blocks in the given height range + */ + public List getBlocksByRangeChunked(Long startHeight, Long endHeight, Long maxChunkSize); + + /** + * Get block ids as a binary request to the daemon. + * + * @param blockIds specify block ids to fetch; first 10 blocks id goes + * sequential, next goes in pow(2,n) offset, like 2, 4, 8, 16, 32, 64 + * and so on, and the last one is always genesis block + * @param startHeight is the starting height of block ids to return + * @return the requested block ids + */ + public List getBlockIds(List blockIds, Long startHeight); + + /** + * Get a transaction by id. + * + * @param txId is the id of the transaction to get + * @return the transaction with the given id + */ + public MoneroTx getTx(String txId); + + /** + * Get a transaction by id. + * + * @param txId is the id of the transaction to get + * @param prune specifies if the returned tx should be pruned (defaults to false) + * @return the transaction with the given id + */ + public MoneroTx getTx(String txId, Boolean prune); + + /** + * Get transactions by ids. + * + * @param txIds are ids of transactions to get + * @return the transactions with the given ids + */ + public List getTxs(Collection txIds); + + /** + * Get transactions by ids. + * + * @param txIds are ids of transactions to get + * @param prune specifies if the returned txs should be pruned (defaults to false) + * @return the transactions with the given ids + */ + public List getTxs(Collection txIds, Boolean prune); + + /** + * Get a transaction hex by id. + * + * @param txId is the id of the transaction to get hex from + * @return the tx hex with the given id + */ + public String getTxHex(String txId); + + /** + * Get a transaction hex by id. + * + * @param txId is the id of the transaction to get hex from + * @param prune specifies if the returned tx hex should be pruned (defaults to false) + * @return the tx hex with the given id + */ + public String getTxHex(String txId, Boolean prune); + + /** + * Get transaction hexes by ids. + * + * @param txIds are ids of transactions to get hexes from + * @return are the tx hexes + */ + public List getTxHexes(Collection txIds); + + /** + * Get transaction hexes by ids. + * + * @param txIds are ids of transactions to get hexes from + * @param prune specifies if the returned tx hexes should be pruned (defaults to false) + * @return are the tx hexes + */ + public List getTxHexes(Collection txIds, Boolean prune); + + /** + * Gets the total emissions and fees from the genesis block to the current height. + * + * @param height is the height to start computing the miner sum + * @param numBlocks are the number of blocks to include in the sum + * @return the sum emission and fees since the geneis block + */ + public MoneroMinerTxSum getMinerTxSum(long height, Long numBlocks); + + /** + * Get the fee estimate per kB. + * + * @return is the fee estimate per kB. + */ + public BigInteger getFeeEstimate(); + + /** + * Get the fee estimate per kB. + * + * @param graceBlocks TODO + * @return is the fee estimate per kB. + */ + public BigInteger getFeeEstimate(Integer graceBlocks); + + /** + * Submits a transaction to the daemon's pool. + * + * @param txHex is the raw transaction hex to submit + * @return the submission results + */ + public MoneroSubmitTxResult submitTxHex(String txHex); + + /** + * Submits a transaction to the daemon's pool. + * + * @param txHex is the raw transaction hex to submit + * @param doNotRelay specifies if the tx should be relayed (optional) + * @return the submission results + */ + public MoneroSubmitTxResult submitTxHex(String txHex, Boolean doNotRelay); + + /** + * Relays a transaction by id. + * + * @param txId identifies the transaction to relay + */ + public void relayTxById(String txId); + + /** + * Relays transactions by id. + * + * @param txIds identify the transactions to relay + */ + public void relayTxsById(Collection txIds); + + /** + * Get valid transactions seen by the node but not yet mined into a block, as well + * as spent key image information for the tx pool. + * + * @return transactions in the transaction pool + */ + public List getTxPool(); + + /** + * Get ids of transactions in the transaction pool. + * + * @return ids of transactions in the transaction pool + */ + public List getTxPoolIds(); + + /** + * Get all transaction pool backlog. + * + * @return transaction pool backlog entries + */ + public List getTxPoolBacklog(); + + /** + * Get transaction pool statistics. + * + * @return statistics about the transaction pool + */ + public MoneroTxPoolStats getTxPoolStats(); + + /** + * Flushes all transactions from the tx pool. + */ + public void flushTxPool(); + + /** + * Flush transactions from the tx pool. + * + * @param ids are ids of transactions to flush + */ + public void flushTxPool(String... ids); + + /** + * Flush transactions from the tx pool. + * + * @param ids are ids of transactions to flush + */ + public void flushTxPool(Collection ids); + + /** + * Get the spent status of the given key image. + * + * @param keyImage is key image hex to get the status of + * @return the status of the key image + */ + public MoneroKeyImageSpentStatus getKeyImageSpentStatus(String keyImage); + + /** + * Get the spent status of each given key image. + * + * @param keyImages are hex key images to get the statuses of + * @return the spent status for each key image + */ + public List getKeyImageSpentStatuses(Collection keyImages); + + /** + * Get outputs identified by a list of output amounts and indices as a binary + * request. + * + * @param outputs identify each output by amount and index + * @return the identified outputs + */ + public List getOutputs(Collection outputs); + + /** + * Get a histogram of output amounts. For all amounts (possibly filtered by + * parameters), gives the number of outputs on the chain for that amount. + * RingCT outputs counts as 0 amount. + * + * @param amounts are amounts of outputs to make the histogram with + * @param minCount TODO + * @param maxCount TODO + * @param isUnlocked makes a histogram with outputs with the specified lock state + * @param recentCutoff TODO + * @return output histogram entries meeting the parameters + */ + public List getOutputHistogram(Collection amounts, Integer minCount, Integer maxCount, Boolean isUnlocked, Integer recentCutoff); + + /** + * Creates an output distribution. + * + * @param amounts are amounts of outputs to make the distribution with + * @return output distribution entries meeting the parameters + */ + public List getOutputDistribution(Collection amounts); + + /** + * Creates an output distribution. + * + * @param amounts are amounts of outputs to make the distribution with + * @param isCumulative specifies if the results should be cumulative (defaults to TODO) + * @param startHeight is the start height lower bound inclusive (optional) + * @param endHeight is the end height upper bound inclusive (optional) + * @return output distribution entries meeting the parameters + */ + public List getOutputDistribution(Collection amounts, Boolean isCumulative, Long startHeight, Long endHeight); + + /** + * Get general information about the state of the node and the network. + * + * @return general information about the node and network + */ + public MoneroDaemonInfo getInfo(); + + /** + * Get synchronization information. + * + * @return contains sync information + */ + public MoneroDaemonSyncInfo getSyncInfo(); + + /** + * Look up information regarding hard fork voting and readiness. + * + * @return hard fork information + */ + public MoneroHardForkInfo getHardForkInfo(); + + /** + * Get alternative chains seen by the node. + * + * @return alternative chains seen by the node + */ + public List getAltChains(); + + /** + * Get known block ids which are not on the main chain. + * + * @return known block ids which are not on the main chain + */ + public List getAltBlockIds(); + +// /** +// * Get the daemon's current download and upload bandwidth limits. +// * +// * @return MoneroDaemonBandwidthLimits contains the current upload and download bandwidth limits +// */ +// public MoneroDaemonBandwidthLimits getBandwidthLimits(); +// +// /** +// * Set the daemon's current download and upload bandwidth limits. +// * +// * @param downloadLimit is the download limit to set (-1 to reset to default, 0 or null to make no change) +// * @param uploadLimit is the upload limit to set (-1 to reset to default, 0 or null to make no change) +// * @return MoneroDaemonBandwidthLimits are the daemon's bandwidth limits after setting +// */ +// public MoneroDaemonBandwidthLimits setBandwidthLimits(Integer downloadLimit, Integer uploadLimit); + + /** + * Get the download bandwidth limit. + * + * @return is the download bandwidth limit + */ + public int getDownloadLimit(); + + /** + * Set the download bandwidth limit. + * + * @param limit is the download limit to set (-1 to reset to default) + * @return int is the new download limit after setting + */ + public int setDownloadLimit(int limit); + + /** + * Reset the download bandwidth limit. + * + * @return the download bandwidth limit after resetting + */ + public int resetDownloadLimit(); + + /** + * Get the upload bandwidth limit. + * + * @return is the upload bandwidth limit + */ + public int getUploadLimit(); + + /** + * Set the upload bandwidth limit. + * + * @param limit is the upload limit to set (-1 to reset to default) + * @return int is the new upload limit after setting + */ + public int setUploadLimit(int limit); + + /** + * Reset the upload bandwidth limit. + * + * @return the upload bandwidth limit after resetting + */ + public int resetUploadLimit(); + + /** + * Get known peers including their last known online status. + * + * @return known peers + */ + public List getKnownPeers(); + + /** + * Get incoming and outgoing connections to the node. + * + * @return the daemon's peer connections + */ + public List getConnections(); + + /** + * Limit number of outgoing peers. + * + * @param limit is the maximum number of outgoing peers + */ + public void setOutgoingPeerLimit(int limit); + + /** + * Limit number of incoming peers. + * + * @param limit is the maximum number of incoming peers + */ + public void setIncomingPeerLimit(int limit); + + /** + * Get peer bans. + * + * @return entries about banned peers + */ + public List getPeerBans(); + + /** + * Ban a peer node. + * + * @param ban contains information about a node to ban + */ + public void setPeerBan(MoneroBan ban); + + /** + * Ban peers nodes. + * + * @param bans are bans to apply against peer nodes + */ + public void setPeerBans(List bans); + + /** + * Start mining. + * + * @param address is the address given miner rewards if the daemon mines a block + * @param numThreads is the number of mining threads to run + * @param isBackground specifies if the miner should run in the background or not + * @param ignoreBattery specifies if the battery state (e.g. on laptop) should be ignored or not + */ + public void startMining(String address, Long numThreads, Boolean isBackground, Boolean ignoreBattery); + + /** + * Stop mining. + */ + public void stopMining(); + + /** + * Get the daemon's mining status. + * + * @return the daemon's mining status + */ + public MoneroMiningStatus getMiningStatus(); + + /** + * Submit a mined block to the network. + * + * @param blockBlob is the mined block to submit + */ + public void submitBlock(String blockBlob); + + /** + * Submit mined blocks to the network. + * + * @param blockBlobs are the mined blocks to submit + */ + public void submitBlocks(Collection blockBlobs); + + /** + * Check for update. + * + * @return the result of the update check + */ + public MoneroDaemonUpdateCheckResult checkForUpdate(); + + /** + * Download an update. + * + * @return the result of the update download + */ + public MoneroDaemonUpdateDownloadResult downloadUpdate(); + + /** + * Download an update. + * + * @param path is the path to download the update (optional) + * @return the result of the update download + */ + public MoneroDaemonUpdateDownloadResult downloadUpdate(String path); + + /** + * Safely disconnect and shut down the daemon. + */ + public void stop(); + + /** + * Get the header of the next block added to the chain. + * + * @return the header of the next block added to the chain + */ + public MoneroBlockHeader getNextBlockHeader(); + + /** + * Register a listener to be notified when blocks are added to the chain. + * + * @param listener is invoked when blocks are added to the chain + */ + public void addListener(MoneroDaemonListener listener); + + /** + * Unregister a listener to be notified when blocks are added to the chain. + * + * @param listener is a previously registered listener to be unregistered + */ + public void removeListener(MoneroDaemonListener listener); + + // ----------------------------- STATIC UTILITIES --------------------------- + + /** + * Parses a network string to an enumerated type. + * + * @param network is the network string to parse + * @return the enumerated network type + */ + public static MoneroNetworkType parseNetworkType(String network) { + if ("mainnet".equals(network)) return MoneroNetworkType.MAINNET; + if ("testnet".equals(network)) return MoneroNetworkType.TESTNET; + if ("stagenet".equals(network)) return MoneroNetworkType.STAGENET; + throw new Error("Invalid network type to parse: " + network); + } +} \ No newline at end of file diff --git a/core/src/main/java/monero/daemon/MoneroDaemonDefault.java b/core/src/main/java/monero/daemon/MoneroDaemonDefault.java new file mode 100644 index 00000000000..cc1738b5734 --- /dev/null +++ b/core/src/main/java/monero/daemon/MoneroDaemonDefault.java @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2017-2019 woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package monero.daemon; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import monero.daemon.model.MoneroBan; +import monero.daemon.model.MoneroBlock; +import monero.daemon.model.MoneroBlockTemplate; +import monero.daemon.model.MoneroDaemonUpdateDownloadResult; +import monero.daemon.model.MoneroKeyImageSpentStatus; +import monero.daemon.model.MoneroOutputDistributionEntry; +import monero.daemon.model.MoneroSubmitTxResult; +import monero.daemon.model.MoneroTx; + +/** + * Default Monero daemon implementation. + */ +public abstract class MoneroDaemonDefault implements MoneroDaemon { + + @Override + public MoneroBlockTemplate getBlockTemplate(String walletAddress) { + return getBlockTemplate(walletAddress, null); + } + + @Override + public List getBlocksByRangeChunked(Long startHeight, Long endHeight) { + return getBlocksByRangeChunked(startHeight, endHeight, null); + } + + @Override + public MoneroTx getTx(String txId) { + return getTx(txId, null); + } + + @Override + public MoneroTx getTx(String txId, Boolean prune) { + return getTxs(Arrays.asList(txId), prune).get(0); + } + + @Override + public List getTxs(Collection txIds) { + return getTxs(txIds, null); + } + + @Override + public String getTxHex(String txId) { + return getTxHex(txId, false); + } + + @Override + public String getTxHex(String txId, Boolean prune) { + return getTxHexes(Arrays.asList(txId), prune).get(0); + } + + @Override + public List getTxHexes(Collection txIds) { + return getTxHexes(txIds, null); + } + + @Override + public BigInteger getFeeEstimate() { + return getFeeEstimate(null); + } + + @Override + public MoneroSubmitTxResult submitTxHex(String txHex) { + return submitTxHex(txHex, false); + } + + @Override + public void relayTxById(String txId) { + relayTxsById(Arrays.asList(txId)); + } + + @Override + public MoneroKeyImageSpentStatus getKeyImageSpentStatus(String keyImage) { + return getKeyImageSpentStatuses(Arrays.asList(keyImage)).get(0); + } + + @Override + public List getOutputDistribution(Collection amounts) { + return getOutputDistribution(amounts, null, null, null); + } + + @Override + public void setPeerBan(MoneroBan ban) { + setPeerBans(Arrays.asList(ban)); + } + + @Override + public void submitBlock(String blockBlob) { + submitBlocks(Arrays.asList(blockBlob)); + } + + @Override + public MoneroDaemonUpdateDownloadResult downloadUpdate() { + return downloadUpdate(null); + } +} diff --git a/core/src/main/java/monero/daemon/MoneroDaemonRpc.java b/core/src/main/java/monero/daemon/MoneroDaemonRpc.java new file mode 100644 index 00000000000..6d4732a3e4d --- /dev/null +++ b/core/src/main/java/monero/daemon/MoneroDaemonRpc.java @@ -0,0 +1,1573 @@ +/** + * Copyright (c) 2017-2019 woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package monero.daemon; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.math.BigInteger; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.log4j.Logger; + +import com.fasterxml.jackson.core.type.TypeReference; + +import common.utils.GenUtils; +import common.utils.JsonUtils; +import monero.daemon.model.MoneroAltChain; +import monero.daemon.model.MoneroBan; +import monero.daemon.model.MoneroBlock; +import monero.daemon.model.MoneroBlockHeader; +import monero.daemon.model.MoneroBlockTemplate; +import monero.daemon.model.MoneroDaemonConnection; +import monero.daemon.model.MoneroDaemonConnectionSpan; +import monero.daemon.model.MoneroDaemonInfo; +import monero.daemon.model.MoneroDaemonListener; +import monero.daemon.model.MoneroDaemonPeer; +import monero.daemon.model.MoneroDaemonSyncInfo; +import monero.daemon.model.MoneroDaemonUpdateCheckResult; +import monero.daemon.model.MoneroDaemonUpdateDownloadResult; +import monero.daemon.model.MoneroHardForkInfo; +import monero.daemon.model.MoneroKeyImage; +import monero.daemon.model.MoneroKeyImageSpentStatus; +import monero.daemon.model.MoneroMinerTxSum; +import monero.daemon.model.MoneroMiningStatus; +import monero.daemon.model.MoneroNetworkType; +import monero.daemon.model.MoneroOutput; +import monero.daemon.model.MoneroOutputDistributionEntry; +import monero.daemon.model.MoneroOutputHistogramEntry; +import monero.daemon.model.MoneroSubmitTxResult; +import monero.daemon.model.MoneroTx; +import monero.daemon.model.MoneroTxBacklogEntry; +import monero.daemon.model.MoneroTxPoolStats; +import monero.rpc.MoneroRpcConnection; +import monero.rpc.MoneroRpcException; +import monero.utils.MoneroCppUtils; +import monero.utils.MoneroException; +import monero.utils.MoneroUtils; + +/** + * Implements a Monero daemon using monero-daemon-rpc. + * + * TODO: every call needs to checkResponseStatus + */ +public class MoneroDaemonRpc extends MoneroDaemonDefault { + + // static variables + private static final Logger LOGGER = Logger.getLogger(MoneroDaemonRpc.class); + private static final String DEFAULT_ID = "0000000000000000000000000000000000000000000000000000000000000000"; + private static long MAX_REQ_SIZE = 3000000; // max request size when fetching blocks from daemon + private static int NUM_HEADERS_PER_REQ = 750; + + // instance variables + private MoneroRpcConnection rpc; + private MoneroDaemonPoller daemonPoller; + private Map cachedHeaders; + + public MoneroDaemonRpc(URI uri) { + this(new MoneroRpcConnection(uri)); + } + + public MoneroDaemonRpc(String uri) { + this(new MoneroRpcConnection(uri)); + } + + public MoneroDaemonRpc(String uri, String username, String password) { + this(new MoneroRpcConnection(uri, username, password)); + } + + public MoneroDaemonRpc(URI uri, String username, String password) { + this(new MoneroRpcConnection(uri, username, password)); + } + + public MoneroDaemonRpc(MoneroRpcConnection rpc) { + assertNotNull(rpc); + this.rpc = rpc; + this.daemonPoller = new MoneroDaemonPoller(this); + this.cachedHeaders = new HashMap(); + } + + /** + * Get the daemon's RPC connection. + * + * @return the daemon's rpc connection + */ + public MoneroRpcConnection getRpcConnection() { + return this.rpc; + } + + /** + * Indicates if the client is connected to the daemon via RPC. + * + * @return true if the client is connected to the daemon, false otherwise + */ + public boolean isConnected() { + try { + getHeight(); + return true; + } catch (MoneroException e) { + return false; + } + } + + @Override + public boolean isTrusted() { + Map resp = rpc.sendPathRequest("get_height"); + checkResponseStatus(resp); + return !(boolean) resp.get("untrusted"); + } + + @SuppressWarnings("unchecked") + @Override + public long getHeight() { + Map respMap = rpc.sendJsonRequest("get_block_count"); + Map resultMap = (Map) respMap.get("result"); + return ((BigInteger) resultMap.get("count")).intValue(); + } + + @Override + public String getBlockId(long height) { + Map respMap = rpc.sendJsonRequest("on_get_block_hash", Arrays.asList(height)); + return (String) respMap.get("result"); + } + + @SuppressWarnings("unchecked") + @Override + public MoneroBlockTemplate getBlockTemplate(String walletAddress, Integer reserveSize) { + Map params = new HashMap(); + params.put("wallet_address", walletAddress); + params.put("reserve_size", reserveSize); + Map respMap = rpc.sendJsonRequest("get_block_template", params); + Map resultMap = (Map) respMap.get("result"); + MoneroBlockTemplate template = convertRpcBlockTemplate(resultMap); + return template; + } + + @SuppressWarnings("unchecked") + @Override + public MoneroBlockHeader getLastBlockHeader() { + Map respMap = rpc.sendJsonRequest("get_last_block_header"); + Map resultMap = (Map) respMap.get("result"); + MoneroBlockHeader header = convertRpcBlockHeader((Map) resultMap.get("block_header")); + return header; + } + + @SuppressWarnings("unchecked") + @Override + public MoneroBlockHeader getBlockHeaderById(String blockId) { + Map params = new HashMap(); + params.put("hash", blockId); + Map respMap = rpc.sendJsonRequest("get_block_header_by_hash", params); + Map resultMap = (Map) respMap.get("result"); + MoneroBlockHeader header = convertRpcBlockHeader((Map) resultMap.get("block_header")); + return header; + } + + @SuppressWarnings("unchecked") + @Override + public MoneroBlockHeader getBlockHeaderByHeight(long height) { + Map params = new HashMap(); + params.put("height", height); + Map respMap = rpc.sendJsonRequest("get_block_header_by_height", params); + Map resultMap = (Map) respMap.get("result"); + MoneroBlockHeader header = convertRpcBlockHeader((Map) resultMap.get("block_header")); + return header; + } + + @SuppressWarnings("unchecked") + @Override + public List getBlockHeadersByRange(Long startHeight, Long endHeight) { + Map params = new HashMap(); + params.put("start_height", startHeight); + params.put("end_height", endHeight); + Map respMap = rpc.sendJsonRequest("get_block_headers_range", params); + Map resultMap = (Map) respMap.get("result"); + List> rpcHeaders = (List>) resultMap.get("headers"); + List headers = new ArrayList(); + for (Map rpcHeader : rpcHeaders) { + MoneroBlockHeader header = convertRpcBlockHeader(rpcHeader); + headers.add(header); + } + return headers; + } + + @SuppressWarnings("unchecked") + @Override + public MoneroBlock getBlockById(String blockId) { + Map params = new HashMap(); + params.put("hash", blockId); + Map respMap = rpc.sendJsonRequest("get_block", params); + Map resultMap = (Map) respMap.get("result"); + MoneroBlock block = convertRpcBlock(resultMap); + return block; + } + + @Override + public List getBlocksById(List blockIds, Long startHeight, Boolean prune) { + throw new RuntimeException("Not implemented"); + } + + @SuppressWarnings("unchecked") + @Override + public MoneroBlock getBlockByHeight(long height) { + Map params = new HashMap(); + params.put("height", height); + Map respMap = rpc.sendJsonRequest("get_block", params); + Map rpcBlock = (Map) respMap.get("result"); + MoneroBlock block = convertRpcBlock((Map) rpcBlock); + return block; + } + + @SuppressWarnings({ "unchecked" }) + @Override + public List getBlocksByHeight(List heights) { + + // fetch blocks in binary + Map params = new HashMap(); + params.put("heights", heights); + byte[] respBin = rpc.sendBinaryRequest("get_blocks_by_height.bin", params); + + // convert binary blocks to map + Map rpcResp = MoneroCppUtils.binaryBlocksToMap(respBin); + checkResponseStatus(rpcResp); + + // build blocks with transactions + List blocks = new ArrayList(); + List> rpcBlocks = (List>) rpcResp.get("blocks"); + List>> rpcTxs = (List>>) rpcResp.get("txs"); + assertEquals(rpcBlocks.size(), rpcTxs.size()); + for (int blockIdx = 0; blockIdx < rpcBlocks.size(); blockIdx++) { + + // build block + MoneroBlock block = convertRpcBlock(rpcBlocks.get(blockIdx)); + block.setHeight(heights.get(blockIdx)); + blocks.add(block); + + // build transactions + List txs = new ArrayList(); + for (int txIdx = 0; txIdx < rpcTxs.get(blockIdx).size(); txIdx++) { + MoneroTx tx = new MoneroTx(); + txs.add(tx); + List txIds = (List) rpcBlocks.get(blockIdx).get("tx_hashes"); + tx.setId(txIds.get(txIdx)); + tx.setIsConfirmed(true); + tx.setInTxPool(false); + tx.setIsMinerTx(false); + tx.setDoNotRelay(false); + tx.setIsRelayed(true); + tx.setIsFailed(false); + tx.setIsDoubleSpendSeen(false); + List> blockTxs = (List>) rpcTxs.get(blockIdx); + convertRpcTx(blockTxs.get(txIdx), tx); + } + + // merge into one block + block.setTxs(new ArrayList()); + for (MoneroTx tx : txs) { + if (tx.getBlock() != null) block.merge(tx.getBlock()); + else block.getTxs().add(tx.setBlock(block)); + } + } + + return blocks; + } + + @Override + public List getBlocksByRange(Long startHeight, Long endHeight) { + if (startHeight == null) startHeight = 0l; + if (endHeight == null) endHeight = getHeight() - 1; + List heights = new ArrayList(); + for (long height = startHeight; height <= endHeight; height++) heights.add(height); + return getBlocksByHeight(heights); + } + + @Override + public List getBlocksByRangeChunked(Long startHeight, Long endHeight, Long maxChunkSize) { + if (startHeight == null) startHeight = 0l; + if (endHeight == null) endHeight = getHeight() - 1; + long lastHeight = startHeight - 1; + List blocks = new ArrayList(); + while (lastHeight < endHeight) { + blocks.addAll(getMaxBlocks(lastHeight + 1, endHeight, maxChunkSize)); + lastHeight = blocks.get(blocks.size() - 1).getHeight(); + } + return blocks; + } + + @Override + public List getBlockIds(List blockIds, Long startHeight) { + throw new RuntimeException("Not implemented"); + } + + @SuppressWarnings("unchecked") + @Override + public List getTxs(Collection txIds, Boolean prune) { + + // validate input + if (txIds.isEmpty()) throw new MoneroException("Must provide an array of transaction ids"); + + // fetch transactions + Map params = new HashMap(); + params.put("txs_hashes", txIds); + params.put("decode_as_json", true); + params.put("prune", prune); + Map respMap = rpc.sendPathRequest("get_transactions", params); + try { + checkResponseStatus(respMap); + } catch (MoneroException e) { + if (e.getMessage().indexOf("Failed to parse hex representation of transaction hash") >= 0) throw new MoneroException("Invalid transaction id", e.getCode()); + throw e; + } + + // interpret response + List> rpcTxs = (List>) respMap.get("txs"); + + // build transaction models + List txs = new ArrayList(); + if (rpcTxs != null) { + for (int i = 0; i < rpcTxs.size(); i++) { + MoneroTx tx = new MoneroTx(); + tx.setIsMinerTx(false); + txs.add(convertRpcTx(rpcTxs.get(i), tx)); + } + } + + // fetch unconfirmed txs from pool and merge additional fields // TODO monero-daemon-rpc: merge rpc calls so this isn't necessary? + //System.out.println("Fetching from pool..."); // TODO monero core: getTxPool() can get stuck under certain conditions (observed it before coordinating tx pool as part of tests, so double spend related?) + List poolTxs = getTxPool(); + for (MoneroTx tx : txs) { + for (MoneroTx poolTx : poolTxs) { + if (tx.getId().equals(poolTx.getId())) tx.merge(poolTx); + } + } + + return txs; + } + + @Override + public List getTxHexes(Collection txIds, Boolean prune) { + List hexes = new ArrayList(); + for (MoneroTx tx : getTxs(txIds, prune)) hexes.add(Boolean.TRUE.equals(prune) ? tx.getPrunedHex() : tx.getFullHex()); + return hexes; + } + + @SuppressWarnings("unchecked") + @Override + public MoneroMinerTxSum getMinerTxSum(long height, Long numBlocks) { + assertTrue("Height must be an integer >= 0", height >= 0); + if (numBlocks == null) numBlocks = getHeight(); + else assertTrue("Count must be an integer >= 0", numBlocks >= 0); + Map params = new HashMap(); + params.put("height", height); + params.put("count", numBlocks); + Map respMap = rpc.sendJsonRequest("get_coinbase_tx_sum", params); + Map resultMap = (Map) respMap.get("result"); + checkResponseStatus(resultMap); + MoneroMinerTxSum txSum = new MoneroMinerTxSum(); + txSum.setEmissionSum((BigInteger) resultMap.get("emission_amount")); + txSum.setFeeSum((BigInteger) resultMap.get("fee_amount")); + return txSum; + } + + @SuppressWarnings("unchecked") + @Override + public BigInteger getFeeEstimate(Integer graceBlocks) { + Map resp = rpc.sendJsonRequest("get_fee_estimate"); + Map result = (Map) resp.get("result"); + checkResponseStatus(result); + return (BigInteger) result.get("fee"); + } + + @Override + public MoneroSubmitTxResult submitTxHex(String txHex, Boolean doNotRelay) { + Map params = new HashMap(); + params.put("tx_as_hex", txHex); + params.put("do_not_relay", doNotRelay); + Map resp = rpc.sendPathRequest("send_raw_transaction", params); + MoneroSubmitTxResult submitResult = convertRpcSubmitTxResult(resp); + + // set isGood based on status + try { + checkResponseStatus(resp); + submitResult.setIsGood(true); + } catch (MoneroException e) { + submitResult.setIsGood(false); + } + return submitResult; + } + + @SuppressWarnings("unchecked") + @Override + public void relayTxsById(Collection txIds) { + Map params = new HashMap(); + params.put("txids", txIds); + Map resp = rpc.sendJsonRequest("relay_tx", params); + checkResponseStatus((Map) resp.get("result")); + } + + @SuppressWarnings("unchecked") + @Override + public List getTxPool() { + + + // send rpc request + Map resp = rpc.sendPathRequest("get_transaction_pool"); + checkResponseStatus(resp); + + // build txs + List txs = new ArrayList(); + if (resp.containsKey("transactions")) { + for (Map rpcTx : (List>) resp.get("transactions")) { + MoneroTx tx = new MoneroTx(); + txs.add(tx); + tx.setIsConfirmed(false); + tx.setIsMinerTx(false); + tx.setInTxPool(true); + tx.setNumConfirmations(0l); + convertRpcTx(rpcTx, tx); + } + } + + return txs; + } + + @Override + public List getTxPoolIds() { + throw new RuntimeException("Not implemented"); + } + + @Override + public List getTxPoolBacklog() { + throw new RuntimeException("Not implemented"); + } + + @Override + public MoneroTxPoolStats getTxPoolStats() { + throw new MoneroException("Response contains field 'histo' which is binary'"); +// let resp = await this.config.rpc.sendPathRequest("get_transaction_pool_stats"); +// MoneroDaemonRpc._checkResponseStatus(resp); +// let stats = MoneroDaemonRpc._convertRpcTxPoolStats(resp.pool_stats); +// +// // uninitialize some stats if not applicable +// if (stats.getHisto98pc() === 0) stats.setHisto98pc(undefined); +// if (stats.getNumTxs() === 0) { +// stats.setBytesMin(undefined); +// stats.setBytesMed(undefined); +// stats.setBytesMax(undefined); +// stats.setHisto98pc(undefined); +// stats.setOldestTimestamp(undefined); +// } +// +// return stats; + } + + @Override + public void flushTxPool() { + flushTxPool(new String[0]); + } + + @SuppressWarnings("unchecked") + @Override + public void flushTxPool(String... ids) { + Map params = new HashMap(); + params.put("txids", ids); + Map resp = rpc.sendJsonRequest("flush_txpool", params); + checkResponseStatus((Map) resp.get("result")); + } + + @Override + public void flushTxPool(Collection ids) { + flushTxPool(ids.toArray(new String[0])); + } + + @SuppressWarnings("unchecked") + @Override + public List getKeyImageSpentStatuses(Collection keyImages) { + if (keyImages == null || keyImages.isEmpty()) throw new MoneroException("Must provide key images to check the status of"); + Map params = new HashMap(); + params.put("key_images", keyImages); + Map resp = rpc.sendPathRequest("is_key_image_spent", params); + checkResponseStatus(resp); + List statuses = new ArrayList(); + for (BigInteger bi : (List) resp.get("spent_status")) { + statuses.add(MoneroKeyImageSpentStatus.valueOf(bi.intValue())); + } + return statuses; + } + + @Override + public List getOutputs(Collection outputs) { + throw new RuntimeException("Not implemented"); + } + + @SuppressWarnings("unchecked") + @Override + public List getOutputHistogram(Collection amounts, Integer minCount, Integer maxCount, Boolean isUnlocked, Integer recentCutoff) { + + // build request params + Map params = new HashMap(); + params.put("amounts", amounts); + params.put("min_count", minCount); + params.put("max_count", maxCount); + params.put("unlocked", isUnlocked); + params.put("recent_cutoff", recentCutoff); + + // send rpc request + Map resp = rpc.sendJsonRequest("get_output_histogram", params); + Map result = (Map) resp.get("result"); + checkResponseStatus(result); + + // build histogram entries from response + List entries = new ArrayList(); + if (!result.containsKey("histogram")) return entries; + for (Map rpcEntry : (List>) result.get("histogram")) { + entries.add(convertRpcOutputHistogramEntry(rpcEntry)); + } + return entries; + } + + @Override + public List getOutputDistribution(Collection amounts, Boolean isCumulative, Long startHeight, Long endHeight) { + throw new RuntimeException("Not implemented (response 'distribution' field is binary)"); +// let amountStrs = []; +// for (let amount of amounts) amountStrs.push(amount.toJSValue()); +// console.log(amountStrs); +// console.log(cumulative); +// console.log(startHeight); +// console.log(endHeight); +// +// // send rpc request +// console.log("*********** SENDING REQUEST *************"); +// if (startHeight === undefined) startHeight = 0; +// let resp = await this.config.rpc.sendJsonRequest("get_output_distribution", { +// amounts: amountStrs, +// cumulative: cumulative, +// from_height: startHeight, +// to_height: endHeight +// }); +// +// console.log("RESPONSE"); +// console.log(resp); +// +// // build distribution entries from response +// let entries = []; +// if (!resp.result.distributions) return entries; +// for (let rpcEntry of resp.result.distributions) { +// let entry = MoneroDaemonRpc._convertRpcOutputDistributionEntry(rpcEntry); +// entries.push(entry); +// } +// return entries; + } + + @SuppressWarnings("unchecked") + @Override + public MoneroDaemonInfo getInfo() { + Map resp = rpc.sendJsonRequest("get_info"); + Map result = (Map) resp.get("result"); + checkResponseStatus(result); + return convertRpcInfo(result); + } + + @SuppressWarnings("unchecked") + @Override + public MoneroDaemonSyncInfo getSyncInfo() { + Map resp = rpc.sendJsonRequest("sync_info"); + Map result = (Map) resp.get("result"); + checkResponseStatus(result); + return convertRpcSyncInfo(result); + } + + @SuppressWarnings("unchecked") + @Override + public MoneroHardForkInfo getHardForkInfo() { + Map resp = rpc.sendJsonRequest("hard_fork_info"); + Map result = (Map) resp.get("result"); + checkResponseStatus(result); + return convertRpcHardForkInfo(result); + } + + @SuppressWarnings("unchecked") + @Override + public List getAltChains() { + Map resp = rpc.sendJsonRequest("get_alternate_chains"); + Map result = (Map) resp.get("result"); + checkResponseStatus(result); + List chains = new ArrayList(); + if (!result.containsKey("chains")) return chains; + for (Map rpcChain : (List>) result.get("chains")) chains.add(convertRpcAltChain(rpcChain)); + return chains; + } + + @SuppressWarnings("unchecked") + @Override + public List getAltBlockIds() { + Map resp = rpc.sendPathRequest("get_alt_blocks_hashes"); + checkResponseStatus(resp); + if (!resp.containsKey("blks_hashes")) return new ArrayList(); + return (List) resp.get("blks_hashes"); + } + + @Override + public int getDownloadLimit() { + return getBandwidthLimits()[0]; + } + + @Override + public int setDownloadLimit(int limit) { + if (limit == -1) return resetDownloadLimit(); + if (limit <= 0) throw new MoneroException("Download limit must be an integer greater than 0"); + return setBandwidthLimits(limit, 0)[0]; + } + + @Override + public int resetDownloadLimit() { + return setBandwidthLimits(-1, 0)[0]; + } + + @Override + public int getUploadLimit() { + return getBandwidthLimits()[1]; + } + + @Override + public int setUploadLimit(int limit) { + if (limit == -1) return resetUploadLimit(); + if (limit <= 0) throw new MoneroException("Upload limit must be an integer greater than 0"); + return setBandwidthLimits(0, limit)[1]; + } + + @Override + public int resetUploadLimit() { + return setBandwidthLimits(0, -1)[1]; + } + + @SuppressWarnings("unchecked") + @Override + public List getConnections() { + Map resp = rpc.sendJsonRequest("get_connections"); + Map result = (Map) resp.get("result"); + checkResponseStatus(result); + List connections = new ArrayList(); + if (!result.containsKey("connections")) return connections; + for (Map rpcConnection : (List>) result.get("connections")) { + connections.add(convertRpcConnection(rpcConnection)); + } + return connections; + } + + @SuppressWarnings("unchecked") + @Override + public List getKnownPeers() { + + // send request + Map respMap = rpc.sendPathRequest("get_peer_list"); + checkResponseStatus(respMap); + + // build peers + List peers = new ArrayList(); + if (respMap.containsKey("gray_list")) { + for (Map rpcPeer : (List>) respMap.get("gray_list")) { + MoneroDaemonPeer peer = convertRpcPeer(rpcPeer); + peer.setIsOnline(false); // gray list means offline last checked + peers.add(peer); + } + } + if (respMap.containsKey("white_list")) { + for (Map rpcPeer : (List>) respMap.get("white_list")) { + MoneroDaemonPeer peer = convertRpcPeer(rpcPeer); + peer.setIsOnline(true); // white list means online last checked + peers.add(peer); + } + } + return peers; + } + + @Override + public void setOutgoingPeerLimit(int limit) { + if (limit < 0) throw new MoneroException("Outgoing peer limit must be >= 0"); + Map params = new HashMap(); + params.put("out_peers", limit); + Map resp = rpc.sendPathRequest("out_peers", params); + checkResponseStatus(resp); + } + + @Override + public void setIncomingPeerLimit(int limit) { + if (limit < 0) throw new MoneroException("Incoming peer limit must be >= 0"); + Map params = new HashMap(); + params.put("in_peers", limit); + Map resp = rpc.sendPathRequest("in_peers", params); + checkResponseStatus(resp); + } + + @SuppressWarnings("unchecked") + @Override + public List getPeerBans() { + Map resp = (Map) rpc.sendJsonRequest("get_bans"); + Map result = (Map) resp.get("result"); + checkResponseStatus(result); + List bans = new ArrayList(); + for (Map rpcBan : (List>) result.get("bans")) { + MoneroBan ban = new MoneroBan(); + ban.setHost((String) rpcBan.get("host")); + ban.setIp(((BigInteger) rpcBan.get("ip")).intValue()); + ban.setSeconds(((BigInteger) rpcBan.get("seconds")).longValue()); + bans.add(ban); + } + return bans; + } + + @SuppressWarnings("unchecked") + @Override + public void setPeerBans(List bans) { + List> rpcBans = new ArrayList>(); + for (MoneroBan ban : bans) rpcBans.add(convertToRpcBan(ban)); + Map params = new HashMap(); + params.put("bans", rpcBans); + Map resp = rpc.sendJsonRequest("set_bans", params); + checkResponseStatus((Map) resp.get("result")); + } + +// async setOutgoingPeerLimit(limit) { +// assert(GenUtils.isInt(limit) && limit >= 0, "Outgoing peer limit must be >= 0"); +// let resp = this.config.rpc.sendPathRequest("out_peers", {out_peers: limit}); +// MoneroDaemonRpc._checkResponseStatus(resp); +// } +// +// async setIncomingPeerLimit(limit) { +// assert(GenUtils.isInt(limit) && limit >= 0, "Incoming peer limit must be >= 0"); +// let resp = this.config.rpc.sendPathRequest("in_peers", {in_peers: limit}); +// MoneroDaemonRpc._checkResponseStatus(resp); +// } +// +// async getPeerBans() { +// Map resp = rpc.sendJsonRequest("get_bans"); +// MoneroDaemonRpc._checkResponseStatus(resp.result); +// let bans = []; +// for (let rpcBan of resp.result.bans) { +// let ban = new MoneroBan(); +// ban.setHost(rpcBan.host); +// ban.setIp(rpcBan.ip); +// ban.setSeconds(rpcBan.seconds); +// bans.push(ban); +// } +// return bans; +// } +// +// async setPeerBan(ban) { +// return this.setPeerBans([ban]); +// } +// +// async setPeerBans(bans) { +// let rpcBans = []; +// for (let ban of bans) rpcBans.push(MoneroDaemonRpc._convertRpcBan(ban)); +// List resp = rpc.sendJsonRequest("set_bans", {bans: rpcBans}); +// MoneroDaemonRpc._checkResponseStatus(resp.result); +// } + + @Override + public void startMining(String address, Long numThreads, Boolean isBackground, Boolean ignoreBattery) { + if (address == null || address.isEmpty()) throw new MoneroException("Must provide address to mine to"); + if (numThreads == null || numThreads <= 0) throw new MoneroException("Number of threads must be an integer greater than 0"); + Map params = new HashMap(); + params.put("miner_address", address); + params.put("threads_count", numThreads); + params.put("do_background_mining", isBackground); + params.put("ignore_battery", ignoreBattery); + Map resp = rpc.sendPathRequest("start_mining", params); + checkResponseStatus(resp); + } + + @Override + public void stopMining() { + Map resp = rpc.sendPathRequest("stop_mining"); + checkResponseStatus(resp); + } + + @Override + public MoneroMiningStatus getMiningStatus() { + Map resp = rpc.sendPathRequest("mining_status"); + checkResponseStatus(resp); + return convertRpcMiningStatus(resp); + } + + @SuppressWarnings("unchecked") + @Override + public void submitBlocks(Collection blockBlobs) { + if (blockBlobs.isEmpty()) throw new MoneroException("Must provide an array of mined block blobs to submit"); + Map resp = rpc.sendJsonRequest("submit_block", blockBlobs); + checkResponseStatus((Map) resp.get("result")); + } + + @Override + public MoneroDaemonUpdateCheckResult checkForUpdate() { + Map params = new HashMap(); + params.put("command", "check"); + Map respMap = rpc.sendPathRequest("update", params); + checkResponseStatus(respMap); + return convertRpcUpdateCheckResult(respMap); + } + + @Override + public MoneroDaemonUpdateDownloadResult downloadUpdate(String path) { + Map params = new HashMap(); + params.put("command", "download"); + params.put("path", path); + Map resp = rpc.sendPathRequest("update", params); + checkResponseStatus(resp); + return convertRpcUpdateDownloadResult(resp); + } + + @Override + public void stop() { + Map resp = rpc.sendPathRequest("stop_daemon"); + checkResponseStatus(resp); + } + + @Override + public MoneroBlockHeader getNextBlockHeader() { + Object syncObject = new Object(); + synchronized(syncObject) { + try { + MoneroDaemonListener customListener = new MoneroDaemonListener() { + @Override + public void onBlockHeader(MoneroBlockHeader header) { + super.onBlockHeader(header); + synchronized(syncObject) { + syncObject.notifyAll(); + } + } + }; + addListener(customListener); + syncObject.wait(); + removeListener(customListener); + return customListener.getLastBlockHeader(); + } catch (InterruptedException e) { + throw new MoneroException(e); + } + } + } + + @Override + public void addListener(MoneroDaemonListener listener) { + daemonPoller.addListener(listener); + } + + @Override + public void removeListener(MoneroDaemonListener listener) { + daemonPoller.removeListener(listener); + } + + // ------------------------------- PRIVATE INSTANCE ---------------------------- + + private int[] getBandwidthLimits() { + Map resp = rpc.sendPathRequest("get_limit"); + checkResponseStatus(resp); + return new int[] { ((BigInteger) resp.get("limit_down")).intValue(), ((BigInteger) resp.get("limit_up")).intValue() }; + } + + private int[] setBandwidthLimits(Integer downLimit, Integer upLimit) { + if (downLimit == null) downLimit = 0; + if (upLimit == null) upLimit = 0; + Map params = new HashMap(); + params.put("limit_down", downLimit); + params.put("limit_up", upLimit); + Map resp = rpc.sendPathRequest("set_limit", params); + checkResponseStatus(resp); + return new int[] { ((BigInteger) resp.get("limit_down")).intValue(), ((BigInteger) resp.get("limit_up")).intValue() }; + } + + /** + * Get a contiguous chunk of blocks starting from a given height up to a maximum + * height or maximum amount of block data fetched from the blockchain, whichever comes first. + * + * @param startHeight is the start height to retrieve blocks (default 0) + * @param maxHeight is the maximum end height to retrieve blocks (default blockchain height) + * @param chunkSize is the maximum chunk size in any one request (default 3,000,000 bytes) + * @return List are the resulting chunk of blocks + */ + private List getMaxBlocks(Long startHeight, Long maxHeight, Long chunkSize) { + if (startHeight == null) startHeight = 0l; + if (maxHeight == null) maxHeight = getHeight() - 1; + if (chunkSize == null) chunkSize = MAX_REQ_SIZE; + + // determine end height to fetch + int reqSize = 0; + long endHeight = startHeight - 1; + while (reqSize < chunkSize && endHeight < maxHeight) { + + // get header of next block + MoneroBlockHeader header = getBlockHeaderByHeightCached(endHeight + 1, maxHeight); + + // block cannot be bigger than max request size + assertTrue("Block exceeds maximum request size: " + header.getSize(), header.getSize() <= chunkSize); + + // done iterating if fetching block would exceed max request size + if (reqSize + header.getSize() > chunkSize) break; + + // otherwise block is included + reqSize += header.getSize(); + endHeight++; + } + return endHeight >= startHeight ? getBlocksByRange(startHeight, endHeight) : new ArrayList(); + } + + /** + * Retrieves a header by height from the cache or fetches and caches a header + * range if not already in the cache. + * + * @param height is the height of the header to retrieve from the cache + * @param maxHeight is the maximum height of headers to cache + */ + private MoneroBlockHeader getBlockHeaderByHeightCached(long height, long maxHeight) { + + // get header from cache + MoneroBlockHeader cachedHeader = cachedHeaders.get(height); + if (cachedHeader != null) return cachedHeader; + + // fetch and cache headers if not in cache + long endHeight = Math.min(maxHeight, height + NUM_HEADERS_PER_REQ - 1); // TODO: could specify end height to cache to optimize small requests (would like to have time profiling in place though) + List headers = getBlockHeadersByRange(height, endHeight); + for (MoneroBlockHeader header : headers) { + cachedHeaders.put(header.getHeight(), header); + } + + // return the cached header + return cachedHeaders.get(height); + } + + //---------------------------------- PRIVATE STATIC ------------------------------- + + private static void checkResponseStatus(Map resp) { + String status = (String) resp.get("status"); + if (!"OK".equals(status)) throw new MoneroRpcException(status, null, null, null); + } + + private static MoneroBlockTemplate convertRpcBlockTemplate(Map rpcTemplate) { + MoneroBlockTemplate template = new MoneroBlockTemplate(); + for (String key : rpcTemplate.keySet()) { + Object val = rpcTemplate.get(key); + if (key.equals("blockhashing_blob")) template.setBlockTemplateBlob((String) val); + else if (key.equals("blocktemplate_blob")) template.setBlockHashingBlob((String) val); + else if (key.equals("difficulty")) template.setDifficulty((BigInteger) val); + else if (key.equals("expected_reward")) template.setExpectedReward((BigInteger) val); + else if (key.equals("difficulty")) { } // handled by wide_difficulty + else if (key.equals("difficulty_top64")) { } // handled by wide_difficulty + else if (key.equals("wide_difficulty")) template.setDifficulty(MoneroUtils.reconcile(template.getDifficulty(), prefixedHexToBI((String) val))); + else if (key.equals("height")) template.setHeight(((BigInteger) val).longValue()); + else if (key.equals("prev_hash")) template.setPrevId((String) val); + else if (key.equals("reserved_offset")) template.setReservedOffset(((BigInteger) val).longValue()); + else if (key.equals("status")) {} // handled elsewhere + else if (key.equals("untrusted")) {} // handled elsewhere + else LOGGER.warn("WARNING: ignoring unexpected field in block template: " + key + ": " + val); + } + return template; + } + + private static MoneroBlockHeader convertRpcBlockHeader(Map rpcHeader) { + return convertRpcBlockHeader(rpcHeader, null); + } + + private static MoneroBlockHeader convertRpcBlockHeader(Map rpcHeader, MoneroBlockHeader header) { + if (header == null) header = new MoneroBlockHeader(); + for (String key : rpcHeader.keySet()) { + Object val = rpcHeader.get(key); + if (key.equals("block_size")) header.setSize(MoneroUtils.reconcile(header.getSize(), ((BigInteger) val).longValue())); + else if (key.equals("depth")) header.setDepth(MoneroUtils.reconcile(header.getDepth(), ((BigInteger) val).longValue())); + else if (key.equals("difficulty")) { } // handled by wide_difficulty + else if (key.equals("cumulative_difficulty")) { } // handled by wide_cumulative_difficulty + else if (key.equals("difficulty_top64")) { } // handled by wide_difficulty + else if (key.equals("cumulative_difficulty_top64")) { } // handled by wide_cumulative_difficulty + else if (key.equals("wide_difficulty")) header.setDifficulty(MoneroUtils.reconcile(header.getDifficulty(), prefixedHexToBI((String) val))); + else if (key.equals("wide_cumulative_difficulty")) header.setCumulativeDifficulty(MoneroUtils.reconcile(header.getCumulativeDifficulty(), prefixedHexToBI((String) val))); + else if (key.equals("hash")) header.setId(MoneroUtils.reconcile(header.getId(), (String) val)); + else if (key.equals("height")) header.setHeight(MoneroUtils.reconcile(header.getHeight(), ((BigInteger) val).longValue())); + else if (key.equals("major_version")) header.setMajorVersion(MoneroUtils.reconcile(header.getMajorVersion(), ((BigInteger) val).intValue())); + else if (key.equals("minor_version")) header.setMinorVersion(MoneroUtils.reconcile(header.getMinorVersion(), ((BigInteger) val).intValue())); + else if (key.equals("nonce")) header.setNonce(MoneroUtils.reconcile(header.getNonce(), ((BigInteger) val).intValue())); + else if (key.equals("num_txes")) header.setNumTxs(MoneroUtils.reconcile(header.getNumTxs(), ((BigInteger) val).intValue())); + else if (key.equals("orphan_status")) header.setOrphanStatus(MoneroUtils.reconcile(header.getOrphanStatus(), (Boolean) val)); + else if (key.equals("prev_hash") || key.equals("prev_id")) header.setPrevId(MoneroUtils.reconcile(header.getPrevId(), (String) val)); + else if (key.equals("reward")) header.setReward(MoneroUtils.reconcile(header.getReward(), (BigInteger) val)); + else if (key.equals("timestamp")) header.setTimestamp(MoneroUtils.reconcile(header.getTimestamp(), ((BigInteger) val).longValue())); + else if (key.equals("block_weight")) header.setWeight(MoneroUtils.reconcile(header.getWeight(), ((BigInteger) val).longValue())); + else if (key.equals("long_term_weight")) header.setLongTermWeight(MoneroUtils.reconcile(header.getLongTermWeight(), ((BigInteger) val).longValue())); + else if (key.equals("pow_hash")) header.setPowHash(MoneroUtils.reconcile(header.getPowHash(), "".equals(val) ? null : (String) val)); + else if (key.equals("tx_hashes")) {} // used in block model, not header model + else if (key.equals("miner_tx")) {} // used in block model, not header model + else if (key.equals("miner_tx_hash")) header.setMinerTxId((String) val); + else LOGGER.warn("WARNING: ignoring unexpected block header field: '" + key + "': " + val); + } + return header; + } + + @SuppressWarnings("unchecked") + private static MoneroBlock convertRpcBlock(Map rpcBlock) { + + // build block + MoneroBlock block = new MoneroBlock(); + convertRpcBlockHeader(rpcBlock.containsKey("block_header") ? (Map) rpcBlock.get("block_header") : rpcBlock, block); + block.setHex((String) rpcBlock.get("blob")); + block.setTxIds(rpcBlock.containsKey("tx_hashes") ? (List) rpcBlock.get("tx_hashes") : new ArrayList()); + + // build miner tx + Map rpcMinerTx = (Map) (rpcBlock.containsKey("json") ? JsonUtils.deserialize(MoneroRpcConnection.MAPPER, (String) rpcBlock.get("json"), new TypeReference>(){}).get("miner_tx") : rpcBlock.get("miner_tx")); // may need to be parsed from json + MoneroTx minerTx = new MoneroTx().setIsConfirmed(true).setIsMinerTx(true); + MoneroDaemonRpc.convertRpcTx(rpcMinerTx, minerTx); + block.setMinerTx(minerTx); + + return block; + } + + /** + * Transfers RPC tx fields to a given MoneroTx without overwriting previous values. + * + * TODO: switch from safe set + * + * @param rpcTx is the RPC map containing transaction fields + * @param tx is the MoneroTx to populate with values (optional) + * @returns tx is the same tx that was passed in or a new one if none given + */ + @SuppressWarnings("unchecked") + private static MoneroTx convertRpcTx(Map rpcTx, MoneroTx tx) { + if (rpcTx == null) return null; + if (tx == null) tx = new MoneroTx(); + +// System.out.println("******** BUILDING TX ***********"); +// System.out.println(rpcTx); +// System.out.println(tx.toString()); + + // initialize from rpc map + MoneroBlock block = null; + for (String key : rpcTx.keySet()) { + Object val = rpcTx.get(key); + if (key.equals("tx_hash") || key.equals("id_hash")) tx.setId(MoneroUtils.reconcile(tx.getId(), (String) val)); + else if (key.equals("block_timestamp")) { + if (block == null) block = new MoneroBlock(); + block.setTimestamp(MoneroUtils.reconcile(block.getTimestamp(), ((BigInteger) val).longValue())); + } + else if (key.equals("block_height")) { + if (block == null) block = new MoneroBlock(); + block.setHeight(MoneroUtils.reconcile(block.getHeight(), ((BigInteger) val).longValue())); + } + else if (key.equals("last_relayed_time")) tx.setLastRelayedTimestamp(MoneroUtils.reconcile(tx.getLastRelayedTimestamp(), ((BigInteger) val).longValue())); + else if (key.equals("receive_time")) tx.setReceivedTimestamp(MoneroUtils.reconcile(tx.getReceivedTimestamp(), ((BigInteger) val).longValue())); + else if (key.equals("in_pool")) { + tx.setIsConfirmed(MoneroUtils.reconcile(tx.isConfirmed(), !(Boolean) val)); + tx.setInTxPool(MoneroUtils.reconcile(tx.inTxPool(), (Boolean) val)); + } + else if (key.equals("double_spend_seen")) tx.setIsDoubleSpendSeen(MoneroUtils.reconcile(tx.isDoubleSpendSeen(), (Boolean) val)); + else if (key.equals("version")) tx.setVersion(MoneroUtils.reconcile(tx.getVersion(), ((BigInteger) val).intValue())); + else if (key.equals("extra")) { + List ints = new ArrayList(); + for (BigInteger bi : (List) val) ints.add(bi.intValue()); + tx.setExtra(MoneroUtils.reconcile(tx.getExtra(), GenUtils.listToIntArray(ints))); + } + else if (key.equals("vin")) { + List> rpcVins = (List>) val; + if (rpcVins.size() != 1 || !rpcVins.get(0).containsKey("gen")) { // ignore miner vin TODO: why? probably needs re-enabled + List vins = new ArrayList(); + for (Map rpcVin : rpcVins) vins.add(convertRpcOutput(rpcVin, tx)); + tx.setVins(vins); + } + } + else if (key.equals("vout")) { + List> rpcVouts = (List>) val; + List vouts = new ArrayList(); + for (Map rpcVout : rpcVouts) vouts.add(convertRpcOutput(rpcVout, tx)); + tx.setVouts(vouts); + } + else if (key.equals("rct_signatures")) tx.setRctSignatures(MoneroUtils.reconcile(tx.getRctSignatures(), (Map) val)); + else if (key.equals("rctsig_prunable")) tx.setRctSigPrunable(MoneroUtils.reconcile(tx.getRctSigPrunable(), val)); + else if (key.equals("unlock_time")) tx.setUnlockTime(MoneroUtils.reconcile(tx.getUnlockTime(), ((BigInteger) val).longValue())); + else if (key.equals("as_json") || key.equals("tx_json")) { } // handled last so tx is as initialized as possible + else if (key.equals("as_hex") || key.equals("tx_blob")) tx.setFullHex(MoneroUtils.reconcile(tx.getFullHex(), "".equals((String) val) ? null : (String) val)); + else if (key.equals("blob_size")) tx.setSize(MoneroUtils.reconcile(tx.getSize(), ((BigInteger) val).longValue())); + else if (key.equals("weight")) tx.setWeight(MoneroUtils.reconcile(tx.getWeight(), ((BigInteger) val).longValue())); + else if (key.equals("fee")) tx.setFee(MoneroUtils.reconcile(tx.getFee(), (BigInteger) val)); + else if (key.equals("relayed")) tx.setIsRelayed(MoneroUtils.reconcile(tx.isRelayed(), (Boolean) val)); + else if (key.equals("output_indices")) { + List indices = new ArrayList(); + for (BigInteger bi : (List) val) indices.add(bi.intValue()); + tx.setOutputIndices(MoneroUtils.reconcile(tx.getOutputIndices(), indices)); + } + else if (key.equals("do_not_relay")) tx.setDoNotRelay(MoneroUtils.reconcile(tx.getDoNotRelay(), (Boolean) val)); + else if (key.equals("kept_by_block")) tx.setIsKeptByBlock(MoneroUtils.reconcile(tx.isKeptByBlock(), (Boolean) val)); + else if (key.equals("signatures")) tx.setSignatures(MoneroUtils.reconcile(tx.getSignatures(), (List) val)); + else if (key.equals("last_failed_height")) { + long lastFailedHeight = ((BigInteger) val).longValue(); + if (lastFailedHeight == 0) tx.setIsFailed(MoneroUtils.reconcile(tx.isFailed(), false)); + else { + tx.setIsFailed(MoneroUtils.reconcile(tx.isFailed(), true)); + tx.setLastFailedHeight(MoneroUtils.reconcile(tx.getLastFailedHeight(), lastFailedHeight)); + } + } + else if (key.equals("last_failed_id_hash")) { + if (DEFAULT_ID.equals((String) val)) tx.setIsFailed(MoneroUtils.reconcile(tx.isFailed(), false)); + else { + tx.setIsFailed(MoneroUtils.reconcile(tx.isFailed(), true)); + tx.setLastFailedId(MoneroUtils.reconcile(tx.getLastFailedId(), (String) val)); + } + } + else if (key.equals("max_used_block_height")) tx.setMaxUsedBlockHeight(MoneroUtils.reconcile(tx.getMaxUsedBlockHeight(), ((BigInteger) val).longValue())); + else if (key.equals("max_used_block_id_hash")) tx.setMaxUsedBlockId(MoneroUtils.reconcile(tx.getMaxUsedBlockId(), (String) val)); + else if (key.equals("prunable_hash")) tx.setPrunableHash(MoneroUtils.reconcile(tx.getPrunableHash(), "".equals((String) val) ? null : (String) val)); + else if (key.equals("prunable_as_hex")) tx.setPrunableHex(MoneroUtils.reconcile(tx.getPrunableHex(), "".equals((String) val) ? null : (String) val)); + else if (key.equals("pruned_as_hex")) tx.setPrunedHex(MoneroUtils.reconcile(tx.getPrunedHex(), "".equals((String) val) ? null : (String) val)); + else LOGGER.warn("WARNING: ignoring unexpected field in rpc tx: " + key + ": " + val); + } + + // link block and tx + if (block != null) tx.setBlock(block.setTxs(Arrays.asList(tx))); + + // TODO monero-daemon-rpc: unconfirmed txs misreport block height and timestamp + if (tx.getBlock() != null && tx.getBlock().getHeight() != null && (long) tx.getBlock().getHeight() == tx.getBlock().getTimestamp()) { + tx.setBlock(null); + tx.setIsConfirmed(false); + } + + // initialize remaining known fields + if (tx.isConfirmed()) { + tx.setIsRelayed(MoneroUtils.reconcile(tx.isRelayed(), true)); + tx.setDoNotRelay(MoneroUtils.reconcile(tx.getDoNotRelay(), false)); + tx.setIsFailed(MoneroUtils.reconcile(tx.isFailed(), false)); + } else { + tx.setNumConfirmations(0l); + } + if (tx.isFailed() == null) tx.setIsFailed(false); + if (tx.getOutputIndices() != null && tx.getVouts() != null) { + assertEquals(tx.getOutputIndices().size(), (int) tx.getVouts().size()); + for (int i = 0; i < tx.getVouts().size(); i++) { + tx.getVouts().get(i).setIndex(tx.getOutputIndices().get(i)); // transfer output indices to vouts + } + } + if (rpcTx.containsKey("as_json") && !"".equals(rpcTx.get("as_json"))) convertRpcTx(JsonUtils.deserialize(MoneroRpcConnection.MAPPER, (String) rpcTx.get("as_json"), new TypeReference>(){}), tx); + if (rpcTx.containsKey("tx_json") && !"".equals(rpcTx.get("tx_json"))) convertRpcTx(JsonUtils.deserialize(MoneroRpcConnection.MAPPER, (String) rpcTx.get("tx_json"), new TypeReference>(){}), tx); + if (!Boolean.TRUE.equals(tx.isRelayed())) tx.setLastRelayedTimestamp(null); // TODO monero-daemon-rpc: returns last_relayed_timestamp despite relayed: false, self inconsistent + + // return built transaction + return tx; + } + + @SuppressWarnings("unchecked") + private static MoneroOutput convertRpcOutput(Map rpcOutput, MoneroTx tx) { + MoneroOutput output = new MoneroOutput(); + output.setTx(tx); + for (String key : rpcOutput.keySet()) { + Object val = rpcOutput.get(key); + if (key.equals("gen")) throw new Error("Output with 'gen' from daemon rpc is miner tx which we ignore (i.e. each miner vin is null)"); + else if (key.equals("key")) { + Map rpcKey = (Map) val; + output.setAmount(MoneroUtils.reconcile(output.getAmount(), (BigInteger) rpcKey.get("amount"))); + output.setKeyImage(MoneroUtils.reconcile(output.getKeyImage(), new MoneroKeyImage((String) rpcKey.get("k_image")))); + List ringOutputIndices = new ArrayList(); + for (BigInteger bi : (List) rpcKey.get("key_offsets")) ringOutputIndices.add(bi.intValue()); + output.setRingOutputIndices(MoneroUtils.reconcile(output.getRingOutputIndices(), ringOutputIndices)); + } + else if (key.equals("amount")) output.setAmount(MoneroUtils.reconcile(output.getAmount(), (BigInteger) val)); + else if (key.equals("target")) output.setStealthPublicKey(MoneroUtils.reconcile(output.getStealthPublicKey(), (String) ((Map) val).get("key"))); + else LOGGER.warn("WARNING: ignoring unexpected field output: " + key + ": " + val); + } + return output; + } + + private static MoneroDaemonUpdateCheckResult convertRpcUpdateCheckResult(Map rpcResult) { + MoneroDaemonUpdateCheckResult result = new MoneroDaemonUpdateCheckResult(); + for (String key : rpcResult.keySet()) { + Object val = rpcResult.get(key); + if (key.equals("auto_uri")) result.setAutoUri((String) val); + else if (key.equals("hash")) result.setHash((String) val); + else if (key.equals("path")) {} // handled elsewhere + else if (key.equals("status")) {} // handled elsewhere + else if (key.equals("update")) result.setIsUpdateAvailable((Boolean) val); + else if (key.equals("user_uri")) result.setUserUri((String) val); + else if (key.equals("version")) result.setVersion((String) val); + else LOGGER.warn("WARNING: ignoring unexpected field in rpc check update result: " + key + ": " + val); + } + if ("".equals(result.getAutoUri())) result.setAutoUri(null); + if ("".equals(result.getUserUri())) result.setUserUri(null); + if ("".equals(result.getVersion())) result.setVersion(null); + if ("".equals(result.getHash())) result.setHash(null); + return result; + } + + private static MoneroDaemonUpdateDownloadResult convertRpcUpdateDownloadResult(Map rpcResult) { + MoneroDaemonUpdateDownloadResult result = new MoneroDaemonUpdateDownloadResult(convertRpcUpdateCheckResult(rpcResult)); + result.setDownloadPath((String) rpcResult.get("path")); + if ("".equals(result.getDownloadPath())) result.setDownloadPath(null); + return result; + } + + private static MoneroDaemonPeer convertRpcPeer(Map rpcPeer) { + assertNotNull(rpcPeer); + MoneroDaemonPeer peer = new MoneroDaemonPeer(); + for (String key : rpcPeer.keySet()) { + Object val = rpcPeer.get(key); + if (key.equals("host")) peer.setHost((String) val); + else if (key.equals("id")) peer.setId("" + val); // TODO monero-wallet-rpc: peer id is big integer but string in `get_connections` + else if (key.equals("ip")) {} // host used instead which is consistently a string + else if (key.equals("last_seen")) peer.setLastSeenTimestamp(((BigInteger) val).longValue()); + else if (key.equals("port")) peer.setPort(((BigInteger) val).intValue()); + else if (key.equals("rpc_port")) peer.setRpcPort(((BigInteger) val).intValue()); + else if (key.equals("pruning_seed")) peer.setPruningSeed(((BigInteger) val).intValue()); + else LOGGER.warn("WARNING: ignoring unexpected field in rpc peer: " + key + ": " + val); + } + return peer; + } + + private static MoneroSubmitTxResult convertRpcSubmitTxResult(Map rpcResult) { + assertNotNull(rpcResult); + MoneroSubmitTxResult result = new MoneroSubmitTxResult(); + for (String key : rpcResult.keySet()) { + Object val = rpcResult.get(key); + if (key.equals("double_spend")) result.setIsDoubleSpend((Boolean) val); + else if (key.equals("fee_too_low")) result.setIsFeeTooLow((Boolean) val); + else if (key.equals("invalid_input")) result.setHasInvalidInput((Boolean) val); + else if (key.equals("invalid_output")) result.setHasInvalidOutput((Boolean) val); + else if (key.equals("low_mixin")) result.setIsMixinTooLow((Boolean) val); + else if (key.equals("not_rct")) result.setIsRct(!Boolean.TRUE.equals(val)); + else if (key.equals("not_relayed")) result.setIsRelayed(!Boolean.TRUE.equals(val)); + else if (key.equals("overspend")) result.setIsOverspend((Boolean) val); + else if (key.equals("reason")) result.setReason("".equals((String) val) ? null : (String) val); + else if (key.equals("too_big")) result.setIsTooBig((Boolean) val); + else if (key.equals("sanity_check_failed")) result.setSanityCheckFailed((Boolean) val); + else if (key.equals("status") || key.equals("untrusted")) {} // handled elsewhere + else LOGGER.warn("WARNING: ignoring unexpected field in submit tx hex result: " + key + ": " + val); + } + return result; + } + + private static MoneroDaemonConnection convertRpcConnection(Map rpcConnection) { + MoneroDaemonConnection connection = new MoneroDaemonConnection(); + MoneroDaemonPeer peer = new MoneroDaemonPeer(); + connection.setPeer(peer); + peer.setIsOnline(true); + for (String key : rpcConnection.keySet()) { + Object val = rpcConnection.get(key); + if (key.equals("address")) peer.setAddress((String) val); + else if (key.equals("avg_download")) connection.setAvgDownload(((BigInteger) val).longValue()); + else if (key.equals("avg_upload")) connection.setAvgUpload(((BigInteger) val).longValue()); + else if (key.equals("connection_id")) connection.setId((String) val); + else if (key.equals("current_download")) connection.setCurrentDownload(((BigInteger) val).longValue()); + else if (key.equals("current_upload")) connection.setCurrentUpload(((BigInteger) val).longValue()); + else if (key.equals("height")) connection.setHeight(((BigInteger) val).longValue()); + else if (key.equals("host")) peer.setHost((String) val); + else if (key.equals("ip")) {} // host used instead which is consistently a string + else if (key.equals("incoming")) connection.setIsIncoming((Boolean) val); + else if (key.equals("live_time")) connection.setLiveTime(((BigInteger) val).longValue()); + else if (key.equals("local_ip")) connection.setIsLocalIp((Boolean) val); + else if (key.equals("localhost")) connection.setIsLocalHost((Boolean) val); + else if (key.equals("peer_id")) peer.setId((String) val); + else if (key.equals("port")) peer.setPort(Integer.parseInt((String) val)); + else if (key.equals("rpc_port")) peer.setRpcPort(((BigInteger) val).intValue()); + else if (key.equals("recv_count")) connection.setNumReceives(((BigInteger) val).intValue()); + else if (key.equals("recv_idle_time")) connection.setReceiveIdleTime(((BigInteger) val).longValue()); + else if (key.equals("send_count")) connection.setNumSends(((BigInteger) val).intValue()); + else if (key.equals("send_idle_time")) connection.setSendIdleTime(((BigInteger) val).longValue()); + else if (key.equals("state")) connection.setState((String) val); + else if (key.equals("support_flags")) connection.setNumSupportFlags(((BigInteger) val).intValue()); + else if (key.equals("pruning_seed")) peer.setPruningSeed(((BigInteger) val).intValue()); + else LOGGER.warn("WARNING: ignoring unexpected field in connection: " + key + ": " + val); + } + return connection; + } + + private static MoneroOutputHistogramEntry convertRpcOutputHistogramEntry(Map rpcEntry) { + MoneroOutputHistogramEntry entry = new MoneroOutputHistogramEntry(); + for (String key : rpcEntry.keySet()) { + Object val = rpcEntry.get(key); + if (key.equals("amount")) entry.setAmount((BigInteger) val); + else if (key.equals("total_instances")) entry.setNumInstances(((BigInteger) val).longValue()); + else if (key.equals("unlocked_instances")) entry.setNumUnlockedInstances(((BigInteger) val).longValue()); + else if (key.equals("recent_instances")) entry.setNumRecentInstances(((BigInteger) val).longValue()); + else LOGGER.warn("WARNING: ignoring unexpected field in output histogram: " + key + ": " + val); + } + return entry; + } + + private static MoneroDaemonInfo convertRpcInfo(Map rpcInfo) { + if (rpcInfo == null) return null; + MoneroDaemonInfo info = new MoneroDaemonInfo(); + for (String key : rpcInfo.keySet()) { + Object val = rpcInfo.get(key); + if (key.equals("version")) info.setVersion((String) val); + else if (key.equals("alt_blocks_count")) info.setNumAltBlocks(((BigInteger) val).longValue()); + else if (key.equals("block_size_limit")) info.setBlockSizeLimit(((BigInteger) val).longValue()); + else if (key.equals("block_size_median")) info.setBlockSizeMedian(((BigInteger) val).longValue()); + else if (key.equals("block_weight_limit")) info.setBlockWeightLimit(((BigInteger) val).longValue()); + else if (key.equals("block_weight_median")) info.setBlockWeightMedian(((BigInteger) val).longValue()); + else if (key.equals("bootstrap_daemon_address")) { if (!((String) val).isEmpty()) info.setBootstrapDaemonAddress((String) val); } + else if (key.equals("difficulty")) { } // handled by wide_difficulty + else if (key.equals("cumulative_difficulty")) { } // handled by wide_cumulative_difficulty + else if (key.equals("difficulty_top64")) { } // handled by wide_difficulty + else if (key.equals("cumulative_difficulty_top64")) { } // handled by wide_cumulative_difficulty + else if (key.equals("wide_difficulty")) info.setDifficulty(MoneroUtils.reconcile(info.getDifficulty(), prefixedHexToBI((String) val))); + else if (key.equals("wide_cumulative_difficulty")) info.setCumulativeDifficulty(MoneroUtils.reconcile(info.getCumulativeDifficulty(), prefixedHexToBI((String) val))); + else if (key.equals("free_space")) info.setFreeSpace((BigInteger) val); + else if (key.equals("database_size")) info.setDatabaseSize(((BigInteger) val).longValue()); + else if (key.equals("grey_peerlist_size")) info.setNumOfflinePeers(((BigInteger) val).intValue()); + else if (key.equals("height")) info.setHeight(((BigInteger) val).longValue()); + else if (key.equals("height_without_bootstrap")) info.setHeightWithoutBootstrap(((BigInteger) val).longValue()); + else if (key.equals("incoming_connections_count")) info.setNumIncomingConnections(((BigInteger) val).intValue()); + else if (key.equals("offline")) info.setIsOffline((Boolean) val); + else if (key.equals("outgoing_connections_count")) info.setNumOutgoingConnections(((BigInteger) val).intValue()); + else if (key.equals("rpc_connections_count")) info.setNumRpcConnections(((BigInteger) val).intValue()); + else if (key.equals("start_time")) info.setStartTimestamp(((BigInteger) val).longValue()); + else if (key.equals("status")) {} // handled elsewhere + else if (key.equals("target")) info.setTarget(((BigInteger) val).longValue()); + else if (key.equals("target_height")) info.setTargetHeight(((BigInteger) val).longValue()); + else if (key.equals("top_block_hash")) info.setTopBlockId((String) val); + else if (key.equals("tx_count")) info.setNumTxs(((BigInteger) val).intValue()); + else if (key.equals("tx_pool_size")) info.setNumTxsPool(((BigInteger) val).intValue()); + else if (key.equals("untrusted")) {} // handled elsewhere + else if (key.equals("was_bootstrap_ever_used")) info.setWasBootstrapEverUsed((Boolean) val); + else if (key.equals("white_peerlist_size")) info.setNumOnlinePeers(((BigInteger) val).intValue()); + else if (key.equals("update_available")) info.setUpdateAvailable((Boolean) val); + else if (key.equals("nettype")) info.setNetworkType(MoneroUtils.reconcile(info.getNetworkType(), MoneroDaemon.parseNetworkType((String) val))); + else if (key.equals("mainnet")) { if ((Boolean) val) info.setNetworkType(MoneroUtils.reconcile(info.getNetworkType(), MoneroNetworkType.MAINNET)); } + else if (key.equals("testnet")) { if ((Boolean) val) info.setNetworkType(MoneroUtils.reconcile(info.getNetworkType(), MoneroNetworkType.TESTNET)); } + else if (key.equals("stagenet")) { if ((Boolean) val) info.setNetworkType(MoneroUtils.reconcile(info.getNetworkType(), MoneroNetworkType.STAGENET)); } + else LOGGER.warn("WARNING: Ignoring unexpected info field: " + key + ": " + val); + } + return info; + } + + /** + * Initializes sync info from RPC sync info. + * + * @param rpcSyncInfo is the rpc map to initialize the sync info from + * @return {MoneroDaemonSyncInfo} is sync info initialized from the map + */ + @SuppressWarnings("unchecked") + private static MoneroDaemonSyncInfo convertRpcSyncInfo(Map rpcSyncInfo) { + MoneroDaemonSyncInfo syncInfo = new MoneroDaemonSyncInfo(); + for (String key : rpcSyncInfo.keySet()) { + Object val = rpcSyncInfo.get(key); + if (key.equals("height")) syncInfo.setHeight(((BigInteger) val).longValue()); + else if (key.equals("peers")) { + syncInfo.setConnections(new ArrayList()); + List> rpcConnections = (List>) val; + for (Map rpcConnection : rpcConnections) { + syncInfo.getConnections().add(convertRpcConnection((Map) rpcConnection.get("info"))); + } + } else if (key.equals("spans")) { + syncInfo.setSpans(new ArrayList()); + List> rpcSpans = (List>) val; + for (Map rpcSpan : rpcSpans) { + syncInfo.getSpans().add(convertRpcConnectionSpan(rpcSpan)); + } + } + else if (key.equals("status")) {} // handled elsewhere + else if (key.equals("target_height")) syncInfo.setTargetHeight(((BigInteger) val).longValue()); + else if (key.equals("next_needed_pruning_seed")) syncInfo.setNextNeededPruningSeed(((BigInteger) val).intValue()); + else if (key.equals("overview")) { // this returns [] without pruning + try { + List overview = JsonUtils.deserialize((String) val, new TypeReference>(){}); + if (!overview.isEmpty()) LOGGER.warn("WARNING: ignoring non-empty 'overview' field (not implemented): " + overview); // TODO + } catch (Exception e) { + //e.printStackTrace(); + LOGGER.warn("WARNING: failed to parse 'overview' field: " + val); + } + } + else LOGGER.warn("WARNING: ignoring unexpected field in sync info: " + key + ": " + val); + } + return syncInfo; + } + + private static MoneroHardForkInfo convertRpcHardForkInfo(Map rpcHardForkInfo) { + MoneroHardForkInfo info = new MoneroHardForkInfo(); + for (String key : rpcHardForkInfo.keySet()) { + Object val = rpcHardForkInfo.get(key); + if (key.equals("earliest_height")) info.setEarliestHeight(((BigInteger) val).longValue()); + else if (key.equals("enabled")) info.setIsEnabled((Boolean) val); + else if (key.equals("state")) info.setState(((BigInteger) val).intValue()); + else if (key.equals("status")) {} // handled elsewhere + else if (key.equals("untrusted")) {} // handled elsewhere + else if (key.equals("threshold")) info.setThreshold(((BigInteger) val).intValue()); + else if (key.equals("version")) info.setVersion(((BigInteger) val).intValue()); + else if (key.equals("votes")) info.setNumVotes(((BigInteger) val).intValue()); + else if (key.equals("voting")) info.setVoting(((BigInteger) val).intValue()); + else if (key.equals("window")) info.setWindow(((BigInteger) val).intValue()); + else LOGGER.warn("WARNING: ignoring unexpected field in hard fork info: " + key + ": " + val); + } + return info; + } + + private static MoneroDaemonConnectionSpan convertRpcConnectionSpan(Map rpcConnectionSpan) { + MoneroDaemonConnectionSpan span = new MoneroDaemonConnectionSpan(); + for (String key : rpcConnectionSpan.keySet()) { + Object val = rpcConnectionSpan.get(key); + if (key.equals("connection_id")) span.setConnectionId((String) val); + else if (key.equals("nblocks")) span.setNumBlocks(((BigInteger) val).longValue()); + else if (key.equals("rate")) span.setRate(((BigInteger) val).longValue()); + else if (key.equals("remote_address")) { if (!"".equals(val)) span.setRemoteAddress((String) val); } + else if (key.equals("size")) span.setSize(((BigInteger) val).longValue()); + else if (key.equals("speed")) span.setSpeed(((BigInteger) val).longValue()); + else if (key.equals("start_block_height")) span.setStartHeight(((BigInteger) val).longValue()); + else LOGGER.warn("WARNING: ignoring unexpected field in daemon connection span: " + key + ": " + val); + } + return span; + } + + private static Map convertToRpcBan(MoneroBan ban) { + Map rpcBan = new HashMap(); + rpcBan.put("host", ban.getHost()); + rpcBan.put("ip", ban.getIp()); + rpcBan.put("ban", ban.isBanned()); + rpcBan.put("seconds", ban.getSeconds()); + return rpcBan; + } + + private static MoneroMiningStatus convertRpcMiningStatus(Map rpcStatus) { + MoneroMiningStatus status = new MoneroMiningStatus(); + status.setIsActive((Boolean) rpcStatus.get("active")); + status.setSpeed(((BigInteger) rpcStatus.get("speed")).longValue()); + status.setNumThreads(((BigInteger) rpcStatus.get("threads_count")).intValue()); + if (status.isActive()) { + status.setAddress((String) rpcStatus.get("address")); + status.setIsBackground((Boolean) rpcStatus.get("is_background_mining_enabled")); + } + return status; + } + + @SuppressWarnings("unchecked") + private static MoneroAltChain convertRpcAltChain(Map rpcChain) { + MoneroAltChain chain = new MoneroAltChain(); + for (String key : rpcChain.keySet()) { + Object val = rpcChain.get(key); + if (key.equals("block_hash")) {} // using block_hashes instead + else if (key.equals("difficulty")) { } // handled by wide_difficulty + else if (key.equals("difficulty_top64")) { } // handled by wide_difficulty + else if (key.equals("wide_difficulty")) chain.setDifficulty(MoneroUtils.reconcile(chain.getDifficulty(), prefixedHexToBI((String) val))); + else if (key.equals("height")) chain.setHeight(((BigInteger) val).longValue()); + else if (key.equals("length")) chain.setLength(((BigInteger) val).longValue()); + else if (key.equals("block_hashes")) chain.setBlockIds((List) val); + else if (key.equals("main_chain_parent_block")) chain.setMainChainParentBlockId((String) val); + else LOGGER.warn("WARNING: ignoring unexpected field in alternative chain: " + key + ": " + val); + } + return chain; + } + + /** + * Converts a '0x' prefixed hexidecimal string to a BigInteger. + * + * @param hex is the '0x' prefixed hexidecimal string to convert + * @return BigInteger is the hexicedimal converted to decimal + */ + private static BigInteger prefixedHexToBI(String hex) { + assertTrue("Given hex does not start with \"0x\": " + hex, hex.startsWith("0x")); + return new BigInteger(hex.substring(2), 16); + } + + /** + * Polls a Monero daemon for updates and notifies listeners as they occur. + */ + private class MoneroDaemonPoller { + + private MoneroDaemon daemon; + private MoneroDaemonPollerRunnable runnable; + private List listeners; + private static final long POLL_INTERVAL_MS = 10000; // poll every X ms TODO: poll interval should come from configuration + + public MoneroDaemonPoller(MoneroDaemon daemon) { + this.daemon = daemon; + this.listeners = new ArrayList(); + } + + public void addListener(MoneroDaemonListener listener) { + + // register listener + listeners.add(listener); + + // start polling thread + if (runnable == null) { + runnable = new MoneroDaemonPollerRunnable(daemon, POLL_INTERVAL_MS); + Thread thread = new Thread(runnable); + thread.setDaemon(true); // daemon thread does not prevent JVM from halting + thread.start(); + } + } + + public void removeListener(MoneroDaemonListener listener) { + boolean found = listeners.remove(listener); + if (!found) throw new MoneroException("Listener is not registered"); + if (listeners.isEmpty()) { + runnable.terminate(); + runnable = null; + } + } + + private class MoneroDaemonPollerRunnable implements Runnable { + + private MoneroDaemon daemon; + private long interval; + private boolean isTerminated; + + public MoneroDaemonPollerRunnable(MoneroDaemon daemon, long interval) { + this.daemon = daemon; + this.interval = interval; + this.isTerminated = false; + } + + @Override + public void run() { + + // get header to detect changes while polling + MoneroBlockHeader lastHeader = daemon.getLastBlockHeader(); + + // poll until stopped + while (!isTerminated) { + + // pause for interval ms + try { + TimeUnit.MILLISECONDS.sleep(interval); + } catch (InterruptedException e) { + e.printStackTrace(); + terminate(); + } + + // fetch and compare latest block header + MoneroBlockHeader header = daemon.getLastBlockHeader(); + if (!header.getId().equals(lastHeader.getId())) { + lastHeader = header; + for (MoneroDaemonListener listener : listeners) { + listener.onBlockHeader(header); // notify listener + } + } + } + } + + public void terminate() { + isTerminated = true; + } + } + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroAltChain.java b/core/src/main/java/monero/daemon/model/MoneroAltChain.java new file mode 100644 index 00000000000..115904badad --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroAltChain.java @@ -0,0 +1,56 @@ +package monero.daemon.model; + +import java.math.BigInteger; +import java.util.List; + +/** + * Models an alternative chain seen by the node. + */ +public class MoneroAltChain { + + private List blockIds; + private BigInteger difficulty; + private Long height; + private Long length; + private String mainChainParentBlockId; + + public List getBlockIds() { + return blockIds; + } + + public void setBlockIds(List blockIds) { + this.blockIds = blockIds; + } + + public BigInteger getDifficulty() { + return difficulty; + } + + public void setDifficulty(BigInteger difficulty) { + this.difficulty = difficulty; + } + + public Long getHeight() { + return height; + } + + public void setHeight(Long height) { + this.height = height; + } + + public Long getLength() { + return length; + } + + public void setLength(Long length) { + this.length = length; + } + + public String getMainChainParentBlockId() { + return mainChainParentBlockId; + } + + public void setMainChainParentBlockId(String mainChainParentBlockId) { + this.mainChainParentBlockId = mainChainParentBlockId; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroBan.java b/core/src/main/java/monero/daemon/model/MoneroBan.java new file mode 100644 index 00000000000..b2bf2ab4704 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroBan.java @@ -0,0 +1,47 @@ +package monero.daemon.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Monero banhammer. + */ +public class MoneroBan { + + private String host; // e.g. 192.168.1.100 + private Integer ip; // integer formatted IP + private Boolean isBanned; + private Long seconds; + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public Integer getIp() { + return ip; + } + + public void setIp(Integer ip) { + this.ip = ip; + } + + @JsonProperty("isBanned") + public Boolean isBanned() { + return isBanned; + } + + public void setIsBanned(Boolean isBanned) { + this.isBanned = isBanned; + } + + public Long getSeconds() { + return seconds; + } + + public void setSeconds(Long seconds) { + this.seconds = seconds; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroBlock.java b/core/src/main/java/monero/daemon/model/MoneroBlock.java new file mode 100644 index 00000000000..cd351f27733 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroBlock.java @@ -0,0 +1,276 @@ +package monero.daemon.model; + +import static org.junit.Assert.assertNotNull; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.fasterxml.jackson.annotation.JsonProperty; + +import common.utils.GenUtils; +import monero.utils.MoneroUtils; + +/** + * Models a Monero block in the blockchain. + */ +public class MoneroBlock extends MoneroBlockHeader { + + private String hex; + private MoneroTx minerTx; + private List txs; + private List txIds; + + public MoneroBlock() { + super(); + } + + public MoneroBlock(MoneroBlockHeader header) { + super(header); + } + + public MoneroBlock(MoneroBlock block) { + super(block); + this.hex = block.getHex(); + if (block.minerTx != null) this.minerTx = block.minerTx.copy().setBlock(this); + if (block.txs != null) { + this.txs = new ArrayList(); + for (MoneroTx tx : block.txs) txs.add(tx.copy().setBlock(this)); + } + if (block.getTxIds() != null) this.txIds = new ArrayList(block.getTxIds()); + } + + public String getHex() { + return hex; + } + + public MoneroBlock setHex(String hex) { + this.hex = hex; + return this; + } + + public MoneroTx getMinerTx() { + return minerTx; + } + + public MoneroBlock setMinerTx(MoneroTx minerTx) { + this.minerTx = minerTx; + return this; + } + + @JsonManagedReference("block_txs") + public List getTxs() { + return txs; + } + + @JsonProperty("txs") + public MoneroBlock setTxs(List txs) { + this.txs = txs; + return this; + } + + @JsonIgnore + public MoneroBlock setTxs(MoneroTx... txs) { + this.txs = GenUtils.arrayToList(txs); + return this; + } + + public List getTxIds() { + return txIds; + } + + public MoneroBlock setTxIds(List txIds) { + this.txIds = txIds; + return this; + } + + public MoneroBlock copy() { + return new MoneroBlock(this); + } + + public MoneroBlock merge(MoneroBlock block) { + assertNotNull(block); + if (this == block) return this; + + // merge header fields + super.merge(block); + + // merge reconcilable block extensions + this.setHex(MoneroUtils.reconcile(this.getHex(), block.getHex())); + this.setTxIds(MoneroUtils.reconcile(this.getTxIds(), block.getTxIds())); + + // merge miner tx + if (this.getMinerTx() == null) this.setMinerTx(block.getMinerTx()); + if (block.getMinerTx() != null) { + block.getMinerTx().setBlock(this); + minerTx.merge(block.getMinerTx()); + } + + // merge non-miner txs + if (block.getTxs() != null) { + for (MoneroTx tx : block.getTxs()) { + tx.setBlock(this); + MoneroUtils.mergeTx(txs, tx); + } + } + + return this; + } + + public String toString(int indent) { + StringBuilder sb = new StringBuilder(); + sb.append(super.toString(indent)); + sb.append("\n"); + sb.append(MoneroUtils.kvLine("Hex", getHex(), indent)); + sb.append(MoneroUtils.kvLine("Txs ids", getTxIds(), indent)); + if (getMinerTx() != null) { + sb.append(MoneroUtils.kvLine("Miner tx", "", indent)); + sb.append(getMinerTx().toString(indent + 1) + "\n"); + } + if (getTxs() != null) { + sb.append(MoneroUtils.kvLine("Txs", "", indent)); + for (MoneroTx tx : getTxs()) { + sb.append(tx.toString(indent + 1) + "\n"); + } + } + String str = sb.toString(); + return str.charAt(str.length() - 1) == '\n' ? str.substring(0, str.length() - 1) : str; // strip newline + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((minerTx == null) ? 0 : minerTx.hashCode()); + result = prime * result + ((hex == null) ? 0 : hex.hashCode()); + result = prime * result + ((txIds == null) ? 0 : txIds.hashCode()); + result = prime * result + ((txs == null) ? 0 : txs.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!super.equals(obj)) return false; + if (getClass() != obj.getClass()) return false; + MoneroBlock other = (MoneroBlock) obj; + if (minerTx == null) { + if (other.minerTx != null) return false; + } else if (!minerTx.equals(other.minerTx)) return false; + if (hex == null) { + if (other.hex != null) return false; + } else if (!hex.equals(other.hex)) return false; + if (txIds == null) { + if (other.txIds != null) return false; + } else if (!txIds.equals(other.txIds)) return false; + if (txs == null) { + if (other.txs != null) return false; + } else if (!txs.equals(other.txs)) return false; + return true; + } + + // ------------------- OVERRIDE CO-VARIANT RETURN TYPES --------------------- + + public MoneroBlock setId(String id) { + super.setId(id); + return this; + } + + @Override + public MoneroBlock setHeight(Long height) { + super.setHeight(height); + return this; + } + + @Override + public MoneroBlock setTimestamp(Long timestamp) { + super.setTimestamp(timestamp); + return this; + } + + @Override + public MoneroBlock setSize(Long size) { + super.setSize(size); + return this; + } + + @Override + public MoneroBlock setWeight(Long weight) { + super.setWeight(weight); + return this; + } + + @Override + public MoneroBlock setLongTermWeight(Long longTermWeight) { + super.setLongTermWeight(longTermWeight); + return this; + } + + @Override + public MoneroBlock setDepth(Long depth) { + super.setDepth(depth); + return this; + } + + @Override + public MoneroBlock setDifficulty(BigInteger difficulty) { + super.setDifficulty(difficulty); + return this; + } + + @Override + public MoneroBlock setCumulativeDifficulty(BigInteger cumulativeDifficulty) { + super.setCumulativeDifficulty(cumulativeDifficulty); + return this; + } + + @Override + public MoneroBlock setMajorVersion(Integer majorVersion) { + super.setMajorVersion(majorVersion); + return this; + } + + @Override + public MoneroBlock setMinorVersion(Integer minorVersion) { + super.setMinorVersion(minorVersion); + return this; + } + + @Override + public MoneroBlock setNonce(Integer nonce) { + super.setNonce(nonce); + return this; + } + + @Override + public MoneroBlock setNumTxs(Integer numTxs) { + super.setNumTxs(numTxs); + return this; + } + + @Override + public MoneroBlock setOrphanStatus(Boolean orphanStatus) { + super.setOrphanStatus(orphanStatus); + return this; + } + + @Override + public MoneroBlock setPrevId(String prevId) { + super.setPrevId(prevId); + return this; + } + + @Override + public MoneroBlock setReward(BigInteger reward) { + super.setReward(reward); + return this; + } + + @Override + public MoneroBlock setPowHash(String powHash) { + super.setPowHash(powHash); + return this; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroBlockHeader.java b/core/src/main/java/monero/daemon/model/MoneroBlockHeader.java new file mode 100644 index 00000000000..afa27d642b0 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroBlockHeader.java @@ -0,0 +1,368 @@ +package monero.daemon.model; + +import static org.junit.Assert.assertNotNull; + +import java.math.BigInteger; + +import monero.utils.MoneroUtils; + +/** + * Models a Monero block header which contains information about the block. + */ +public class MoneroBlockHeader { + + private String id; + private Long height; + private Long timestamp; + private Long size; + private Long weight; + private Long longTermWeight; + private Long depth; + private BigInteger difficulty; + private BigInteger cumulativeDifficulty; + private Integer majorVersion; + private Integer minorVersion; + private Integer nonce; + private String minerTxId; + private Integer numTxs; + private Boolean orphanStatus; + private String prevId; + private BigInteger reward; + private String powHash; + + public MoneroBlockHeader() { + super(); + } + + public MoneroBlockHeader(MoneroBlockHeader header) { + this.id = header.id; + this.height = header.height; + this.timestamp = header.timestamp; + this.size = header.size; + this.weight = header.weight; + this.longTermWeight = header.longTermWeight; + this.depth = header.depth; + this.difficulty = header.difficulty; + this.cumulativeDifficulty = header.cumulativeDifficulty; + this.majorVersion = header.majorVersion; + this.minorVersion = header.minorVersion; + this.nonce = header.nonce; + this.numTxs = header.numTxs; + this.orphanStatus = header.orphanStatus; + this.prevId = header.prevId; + this.reward = header.reward; + this.powHash = header.powHash; + } + + public String getId() { + return id; + } + + public MoneroBlockHeader setId(String id) { + this.id = id; + return this; + } + + /** + * Return the block's height which is the total number of blocks that have occurred before. + * + * @return the block's height + */ + public Long getHeight() { + return height; + } + + /** + * Set the block's height which is the total number of blocks that have occurred before. + * + * @param height is the block's height to set + * @return a reference to this header for chaining + */ + public MoneroBlockHeader setHeight(Long height) { + this.height = height; + return this; + } + + public Long getTimestamp() { + return timestamp; + } + + public MoneroBlockHeader setTimestamp(Long timestamp) { + this.timestamp = timestamp; + return this; + } + + public Long getSize() { + return size; + } + + public MoneroBlockHeader setSize(Long size) { + this.size = size; + return this; + } + + public Long getWeight() { + return weight; + } + + public MoneroBlockHeader setWeight(Long weight) { + this.weight = weight; + return this; + } + + public Long getLongTermWeight() { + return longTermWeight; + } + + public MoneroBlockHeader setLongTermWeight(Long longTermWeight) { + this.longTermWeight = longTermWeight; + return this; + } + + public Long getDepth() { + return depth; + } + + public MoneroBlockHeader setDepth(Long depth) { + this.depth = depth; + return this; + } + + public BigInteger getDifficulty() { + return difficulty; + } + + public MoneroBlockHeader setDifficulty(BigInteger difficulty) { + this.difficulty = difficulty; + return this; + } + + public BigInteger getCumulativeDifficulty() { + return cumulativeDifficulty; + } + + public MoneroBlockHeader setCumulativeDifficulty(BigInteger cumulativeDifficulty) { + this.cumulativeDifficulty = cumulativeDifficulty; + return this; + } + + public Integer getMajorVersion() { + return majorVersion; + } + + public MoneroBlockHeader setMajorVersion(Integer majorVersion) { + this.majorVersion = majorVersion; + return this; + } + + public Integer getMinorVersion() { + return minorVersion; + } + + public MoneroBlockHeader setMinorVersion(Integer minorVersion) { + this.minorVersion = minorVersion; + return this; + } + + public Integer getNonce() { + return nonce; + } + + public MoneroBlockHeader setNonce(Integer nonce) { + this.nonce = nonce; + return this; + } + + public String getMinerTxId() { + return minerTxId; + } + + public MoneroBlockHeader setMinerTxId(String minerTxId) { + this.minerTxId = minerTxId; + return this; + } + + public Integer getNumTxs() { + return numTxs; + } + + public MoneroBlockHeader setNumTxs(Integer numTxs) { + this.numTxs = numTxs; + return this; + } + + public Boolean getOrphanStatus() { + return orphanStatus; + } + + public MoneroBlockHeader setOrphanStatus(Boolean orphanStatus) { + this.orphanStatus = orphanStatus; + return this; + } + + public String getPrevId() { + return prevId; + } + + public MoneroBlockHeader setPrevId(String prevId) { + this.prevId = prevId; + return this; + } + + public BigInteger getReward() { + return reward; + } + + public MoneroBlockHeader setReward(BigInteger reward) { + this.reward = reward; + return this; + } + + public String getPowHash() { + return powHash; + } + + public MoneroBlockHeader setPowHash(String powHash) { + this.powHash = powHash; + return this; + } + + public MoneroBlockHeader merge(MoneroBlockHeader header) { + assertNotNull(header); + if (this == header) return this; + this.setId(MoneroUtils.reconcile(this.getId(), header.getId())); + this.setHeight(MoneroUtils.reconcile(this.getHeight(), header.getHeight(), null, null, true)); // height can increase + this.setTimestamp(MoneroUtils.reconcile(this.getTimestamp(), header.getTimestamp(), null, null, true)); // block timestamp can increase + this.setSize(MoneroUtils.reconcile(this.getSize(), header.getSize())); + this.setWeight(MoneroUtils.reconcile(this.getWeight(), header.getWeight())); + this.setDepth(MoneroUtils.reconcile(this.getDepth(), header.getDepth())); + this.setDifficulty(MoneroUtils.reconcile(this.getDifficulty(), header.getDifficulty())); + this.setCumulativeDifficulty(MoneroUtils.reconcile(this.getCumulativeDifficulty(), header.getCumulativeDifficulty())); + this.setMajorVersion(MoneroUtils.reconcile(this.getMajorVersion(), header.getMajorVersion())); + this.setMinorVersion(MoneroUtils.reconcile(this.getMinorVersion(), header.getMinorVersion())); + this.setNonce(MoneroUtils.reconcile(this.getNonce(), header.getNonce())); + this.setMinerTxId(MoneroUtils.reconcile(this.getMinerTxId(), header.getMinerTxId())); + this.setNumTxs(MoneroUtils.reconcile(this.getNumTxs(), header.getNumTxs())); + this.setOrphanStatus(MoneroUtils.reconcile(this.getOrphanStatus(), header.getOrphanStatus())); + this.setPrevId(MoneroUtils.reconcile(this.getPrevId(), header.getPrevId())); + this.setReward(MoneroUtils.reconcile(this.getReward(), header.getReward())); + this.setPowHash(MoneroUtils.reconcile(this.getPowHash(), header.getPowHash())); + return this; + } + + public String toString() { + return toString(0); + } + + public String toString(int indent) { + StringBuilder sb = new StringBuilder(); + sb.append(MoneroUtils.kvLine("Id", getId(), indent)); + sb.append(MoneroUtils.kvLine("Height", getHeight(), indent)); + sb.append(MoneroUtils.kvLine("Timestamp", getTimestamp(), indent)); + sb.append(MoneroUtils.kvLine("Size", getSize(), indent)); + sb.append(MoneroUtils.kvLine("Weight", getWeight(), indent)); + sb.append(MoneroUtils.kvLine("Depth", getDepth(), indent)); + sb.append(MoneroUtils.kvLine("Difficulty", getDifficulty(), indent)); + sb.append(MoneroUtils.kvLine("Cumulative difficulty", getCumulativeDifficulty(), indent)); + sb.append(MoneroUtils.kvLine("Major version", getMajorVersion(), indent)); + sb.append(MoneroUtils.kvLine("Minor version", getMinorVersion(), indent)); + sb.append(MoneroUtils.kvLine("Nonce", getNonce(), indent)); + sb.append(MoneroUtils.kvLine("Miner tx id", getMinerTxId(), indent)); + sb.append(MoneroUtils.kvLine("Num txs", getNumTxs(), indent)); + sb.append(MoneroUtils.kvLine("Orphan status", getOrphanStatus(), indent)); + sb.append(MoneroUtils.kvLine("Prev id", getPrevId(), indent)); + sb.append(MoneroUtils.kvLine("Reward", getReward(), indent)); + sb.append(MoneroUtils.kvLine("Pow hash", getPowHash(), indent)); + String str = sb.toString(); + if (str.isEmpty()) return ""; + return str.charAt(str.length() - 1) == '\n' ? str.substring(0, str.length() - 1) : str; // strip newline + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((minerTxId == null) ? 0 : minerTxId.hashCode()); + result = prime * result + ((cumulativeDifficulty == null) ? 0 : cumulativeDifficulty.hashCode()); + result = prime * result + ((depth == null) ? 0 : depth.hashCode()); + result = prime * result + ((difficulty == null) ? 0 : difficulty.hashCode()); + result = prime * result + ((height == null) ? 0 : height.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((longTermWeight == null) ? 0 : longTermWeight.hashCode()); + result = prime * result + ((majorVersion == null) ? 0 : majorVersion.hashCode()); + result = prime * result + ((minorVersion == null) ? 0 : minorVersion.hashCode()); + result = prime * result + ((nonce == null) ? 0 : nonce.hashCode()); + result = prime * result + ((numTxs == null) ? 0 : numTxs.hashCode()); + result = prime * result + ((orphanStatus == null) ? 0 : orphanStatus.hashCode()); + result = prime * result + ((powHash == null) ? 0 : powHash.hashCode()); + result = prime * result + ((prevId == null) ? 0 : prevId.hashCode()); + result = prime * result + ((reward == null) ? 0 : reward.hashCode()); + result = prime * result + ((size == null) ? 0 : size.hashCode()); + result = prime * result + ((timestamp == null) ? 0 : timestamp.hashCode()); + result = prime * result + ((weight == null) ? 0 : weight.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + MoneroBlockHeader other = (MoneroBlockHeader) obj; + if (minerTxId == null) { + if (other.minerTxId != null) return false; + } else if (!minerTxId.equals(other.minerTxId)) return false; + if (cumulativeDifficulty == null) { + if (other.cumulativeDifficulty != null) return false; + } else if (!cumulativeDifficulty.equals(other.cumulativeDifficulty)) return false; + if (depth == null) { + if (other.depth != null) return false; + } else if (!depth.equals(other.depth)) return false; + if (difficulty == null) { + if (other.difficulty != null) return false; + } else if (!difficulty.equals(other.difficulty)) return false; + if (height == null) { + if (other.height != null) return false; + } else if (!height.equals(other.height)) return false; + if (id == null) { + if (other.id != null) return false; + } else if (!id.equals(other.id)) return false; + if (longTermWeight == null) { + if (other.longTermWeight != null) return false; + } else if (!longTermWeight.equals(other.longTermWeight)) return false; + if (majorVersion == null) { + if (other.majorVersion != null) return false; + } else if (!majorVersion.equals(other.majorVersion)) return false; + if (minorVersion == null) { + if (other.minorVersion != null) return false; + } else if (!minorVersion.equals(other.minorVersion)) return false; + if (nonce == null) { + if (other.nonce != null) return false; + } else if (!nonce.equals(other.nonce)) return false; + if (numTxs == null) { + if (other.numTxs != null) return false; + } else if (!numTxs.equals(other.numTxs)) return false; + if (orphanStatus == null) { + if (other.orphanStatus != null) return false; + } else if (!orphanStatus.equals(other.orphanStatus)) return false; + if (powHash == null) { + if (other.powHash != null) return false; + } else if (!powHash.equals(other.powHash)) return false; + if (prevId == null) { + if (other.prevId != null) return false; + } else if (!prevId.equals(other.prevId)) return false; + if (reward == null) { + if (other.reward != null) return false; + } else if (!reward.equals(other.reward)) return false; + if (size == null) { + if (other.size != null) return false; + } else if (!size.equals(other.size)) return false; + if (timestamp == null) { + if (other.timestamp != null) return false; + } else if (!timestamp.equals(other.timestamp)) return false; + if (weight == null) { + if (other.weight != null) return false; + } else if (!weight.equals(other.weight)) return false; + return true; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroBlockTemplate.java b/core/src/main/java/monero/daemon/model/MoneroBlockTemplate.java new file mode 100644 index 00000000000..ce479e7fca1 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroBlockTemplate.java @@ -0,0 +1,73 @@ +package monero.daemon.model; + +import java.math.BigInteger; + +/** + * Monero block template to mine. + */ +public class MoneroBlockTemplate { + + private String blockTemplateBlob; + private String blockHashingBlob; + private BigInteger difficulty; + private BigInteger expectedReward; + private Long height; + private String prevId; + private Long reservedOffset; + + public String getBlockTemplateBlob() { + return blockTemplateBlob; + } + + public void setBlockTemplateBlob(String blockTemplateBlob) { + this.blockTemplateBlob = blockTemplateBlob; + } + + public String getBlockHashingBlob() { + return blockHashingBlob; + } + + public void setBlockHashingBlob(String blockHashingBlob) { + this.blockHashingBlob = blockHashingBlob; + } + + public BigInteger getDifficulty() { + return difficulty; + } + + public void setDifficulty(BigInteger difficulty) { + this.difficulty = difficulty; + } + + public BigInteger getExpectedReward() { + return expectedReward; + } + + public void setExpectedReward(BigInteger expectedReward) { + this.expectedReward = expectedReward; + } + + public Long getHeight() { + return height; + } + + public void setHeight(Long height) { + this.height = height; + } + + public String getPrevId() { + return prevId; + } + + public void setPrevId(String prevId) { + this.prevId = prevId; + } + + public Long getReservedOffset() { + return reservedOffset; + } + + public void setReservedOffset(Long reservedOffset) { + this.reservedOffset = reservedOffset; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroDaemonConnection.java b/core/src/main/java/monero/daemon/model/MoneroDaemonConnection.java new file mode 100644 index 00000000000..83648167488 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroDaemonConnection.java @@ -0,0 +1,165 @@ +package monero.daemon.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Monero daemon connection. + */ +public class MoneroDaemonConnection { + + private MoneroDaemonPeer peer; + private String id; + private Long avgDownload; + private Long avgUpload; + private Long currentDownload; + private Long currentUpload; + private Long height; + private Boolean isIncoming; + private Long liveTime; + private Boolean isLocalIp; + private Boolean isLocalHost; + private Integer numReceives; + private Integer numSends; + private Long receiveIdleTime; + private Long sendIdleTime; + private String state; + private Integer numSupportFlags; + + public MoneroDaemonPeer getPeer() { + return peer; + } + + public void setPeer(MoneroDaemonPeer peer) { + this.peer = peer; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Long getAvgDownload() { + return avgDownload; + } + + public void setAvgDownload(Long avgDownload) { + this.avgDownload = avgDownload; + } + + public Long getAvgUpload() { + return avgUpload; + } + + public void setAvgUpload(Long avgUpload) { + this.avgUpload = avgUpload; + } + + public Long getCurrentDownload() { + return currentDownload; + } + + public void setCurrentDownload(Long currentDownload) { + this.currentDownload = currentDownload; + } + + public Long getCurrentUpload() { + return currentUpload; + } + + public void setCurrentUpload(Long currentUpload) { + this.currentUpload = currentUpload; + } + + public Long getHeight() { + return height; + } + + public void setHeight(Long height) { + this.height = height; + } + + @JsonProperty("isIncoming") + public Boolean isIncoming() { + return isIncoming; + } + + public void setIsIncoming(Boolean isIncoming) { + this.isIncoming = isIncoming; + } + + public Long getLiveTime() { + return liveTime; + } + + public void setLiveTime(Long liveTime) { + this.liveTime = liveTime; + } + + @JsonProperty("isLocalIp") + public Boolean isLocalIp() { + return isLocalIp; + } + + public void setIsLocalIp(Boolean isLocalIp) { + this.isLocalIp = isLocalIp; + } + + public Boolean isLocalHost() { + return isLocalHost; + } + + public void setIsLocalHost(Boolean isLocalHost) { + this.isLocalHost = isLocalHost; + } + + public Integer getNumReceives() { + return numReceives; + } + + public void setNumReceives(Integer numReceives) { + this.numReceives = numReceives; + } + + public Integer getNumSends() { + return numSends; + } + + public void setNumSends(Integer numSends) { + this.numSends = numSends; + } + + public Long getReceiveIdleTime() { + return receiveIdleTime; + } + + public void setReceiveIdleTime(Long receiveIdleTime) { + this.receiveIdleTime = receiveIdleTime; + } + + public Long getSendIdleTime() { + return sendIdleTime; + } + + public void setSendIdleTime(Long sendIdleTime) { + this.sendIdleTime = sendIdleTime; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public Integer getNumSupportFlags() { + return numSupportFlags; + } + + public void setNumSupportFlags(Integer numSupportFlags) { + this.numSupportFlags = numSupportFlags; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroDaemonConnectionSpan.java b/core/src/main/java/monero/daemon/model/MoneroDaemonConnectionSpan.java new file mode 100644 index 00000000000..4c1b18ef75a --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroDaemonConnectionSpan.java @@ -0,0 +1,71 @@ +package monero.daemon.model; + +/** + * Monero daemon connection span. + */ +public class MoneroDaemonConnectionSpan { + + private String connectionId; + private Long numBlocks; + private String remoteAddress; + private Long rate; + private Long speed; + private Long size; + private Long startBlockHeight; + + public String getConnectionId() { + return connectionId; + } + + public void setConnectionId(String connectionId) { + this.connectionId = connectionId; + } + + public Long getNumBlocks() { + return numBlocks; + } + + public void setNumBlocks(Long numBlocks) { + this.numBlocks = numBlocks; + } + + public String getRemoteAddress() { + return remoteAddress; + } + + public void setRemoteAddress(String remoteAddress) { + this.remoteAddress = remoteAddress; + } + + public Long getRate() { + return rate; + } + + public void setRate(Long rate) { + this.rate = rate; + } + + public Long getSpeed() { + return speed; + } + + public void setSpeed(Long speed) { + this.speed = speed; + } + + public Long getSize() { + return size; + } + + public void setSize(Long size) { + this.size = size; + } + + public Long getStartHeight() { + return startBlockHeight; + } + + public void setStartHeight(Long startHeight) { + this.startBlockHeight = startHeight; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroDaemonInfo.java b/core/src/main/java/monero/daemon/model/MoneroDaemonInfo.java new file mode 100644 index 00000000000..029e4067717 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroDaemonInfo.java @@ -0,0 +1,266 @@ +package monero.daemon.model; + +import java.math.BigInteger; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Monero daemon info. + */ +public class MoneroDaemonInfo { + + private String version; + private Long numAltBlocks; + private Long blockSizeLimit; + private Long blockSizeMedian; + private Long blockWeightLimit; + private Long blockWeightMedian; + private String bootstrapDaemonAddress; + private BigInteger difficulty; + private BigInteger cumulativeDifficulty; + private BigInteger freeSpace; + private Integer numOfflinePeers; + private Integer numOnlinePeers; + private Long height; + private Long heightWithoutBootstrap; + private MoneroNetworkType networkType; + private Boolean isOffline; + private Integer numIncomingConnections; + private Integer numOutgoingConnections; + private Integer numRpcConnections; + private Long startTimestamp; + private Long target; + private Long targetHeight; + private String topBlockId; + private Integer numTxs; + private Integer numTxsPool; + private Boolean wasBootstrapEverUsed; + private Long databaseSize; + private Boolean updateAvailable; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public Long getNumAltBlocks() { + return numAltBlocks; + } + + public void setNumAltBlocks(Long numAltBlocks) { + this.numAltBlocks = numAltBlocks; + } + + public Long getBlockSizeLimit() { + return blockSizeLimit; + } + + public void setBlockSizeLimit(Long blockSizeLimit) { + this.blockSizeLimit = blockSizeLimit; + } + + public Long getBlockSizeMedian() { + return blockSizeMedian; + } + + public void setBlockSizeMedian(Long blockSizeMedian) { + this.blockSizeMedian = blockSizeMedian; + } + + public Long getBlockWeightLimit() { + return blockWeightLimit; + } + + public MoneroDaemonInfo setBlockWeightLimit(Long blockWeightLimit) { + this.blockWeightLimit = blockWeightLimit; + return this; + } + + public Long getBlockWeightMedian() { + return blockWeightMedian; + } + + public void setBlockWeightMedian(Long blockWeightMedian) { + this.blockWeightMedian = blockWeightMedian; + } + + public String getBootstrapDaemonAddress() { + return bootstrapDaemonAddress; + } + + public void setBootstrapDaemonAddress(String bootstrapDaemonAddress) { + this.bootstrapDaemonAddress = bootstrapDaemonAddress; + } + + public BigInteger getDifficulty() { + return difficulty; + } + + public void setDifficulty(BigInteger difficulty) { + this.difficulty = difficulty; + } + + public BigInteger getCumulativeDifficulty() { + return cumulativeDifficulty; + } + + public void setCumulativeDifficulty(BigInteger cumulativeDifficulty) { + this.cumulativeDifficulty = cumulativeDifficulty; + } + + public BigInteger getFreeSpace() { + return freeSpace; + } + + public void setFreeSpace(BigInteger freeSpace) { + this.freeSpace = freeSpace; + } + + public Integer getNumOfflinePeers() { + return numOfflinePeers; + } + + public void setNumOfflinePeers(Integer numOfflinePeers) { + this.numOfflinePeers = numOfflinePeers; + } + + public Integer getNumOnlinePeers() { + return numOnlinePeers; + } + + public void setNumOnlinePeers(Integer numOnlinePeers) { + this.numOnlinePeers = numOnlinePeers; + } + + public Long getHeight() { + return height; + } + + public void setHeight(Long height) { + this.height = height; + } + + public Long getHeightWithoutBootstrap() { + return heightWithoutBootstrap; + } + + public void setHeightWithoutBootstrap(Long heightWithoutBootstrap) { + this.heightWithoutBootstrap = heightWithoutBootstrap; + } + + public MoneroNetworkType getNetworkType() { + return networkType; + } + + public void setNetworkType(MoneroNetworkType networkType) { + this.networkType = networkType; + } + + @JsonProperty("isOffline") + public Boolean isOffline() { + return isOffline; + } + + public void setIsOffline(Boolean isOffline) { + this.isOffline = isOffline; + } + + public Integer getNumIncomingConnections() { + return numIncomingConnections; + } + + public void setNumIncomingConnections(Integer numIncomingConnections) { + this.numIncomingConnections = numIncomingConnections; + } + + public Integer getNumOutgoingConnections() { + return numOutgoingConnections; + } + + public void setNumOutgoingConnections(Integer numOutgoingConnections) { + this.numOutgoingConnections = numOutgoingConnections; + } + + public Integer getNumRpcConnections() { + return numRpcConnections; + } + + public void setNumRpcConnections(Integer numRpcConnections) { + this.numRpcConnections = numRpcConnections; + } + + public Long getStartTimestamp() { + return startTimestamp; + } + + public void setStartTimestamp(Long startTimestamp) { + this.startTimestamp = startTimestamp; + } + + public Long getTarget() { + return target; + } + + public void setTarget(Long target) { + this.target = target; + } + + public Long getTargetHeight() { + return targetHeight; + } + + public void setTargetHeight(Long targetHeight) { + this.targetHeight = targetHeight; + } + + public String getTopBlockId() { + return topBlockId; + } + + public void setTopBlockId(String topBlockId) { + this.topBlockId = topBlockId; + } + + public Integer getNumTxs() { + return numTxs; + } + + public void setNumTxs(Integer numTxs) { + this.numTxs = numTxs; + } + + public Integer getNumTxsPool() { + return numTxsPool; + } + + public void setNumTxsPool(Integer numTxsPool) { + this.numTxsPool = numTxsPool; + } + + public Boolean getWasBootstrapEverUsed() { + return wasBootstrapEverUsed; + } + + public void setWasBootstrapEverUsed(Boolean wasBootstrapEverUsed) { + this.wasBootstrapEverUsed = wasBootstrapEverUsed; + } + + public Long getDatabaseSize() { + return databaseSize; + } + + public void setDatabaseSize(Long databaseSize) { + this.databaseSize = databaseSize; + } + + public Boolean getUpdateAvailable() { + return updateAvailable; + } + + public void setUpdateAvailable(Boolean updateAvailable) { + this.updateAvailable = updateAvailable; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroDaemonListener.java b/core/src/main/java/monero/daemon/model/MoneroDaemonListener.java new file mode 100644 index 00000000000..241c275b880 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroDaemonListener.java @@ -0,0 +1,27 @@ +package monero.daemon.model; + +/** + * Receives notifications as a daemon is updated. + */ +public class MoneroDaemonListener { + + private MoneroBlockHeader lastHeader; + + /** + * Called when a new block is added to the chain. + * + * @param header is the header of the block added to the chain + */ + public void onBlockHeader(MoneroBlockHeader header) { + lastHeader = header; + } + + /** + * Get the last notified block header. + * + * @return the last notified block header + */ + public MoneroBlockHeader getLastBlockHeader() { + return lastHeader; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroDaemonPeer.java b/core/src/main/java/monero/daemon/model/MoneroDaemonPeer.java new file mode 100644 index 00000000000..70b8df5c6f6 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroDaemonPeer.java @@ -0,0 +1,83 @@ +package monero.daemon.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Models a peer to the daemon. + */ +public class MoneroDaemonPeer { + + private String id; + private String address; + private String host; + private Integer port; + private Integer rpcPort; + private Boolean isOnline; + private Long lastSeenTimestamp; + private Integer pruningSeed; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + public Integer getRpcPort() { + return rpcPort; + } + + public void setRpcPort(Integer rpcPort) { + this.rpcPort = rpcPort; + } + + @JsonProperty("isOnline") + public Boolean isOnline() { + return isOnline; + } + + public void setIsOnline(Boolean isOnline) { + this.isOnline = isOnline; + } + + public Long getLastSeenTimestamp() { + return lastSeenTimestamp; + } + + public void setLastSeenTimestamp(Long lastSeenTimestamp) { + this.lastSeenTimestamp = lastSeenTimestamp; + } + + public Integer getPruningSeed() { + return pruningSeed; + } + + public void setPruningSeed(Integer pruningSeed) { + this.pruningSeed = pruningSeed; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroDaemonSyncInfo.java b/core/src/main/java/monero/daemon/model/MoneroDaemonSyncInfo.java new file mode 100644 index 00000000000..37c363ab8a2 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroDaemonSyncInfo.java @@ -0,0 +1,64 @@ +package monero.daemon.model; + +import java.util.List; + +/** + * Models daemon synchronization information. + */ +public class MoneroDaemonSyncInfo { + + private Long height; + private List connections; + private List spans; + private Long targetHeight; + private Integer nextNeededPruningSeed; + private String overview; + + public Long getHeight() { + return height; + } + + public void setHeight(Long height) { + this.height = height; + } + + public List getConnections() { + return connections; + } + + public void setConnections(List connections) { + this.connections = connections; + } + + public List getSpans() { + return spans; + } + + public void setSpans(List spans) { + this.spans = spans; + } + + public Long getTargetHeight() { + return targetHeight; + } + + public void setTargetHeight(Long targetHeight) { + this.targetHeight = targetHeight; + } + + public Integer getNextNeededPruningSeed() { + return nextNeededPruningSeed; + } + + public void setNextNeededPruningSeed(Integer nextNeededPruningSeed) { + this.nextNeededPruningSeed = nextNeededPruningSeed; + } + + public String getOverview() { + return overview; + } + + public void setOverview(String overview) { + this.overview = overview; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroDaemonUpdateCheckResult.java b/core/src/main/java/monero/daemon/model/MoneroDaemonUpdateCheckResult.java new file mode 100644 index 00000000000..6bdaae0df65 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroDaemonUpdateCheckResult.java @@ -0,0 +1,68 @@ +package monero.daemon.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Models the result of checking for a daemon update. + */ +public class MoneroDaemonUpdateCheckResult { + + private Boolean isUpdateAvailable; + private String version; + private String hash; + private String autoUri; + private String userUri; + + public MoneroDaemonUpdateCheckResult() { + // nothing to construct + } + + MoneroDaemonUpdateCheckResult(MoneroDaemonUpdateCheckResult checkResult) { + this.isUpdateAvailable = checkResult.isUpdateAvailable; + this.version = checkResult.version; + this.hash = checkResult.hash; + this.autoUri = checkResult.autoUri; + this.userUri = checkResult.userUri; + } + + @JsonProperty("isUpdateAvailable") + public Boolean isUpdateAvailable() { + return isUpdateAvailable; + } + + public void setIsUpdateAvailable(Boolean isUpdateAvailable) { + this.isUpdateAvailable = isUpdateAvailable; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public String getAutoUri() { + return autoUri; + } + + public void setAutoUri(String autoUri) { + this.autoUri = autoUri; + } + + public String getUserUri() { + return userUri; + } + + public void setUserUri(String userUri) { + this.userUri = userUri; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroDaemonUpdateDownloadResult.java b/core/src/main/java/monero/daemon/model/MoneroDaemonUpdateDownloadResult.java new file mode 100644 index 00000000000..77b33a03765 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroDaemonUpdateDownloadResult.java @@ -0,0 +1,21 @@ +package monero.daemon.model; + +/** + * Models the result of downloading an update. + */ +public class MoneroDaemonUpdateDownloadResult extends MoneroDaemonUpdateCheckResult { + + private String downloadPath; + + public MoneroDaemonUpdateDownloadResult(MoneroDaemonUpdateCheckResult checkResult) { + super(checkResult); + } + + public String getDownloadPath() { + return downloadPath; + } + + public void setDownloadPath(String downloadPath) { + this.downloadPath = downloadPath; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroHardForkInfo.java b/core/src/main/java/monero/daemon/model/MoneroHardForkInfo.java new file mode 100644 index 00000000000..c9c440af35b --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroHardForkInfo.java @@ -0,0 +1,83 @@ +package monero.daemon.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Monero hard fork info. + */ +public class MoneroHardForkInfo { + + private Long earliestHeight; + private Boolean isEnabled; + private Integer state; + private Integer threshold; + private Integer version; + private Integer numVotes; + private Integer window; + private Integer voting; + + public Long getEarliestHeight() { + return earliestHeight; + } + + public void setEarliestHeight(Long earliestHeight) { + this.earliestHeight = earliestHeight; + } + + @JsonProperty("isEnabled") + public Boolean isEnabled() { + return isEnabled; + } + + public void setIsEnabled(Boolean isEnabled) { + this.isEnabled = isEnabled; + } + + public Integer getState() { + return state; + } + + public void setState(Integer state) { + this.state = state; + } + + public Integer getThreshold() { + return threshold; + } + + public void setThreshold(Integer threshold) { + this.threshold = threshold; + } + + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public Integer getNumVotes() { + return numVotes; + } + + public void setNumVotes(Integer numVotes) { + this.numVotes = numVotes; + } + + public Integer getWindow() { + return window; + } + + public void setWindow(Integer window) { + this.window = window; + } + + public Integer getVoting() { + return voting; + } + + public void setVoting(Integer voting) { + this.voting = voting; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroKeyImage.java b/core/src/main/java/monero/daemon/model/MoneroKeyImage.java new file mode 100644 index 00000000000..fb15d619813 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroKeyImage.java @@ -0,0 +1,98 @@ +package monero.daemon.model; + +import static org.junit.Assert.assertTrue; + +import monero.utils.MoneroUtils; + +/** + * Models a Monero key image. + */ +public class MoneroKeyImage { + + private String hex; + private String signature; + + public MoneroKeyImage() { + // nothing to construct + } + + public MoneroKeyImage(String hex) { + this(hex, null); + } + + public MoneroKeyImage(String hex, String signature) { + this.hex = hex; + this.signature = signature; + } + + public MoneroKeyImage(MoneroKeyImage keyImage) { + this.hex = keyImage.hex; + this.signature = keyImage.signature; + } + + public String getHex() { + return hex; + } + + public MoneroKeyImage setHex(String hex) { + this.hex = hex; + return this; + } + + public String getSignature() { + return signature; + } + + public MoneroKeyImage setSignature(String signature) { + this.signature = signature; + return this; + } + + public MoneroKeyImage copy() { + return new MoneroKeyImage(this); + } + + public MoneroKeyImage merge(MoneroKeyImage keyImage) { + assertTrue(keyImage instanceof MoneroKeyImage); + if (keyImage == this) return this; + this.setHex(MoneroUtils.reconcile(this.getHex(), keyImage.getHex())); + this.setSignature(MoneroUtils.reconcile(this.getSignature(), keyImage.getSignature())); + return this; + } + + public String toString() { + return toString(0); + } + + public String toString(int indent) { + StringBuilder sb = new StringBuilder(); + sb.append(MoneroUtils.kvLine("Hex", getHex(), indent)); + sb.append(MoneroUtils.kvLine("Signature", getSignature(), indent)); + String str = sb.toString(); + return str.substring(0, str.length() - 1); // strip newline + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((hex == null) ? 0 : hex.hashCode()); + result = prime * result + ((signature == null) ? 0 : signature.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + MoneroKeyImage other = (MoneroKeyImage) obj; + if (hex == null) { + if (other.hex != null) return false; + } else if (!hex.equals(other.hex)) return false; + if (signature == null) { + if (other.signature != null) return false; + } else if (!signature.equals(other.signature)) return false; + return true; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroKeyImageSpentStatus.java b/core/src/main/java/monero/daemon/model/MoneroKeyImageSpentStatus.java new file mode 100644 index 00000000000..a90490980bb --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroKeyImageSpentStatus.java @@ -0,0 +1,19 @@ +package monero.daemon.model; + +import monero.utils.MoneroException; + +/** + * Enumerate key image spent statuses. + */ +public enum MoneroKeyImageSpentStatus { + NOT_SPENT, + CONFIRMED, + TX_POOL; + + public static MoneroKeyImageSpentStatus valueOf(int status) { + if (status == 0) return NOT_SPENT; + else if (status == 1) return CONFIRMED; + else if (status == 2) return TX_POOL; + throw new MoneroException("Invalid integer value for spent status: " + status); + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroMinerTxSum.java b/core/src/main/java/monero/daemon/model/MoneroMinerTxSum.java new file mode 100644 index 00000000000..b9b0bc600dc --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroMinerTxSum.java @@ -0,0 +1,28 @@ +package monero.daemon.model; + +import java.math.BigInteger; + +/** + * Model for the summation of miner emissions and fees. + */ +public class MoneroMinerTxSum { + + private BigInteger emissionSum; + private BigInteger feeSum; + + public BigInteger getEmissionSum() { + return emissionSum; + } + + public void setEmissionSum(BigInteger emissionSum) { + this.emissionSum = emissionSum; + } + + public BigInteger getFeeSum() { + return feeSum; + } + + public void setFeeSum(BigInteger feeSum) { + this.feeSum = feeSum; + } +} \ No newline at end of file diff --git a/core/src/main/java/monero/daemon/model/MoneroMiningStatus.java b/core/src/main/java/monero/daemon/model/MoneroMiningStatus.java new file mode 100644 index 00000000000..826785c12fa --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroMiningStatus.java @@ -0,0 +1,57 @@ +package monero.daemon.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Monero daemon mining status. + */ +public class MoneroMiningStatus { + + private Boolean isActive; + private Boolean isBackground; + private String address; + private Long speed; + private Integer numThreads; + + @JsonProperty("isActive") + public Boolean isActive() { + return isActive; + } + + public void setIsActive(Boolean isActive) { + this.isActive = isActive; + } + + @JsonProperty("isBackground") + public Boolean isBackground() { + return isBackground; + } + + public void setIsBackground(Boolean isBackground) { + this.isBackground = isBackground; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public Long getSpeed() { + return speed; + } + + public void setSpeed(Long speed) { + this.speed = speed; + } + + public Integer getNumThreads() { + return numThreads; + } + + public void setNumThreads(Integer numThreads) { + this.numThreads = numThreads; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroNetworkType.java b/core/src/main/java/monero/daemon/model/MoneroNetworkType.java new file mode 100644 index 00000000000..d00fb7f3d95 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroNetworkType.java @@ -0,0 +1,10 @@ +package monero.daemon.model; + +/** + * Enumerates daemon networks. + */ +public enum MoneroNetworkType { + MAINNET, + TESTNET, + STAGENET +} diff --git a/core/src/main/java/monero/daemon/model/MoneroOutput.java b/core/src/main/java/monero/daemon/model/MoneroOutput.java new file mode 100644 index 00000000000..71901cf8773 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroOutput.java @@ -0,0 +1,167 @@ +package monero.daemon.model; + +import static org.junit.Assert.assertTrue; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonBackReference; + +import monero.utils.MoneroUtils; + +/** + * Models a Monero transaction output. + */ +public class MoneroOutput { + + private MoneroTx tx; + private MoneroKeyImage keyImage; + private BigInteger amount; + private Integer index; + private List ringOutputIndices; + private String stealthPublicKey; + + public MoneroOutput() { + // nothing to build + } + + public MoneroOutput(final MoneroOutput output) { + if (output.keyImage != null) this.keyImage = output.keyImage.copy(); + this.amount = output.amount; + this.index = output.index; + if (output.ringOutputIndices != null) this.ringOutputIndices = new ArrayList(output.ringOutputIndices); + this.stealthPublicKey = output.stealthPublicKey; + } + + public MoneroOutput copy() { + return new MoneroOutput(this); + } + + @JsonBackReference + public MoneroTx getTx() { + return tx; + } + + public MoneroOutput setTx(MoneroTx tx) { + this.tx = tx; + return this; + } + + public MoneroKeyImage getKeyImage() { + return keyImage; + } + + public MoneroOutput setKeyImage(MoneroKeyImage keyImage) { + this.keyImage = keyImage; + return this; + } + + public BigInteger getAmount() { + return amount; + } + + public MoneroOutput setAmount(BigInteger amount) { + this.amount = amount; + return this; + } + + public Integer getIndex() { + return index; + } + + public MoneroOutput setIndex(Integer index) { + this.index = index; + return this; + } + + public List getRingOutputIndices() { + return ringOutputIndices; + } + + public MoneroOutput setRingOutputIndices(List ringOutputIndices) { + this.ringOutputIndices = ringOutputIndices; + return this; + } + + public String getStealthPublicKey() { + return stealthPublicKey; + } + + public MoneroOutput setStealthPublicKey(String stealthPublicKey) { + this.stealthPublicKey = stealthPublicKey; + return this; + } + + public String toString() { + return toString(0); + } + + public MoneroOutput merge(MoneroOutput output) { + assertTrue(output instanceof MoneroOutput); + if (this == output) return this; + + // merge txs if they're different which comes back to merging outputs + if (this.getTx() != output.getTx()) this.getTx().merge(output.getTx()); + + // otherwise merge output fields + else { + if (this.getKeyImage() == null) this.setKeyImage(output.getKeyImage()); + else if (output.getKeyImage() != null) this.getKeyImage().merge(output.getKeyImage()); + this.setAmount(MoneroUtils.reconcile(this.getAmount(), output.getAmount())); + this.setIndex(MoneroUtils.reconcile(this.getIndex(), output.getIndex())); + } + + return this; + } + + public String toString(int indent) { + StringBuilder sb = new StringBuilder(); + if (getKeyImage() != null) { + sb.append(MoneroUtils.kvLine("Key image", "", indent)); + sb.append(getKeyImage().toString(indent + 1) + "\n"); + } + sb.append(MoneroUtils.kvLine("Amount", getAmount(), indent)); + sb.append(MoneroUtils.kvLine("Index", getIndex(), indent)); + sb.append(MoneroUtils.kvLine("Ring output indices", getRingOutputIndices(), indent)); + sb.append(MoneroUtils.kvLine("Stealth public key", getStealthPublicKey(), indent)); + String str = sb.toString(); + return str.isEmpty() ? str : str.substring(0, str.length() - 1); // strip newline + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((amount == null) ? 0 : amount.hashCode()); + result = prime * result + ((index == null) ? 0 : index.hashCode()); + result = prime * result + ((keyImage == null) ? 0 : keyImage.hashCode()); + result = prime * result + ((ringOutputIndices == null) ? 0 : ringOutputIndices.hashCode()); + result = prime * result + ((stealthPublicKey == null) ? 0 : stealthPublicKey.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + MoneroOutput other = (MoneroOutput) obj; + if (amount == null) { + if (other.amount != null) return false; + } else if (!amount.equals(other.amount)) return false; + if (index == null) { + if (other.index != null) return false; + } else if (!index.equals(other.index)) return false; + if (keyImage == null) { + if (other.keyImage != null) return false; + } else if (!keyImage.equals(other.keyImage)) return false; + if (ringOutputIndices == null) { + if (other.ringOutputIndices != null) return false; + } else if (!ringOutputIndices.equals(other.ringOutputIndices)) return false; + if (stealthPublicKey == null) { + if (other.stealthPublicKey != null) return false; + } else if (!stealthPublicKey.equals(other.stealthPublicKey)) return false; + return true; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroOutputDistributionEntry.java b/core/src/main/java/monero/daemon/model/MoneroOutputDistributionEntry.java new file mode 100644 index 00000000000..9ca69dabfcf --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroOutputDistributionEntry.java @@ -0,0 +1,47 @@ +package monero.daemon.model; + +import java.math.BigInteger; +import java.util.List; + +/** + * Monero output distribution entry. + */ +public class MoneroOutputDistributionEntry { + + private BigInteger amount; + private Integer base; + private List distribution; + private Long startHeight; + + public BigInteger getAmount() { + return amount; + } + + public void setAmount(BigInteger amount) { + this.amount = amount; + } + + public Integer getBase() { + return base; + } + + public void setBase(Integer base) { + this.base = base; + } + + public List getDistribution() { + return distribution; + } + + public void setDistribution(List distribution) { + this.distribution = distribution; + } + + public Long getStartHeight() { + return startHeight; + } + + public void setStartHeight(Long startHeight) { + this.startHeight = startHeight; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroOutputHistogramEntry.java b/core/src/main/java/monero/daemon/model/MoneroOutputHistogramEntry.java new file mode 100644 index 00000000000..e22d69bf3ad --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroOutputHistogramEntry.java @@ -0,0 +1,46 @@ +package monero.daemon.model; + +import java.math.BigInteger; + +/** + * Entry in a Monero output histogram (see get_output_histogram of Daemon RPC documentation). + */ +public class MoneroOutputHistogramEntry { + + private BigInteger amount; + private Long numInstances; + private Long numUnlockedInstances; + private Long numRecentInstances; + + public BigInteger getAmount() { + return amount; + } + + public void setAmount(BigInteger amount) { + this.amount = amount; + } + + public Long getNumInstances() { + return numInstances; + } + + public void setNumInstances(Long numInstances) { + this.numInstances = numInstances; + } + + public Long getNumUnlockedInstances() { + return numUnlockedInstances; + } + + public void setNumUnlockedInstances(Long numUnlockedInstances) { + this.numUnlockedInstances = numUnlockedInstances; + } + + public Long getNumRecentInstances() { + return numRecentInstances; + } + + public void setNumRecentInstances(Long numRecentInstances) { + this.numRecentInstances = numRecentInstances; + } +} \ No newline at end of file diff --git a/core/src/main/java/monero/daemon/model/MoneroSubmitTxResult.java b/core/src/main/java/monero/daemon/model/MoneroSubmitTxResult.java new file mode 100644 index 00000000000..d26390c3a01 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroSubmitTxResult.java @@ -0,0 +1,127 @@ +package monero.daemon.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Models the result from submitting a tx to a daemon. + */ +public class MoneroSubmitTxResult { + + private Boolean isGood; + private Boolean isRelayed; + private Boolean isDoubleSpend; + private Boolean isFeeTooLow; + private Boolean isMixinTooLow; + private Boolean hasInvalidInput; + private Boolean hasInvalidOutput; + private Boolean isRct; + private Boolean isOverspend; + private Boolean isTooBig; + private Boolean sanityCheckFailed; + private String reason; + + @JsonProperty("isGood") + public Boolean isGood() { + return isGood; + } + + public void setIsGood(Boolean isGood) { + this.isGood = isGood; + } + + @JsonProperty("isRelayed") + public Boolean isRelayed() { + return isRelayed; + } + + public void setIsRelayed(Boolean isRelayed) { + this.isRelayed = isRelayed; + } + + @JsonProperty("isDoubleSpend") + public Boolean isDoubleSpend() { + return isDoubleSpend; + } + + public void setIsDoubleSpend(Boolean isDoubleSpend) { + this.isDoubleSpend = isDoubleSpend; + } + + @JsonProperty("isFeeTooLow") + public Boolean isFeeTooLow() { + return isFeeTooLow; + } + + public void setIsFeeTooLow(Boolean isFeeTooLow) { + this.isFeeTooLow = isFeeTooLow; + } + + @JsonProperty("isMixinTooLow") + public Boolean isMixinTooLow() { + return isMixinTooLow; + } + + public void setIsMixinTooLow(Boolean isMixinTooLow) { + this.isMixinTooLow = isMixinTooLow; + } + + @JsonProperty("hasInvalidInput") + public Boolean hasInvalidInput() { + return hasInvalidInput; + } + + public void setHasInvalidInput(Boolean hasInvalidInput) { + this.hasInvalidInput = hasInvalidInput; + } + + public Boolean hasInvalidOutput() { + return hasInvalidOutput; + } + + public void setHasInvalidOutput(Boolean hasInvalidOutput) { + this.hasInvalidOutput = hasInvalidOutput; + } + + @JsonProperty("isRct") + public Boolean isRct() { + return isRct; + } + + public void setIsRct(Boolean isRct) { + this.isRct = isRct; + } + + @JsonProperty("isOverspend") + public Boolean isOverspend() { + return isOverspend; + } + + public void setIsOverspend(Boolean isOverspend) { + this.isOverspend = isOverspend; + } + + @JsonProperty("isTooBig") + public Boolean isTooBig() { + return isTooBig; + } + + public void setIsTooBig(Boolean isTooBig) { + this.isTooBig = isTooBig; + } + + public Boolean getSanityCheckFailed() { + return sanityCheckFailed; + } + + public void setSanityCheckFailed(Boolean sanityCheckFailed) { + this.sanityCheckFailed = sanityCheckFailed; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroTx.java b/core/src/main/java/monero/daemon/model/MoneroTx.java new file mode 100644 index 00000000000..9dc7dff3510 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroTx.java @@ -0,0 +1,843 @@ +package monero.daemon.model; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.fasterxml.jackson.annotation.JsonProperty; + +import common.utils.GenUtils; +import monero.utils.MoneroUtils; + +/** + * Represents a transaction on the Monero network. + */ +public class MoneroTx { + + public static final String DEFAULT_PAYMENT_ID = "0000000000000000"; + + private MoneroBlock block; + private String id; + private Integer version; + private Boolean isMinerTx; + private String paymentId; + private BigInteger fee; + private Integer mixin; + private Boolean doNotRelay; + private Boolean isRelayed; + private Boolean isConfirmed; + private Boolean inTxPool; + private Long numConfirmations; + private Long unlockTime; + private Long lastRelayedTimestamp; + private Long receivedTimestamp; + private Boolean isDoubleSpendSeen; + private String key; + private String fullHex; + private String prunedHex; + private String prunableHex; + private String prunableHash; + private Long size; + private Long weight; + private List vins; + private List vouts; + private List outputIndices; + private String metadata; + private int[] extra; + private Object rctSignatures; // TODO: implement + private Object rctSigPrunable; // TODO: implement + private Boolean isKeptByBlock; + private Boolean isFailed; + private Long lastFailedHeight; + private String lastFailedId; + private Long maxUsedBlockHeight; + private String maxUsedBlockId; + private List signatures; + + public MoneroTx() { + // nothing to build + } + + /** + * Construct this transaction as a deep copy of the given transaction. + * + * @param tx is the transaction to make a deep copy of + */ + public MoneroTx(final MoneroTx tx) { + this.id = tx.id; + this.version = tx.version; + this.isMinerTx = tx.isMinerTx; + this.paymentId = tx.paymentId; + this.fee = tx.fee; + this.mixin = tx.mixin; + this.doNotRelay = tx.doNotRelay; + this.isRelayed = tx.isRelayed; + this.isConfirmed = tx.isConfirmed; + this.inTxPool = tx.inTxPool; + this.numConfirmations = tx.numConfirmations; + this.unlockTime = tx.unlockTime; + this.lastRelayedTimestamp = tx.lastRelayedTimestamp; + this.receivedTimestamp = tx.receivedTimestamp; + this.isDoubleSpendSeen = tx.isDoubleSpendSeen; + this.key = tx.key; + this.fullHex = tx.fullHex; + this.prunedHex = tx.prunedHex; + this.prunableHex = tx.prunableHex; + this.prunableHash = tx.prunableHash; + this.size = tx.size; + this.weight = tx.weight; + if (tx.vins != null) { + this.vins = new ArrayList(); + for (MoneroOutput vin : tx.vins) vins.add(vin.copy().setTx(this)); + } + if (tx.vouts != null) { + this.vouts = new ArrayList(); + for (MoneroOutput vout : tx.vouts) vouts.add(vout.copy().setTx(this)); + } + if (tx.outputIndices != null) this.outputIndices = new ArrayList(tx.outputIndices); + this.metadata = tx.metadata; + if (tx.extra != null) this.extra = tx.extra.clone(); + this.rctSignatures = tx.rctSignatures; + this.rctSigPrunable = tx.rctSigPrunable; + this.isKeptByBlock = tx.isKeptByBlock; + this.isFailed = tx.isFailed; + this.lastFailedHeight = tx.lastFailedHeight; + this.lastFailedId = tx.lastFailedId; + this.maxUsedBlockHeight = tx.maxUsedBlockHeight; + this.maxUsedBlockId = tx.maxUsedBlockId; + if (tx.signatures != null) this.signatures = new ArrayList(tx.signatures); + } + + public MoneroTx copy() { + return new MoneroTx(this); + } + + @JsonBackReference("block_txs") + public MoneroBlock getBlock() { + return block; + } + + public MoneroTx setBlock(MoneroBlock block) { + this.block = block; + return this; + } + + public Long getHeight() { + return this.getBlock() == null ? null : this.getBlock().getHeight(); + } + + public String getId() { + return id; + } + + public MoneroTx setId(String id) { + this.id = id; + return this; + } + + public Integer getVersion() { + return version; + } + + public MoneroTx setVersion(Integer version) { + this.version = version; + return this; + } + + @JsonProperty("isMinerTx") + public Boolean isMinerTx() { + return isMinerTx; + } + + public MoneroTx setIsMinerTx(Boolean isMinerTx) { + this.isMinerTx = isMinerTx; + return this; + } + + public String getPaymentId() { + return paymentId; + } + + public MoneroTx setPaymentId(String paymentId) { + this.paymentId = paymentId; + return this; + } + + public BigInteger getFee() { + return fee; + } + + public MoneroTx setFee(BigInteger fee) { + this.fee = fee; + return this; + } + + public Integer getMixin() { + return mixin; + } + + public MoneroTx setMixin(Integer mixin) { + this.mixin = mixin; + return this; + } + + public Boolean getDoNotRelay() { + return doNotRelay; + } + + public MoneroTx setDoNotRelay(Boolean doNotRelay) { + this.doNotRelay = doNotRelay; + return this; + } + + @JsonProperty("isRelayed") + public Boolean isRelayed() { + return isRelayed; + } + + public MoneroTx setIsRelayed(Boolean isRelayed) { + this.isRelayed = isRelayed; + return this; + } + + @JsonProperty("isConfirmed") + public Boolean isConfirmed() { + return isConfirmed; + } + + public MoneroTx setIsConfirmed(Boolean isConfirmed) { + this.isConfirmed = isConfirmed; + return this; + } + + @JsonProperty("inTxPool") + public Boolean inTxPool() { + return inTxPool; + } + + public MoneroTx setInTxPool(Boolean inTxPool) { + this.inTxPool = inTxPool; + return this; + } + + public Long getNumConfirmations() { + return numConfirmations; + } + + public MoneroTx setNumConfirmations(Long numConfirmations) { + this.numConfirmations = numConfirmations; + return this; + } + + public Long getUnlockTime() { + return unlockTime; + } + + public MoneroTx setUnlockTime(Long unlockTime) { + this.unlockTime = unlockTime; + return this; + } + + public Long getLastRelayedTimestamp() { + return lastRelayedTimestamp; + } + + public MoneroTx setLastRelayedTimestamp(Long lastRelayedTimestamp) { + this.lastRelayedTimestamp = lastRelayedTimestamp; + return this; + } + + public Long getReceivedTimestamp() { + return receivedTimestamp; + } + + public MoneroTx setReceivedTimestamp(Long receivedTimestamp) { + this.receivedTimestamp = receivedTimestamp; + return this; + } + + @JsonProperty("isDoubleSpendSeen") + public Boolean isDoubleSpendSeen() { + return isDoubleSpendSeen; + } + + public MoneroTx setIsDoubleSpendSeen(Boolean isDoubleSpend) { + this.isDoubleSpendSeen = isDoubleSpend; + return this; + } + + public String getKey() { + return key; + } + + public MoneroTx setKey(String key) { + this.key = key; + return this; + } + + public String getFullHex() { + return fullHex; + } + + public MoneroTx setFullHex(String fullHex) { + this.fullHex = fullHex; + return this; + } + + public String getPrunedHex() { + return prunedHex; + } + + public MoneroTx setPrunedHex(String prunedHex) { + this.prunedHex = prunedHex; + return this; + } + + public String getPrunableHex() { + return prunableHex; + } + + public MoneroTx setPrunableHex(String prunableHex) { + this.prunableHex = prunableHex; + return this; + } + + public String getPrunableHash() { + return prunableHash; + } + + public MoneroTx setPrunableHash(String prunableHash) { + this.prunableHash = prunableHash; + return this; + } + + public Long getSize() { + return size; + } + + public MoneroTx setSize(Long size) { + this.size = size; + return this; + } + + public Long getWeight() { + return weight; + } + + public MoneroTx setWeight(Long weight) { + this.weight = weight; + return this; + } + + @JsonManagedReference + public List getVins() { + return vins; + } + + public MoneroTx setVins(List vins) { + this.vins = vins; + return this; + } + + @JsonManagedReference + public List getVouts() { + return vouts; + } + + public MoneroTx setVouts(List vouts) { + this.vouts = vouts; + return this; + } + + public List getOutputIndices() { + return outputIndices; + } + + public MoneroTx setOutputIndices(List outputIndices) { + this.outputIndices = outputIndices; + return this; + } + + public String getMetadata() { + return metadata; + } + + public MoneroTx setMetadata(String metadata) { + this.metadata = metadata; + return this; + } + + public int[] getExtra() { + return extra; + } + + public MoneroTx setExtra(int[] extra) { + this.extra = extra; + return this; + } + + public Object getRctSignatures() { + return rctSignatures; + } + + public MoneroTx setRctSignatures(Object rctSignatures) { + this.rctSignatures = rctSignatures; + return this; + } + + public Object getRctSigPrunable() { + return rctSigPrunable; + } + + public MoneroTx setRctSigPrunable(Object rctSigPrunable) { + this.rctSigPrunable = rctSigPrunable; + return this; + } + + @JsonProperty("isKeptByBlock") + public Boolean isKeptByBlock() { + return isKeptByBlock; + } + + public MoneroTx setIsKeptByBlock(Boolean isKeptByBlock) { + this.isKeptByBlock = isKeptByBlock; + return this; + } + + @JsonProperty("isFailed") + public Boolean isFailed() { + return isFailed; + } + + public MoneroTx setIsFailed(Boolean isFailed) { + this.isFailed = isFailed; + return this; + } + + public Long getLastFailedHeight() { + return lastFailedHeight; + } + + public MoneroTx setLastFailedHeight(Long lastFailedHeight) { + this.lastFailedHeight = lastFailedHeight; + return this; + } + + public String getLastFailedId() { + return lastFailedId; + } + + public MoneroTx setLastFailedId(String lastFailedId) { + this.lastFailedId = lastFailedId; + return this; + } + + public Long getMaxUsedBlockHeight() { + return maxUsedBlockHeight; + } + + public MoneroTx setMaxUsedBlockHeight(Long maxUsedBlockHeight) { + this.maxUsedBlockHeight = maxUsedBlockHeight; + return this; + } + + public String getMaxUsedBlockId() { + return maxUsedBlockId; + } + + public MoneroTx setMaxUsedBlockId(String maxUsedBlockId) { + this.maxUsedBlockId = maxUsedBlockId; + return this; + } + + public List getSignatures() { + return signatures; + } + + public MoneroTx setSignatures(List signatures) { + this.signatures = signatures; + return this; + } + + public MoneroTx merge(MoneroTx tx) { + if (this == tx) return this; + + // merge blocks if they're different which comes back to merging txs + if (block != tx.getBlock()) { + if (block == null) { + block = new MoneroBlock(); + block.setTxs(this); + block.setHeight(tx.getHeight()); + } + if (tx.getBlock() == null) { + tx.setBlock(new MoneroBlock()); + tx.getBlock().setTxs(tx); + tx.getBlock().setHeight(getHeight()); + } + block.merge(tx.getBlock()); + return this; + } + + // otherwise merge tx fields + this.setId(MoneroUtils.reconcile(this.getId(), tx.getId())); + this.setVersion(MoneroUtils.reconcile(this.getVersion(), tx.getVersion())); + this.setPaymentId(MoneroUtils.reconcile(this.getPaymentId(), tx.getPaymentId())); + this.setFee(MoneroUtils.reconcile(this.getFee(), tx.getFee())); + this.setMixin(MoneroUtils.reconcile(this.getMixin(), tx.getMixin())); + this.setIsConfirmed(MoneroUtils.reconcile(this.isConfirmed(), tx.isConfirmed(), null, true, null)); + this.setDoNotRelay(MoneroUtils.reconcile(this.getDoNotRelay(), tx.getDoNotRelay(), null, false, null)); // tx can become relayed + this.setIsRelayed(MoneroUtils.reconcile(this.isRelayed(), tx.isRelayed(), null, true, null)); // tx can become relayed + this.setIsDoubleSpendSeen(MoneroUtils.reconcile(this.isDoubleSpendSeen(), tx.isDoubleSpendSeen())); + this.setKey(MoneroUtils.reconcile(this.getKey(), tx.getKey())); + this.setFullHex(MoneroUtils.reconcile(this.getFullHex(), tx.getFullHex())); + this.setPrunedHex(MoneroUtils.reconcile(this.getPrunedHex(), tx.getPrunedHex())); + this.setPrunableHex(MoneroUtils.reconcile(this.getPrunableHex(), tx.getPrunableHex())); + this.setPrunableHash(MoneroUtils.reconcile(this.getPrunableHash(), tx.getPrunableHash())); + this.setSize(MoneroUtils.reconcile(this.getSize(), tx.getSize())); + this.setWeight(MoneroUtils.reconcile(this.getWeight(), tx.getWeight())); + this.setOutputIndices(MoneroUtils.reconcile(this.getOutputIndices(), tx.getOutputIndices())); + this.setMetadata(MoneroUtils.reconcile(this.getMetadata(), tx.getMetadata())); + this.setExtra(MoneroUtils.reconcileIntArrays(this.getExtra(), tx.getExtra())); + this.setRctSignatures(MoneroUtils.reconcile(this.getRctSignatures(), tx.getRctSignatures())); + this.setRctSigPrunable(MoneroUtils.reconcile(this.getRctSigPrunable(), tx.getRctSigPrunable())); + this.setIsKeptByBlock(MoneroUtils.reconcile(this.isKeptByBlock(), tx.isKeptByBlock())); + this.setIsFailed(MoneroUtils.reconcile(this.isFailed(), tx.isFailed())); + this.setLastFailedHeight(MoneroUtils.reconcile(this.getLastFailedHeight(), tx.getLastFailedHeight())); + this.setLastFailedId(MoneroUtils.reconcile(this.getLastFailedId(), tx.getLastFailedId())); + this.setMaxUsedBlockHeight(MoneroUtils.reconcile(this.getMaxUsedBlockHeight(), tx.getMaxUsedBlockHeight())); + this.setMaxUsedBlockId(MoneroUtils.reconcile(this.getMaxUsedBlockId(), tx.getMaxUsedBlockId())); + this.setSignatures(MoneroUtils.reconcile(this.getSignatures(), tx.getSignatures())); + this.setUnlockTime(MoneroUtils.reconcile(this.getUnlockTime(), tx.getUnlockTime())); + this.setNumConfirmations(MoneroUtils.reconcile(this.getNumConfirmations(), tx.getNumConfirmations(), null, null, true)); // num confirmations can increase + + // merge vins + if (tx.getVins() != null) { + for (MoneroOutput merger : tx.getVins()) { + boolean merged = false; + merger.setTx(this); + if (this.getVins() == null) this.setVins(new ArrayList()); + for (MoneroOutput mergee : this.getVins()) { + if (mergee.getKeyImage().getHex().equals(merger.getKeyImage().getHex())) { + mergee.merge(merger); + merged = true; + break; + } + } + if (!merged) this.getVins().add(merger); + } + } + + // merge vouts + if (tx.getVouts() != null) { + for (MoneroOutput vout : tx.getVouts()) vout.setTx(this); + if (this.getVouts() == null) this.setVouts(tx.getVouts()); + else { + + // validate output indices if present + int numIndices = 0; + for (MoneroOutput vout : this.getVouts()) if (vout.getIndex() != null) numIndices++; + for (MoneroOutput vout : tx.getVouts()) if (vout.getIndex() != null) numIndices++; + assertTrue("Some vouts have an output index and some do not", numIndices == 0 || this.getVouts().size() + tx.getVouts().size() == numIndices); + + // merge by output indices if present + if (numIndices > 0) { + for (MoneroOutput merger : tx.getVouts()) { + boolean merged = false; + merger.setTx(this); + if (this.getVouts() == null) this.setVouts(new ArrayList()); + for (MoneroOutput mergee : this.getVouts()) { + if (mergee.getIndex().equals(merger.getIndex())) { + mergee.merge(merger); + merged = true; + break; + } + } + if (!merged) this.getVouts().add(merger); + } + } else { + + // determine if key images present + int numKeyImages = 0; + for (MoneroOutput vout : this.getVouts()) { + if (vout.getKeyImage() != null) { + assertNotNull(vout.getKeyImage().getHex()); + numKeyImages++; + } + } + for (MoneroOutput vout : tx.getVouts()) { + if (vout.getKeyImage() != null) { + assertNotNull(vout.getKeyImage().getHex()); + numKeyImages++; + } + } + assertTrue("Some vouts have a key image and some do not", numKeyImages == 0 || this.getVouts().size() + tx.getVouts().size() == numKeyImages); + + // merge by key images if present + if (numKeyImages > 0) { + for (MoneroOutput merger : tx.getVouts()) { + boolean merged = false; + merger.setTx(this); + if (this.getVouts() == null) this.setVouts(new ArrayList()); + for (MoneroOutput mergee : this.getVouts()) { + if (mergee.getKeyImage().getHex().equals(merger.getKeyImage().getHex())) { + mergee.merge(merger); + merged = true; + break; + } + } + if (!merged) this.getVouts().add(merger); + } + } + + // otherwise merge by position + else { + assertEquals(this.getVouts().size(), tx.getVouts().size()); + for (int i = 0; i < tx.getVouts().size(); i++) { + this.getVouts().get(i).merge(tx.getVouts().get(i)); + } + } + } + } + } + + // handle unrelayed -> relayed -> confirmed + if (this.isConfirmed()) { + this.setInTxPool(false); + this.setReceivedTimestamp(null); + this.setLastRelayedTimestamp(null); + } else { + this.setInTxPool(MoneroUtils.reconcile(this.inTxPool(), tx.inTxPool(), null, true, null)); // unrelayed -> tx pool + this.setReceivedTimestamp(MoneroUtils.reconcile(this.getReceivedTimestamp(), tx.getReceivedTimestamp(), null, null, false)); // take earliest receive time + this.setLastRelayedTimestamp(MoneroUtils.reconcile(this.getLastRelayedTimestamp(), tx.getLastRelayedTimestamp(), null, null, true)); // take latest relay time + } + + return this; // for chaining + } + + public String toString() { + return toString(0); + } + + public String toString(int indent) { + StringBuilder sb = new StringBuilder(); + sb.append(GenUtils.getIndent(indent) + "=== TX ===\n"); + sb.append(MoneroUtils.kvLine("Tx ID: ", getId(), indent)); + sb.append(MoneroUtils.kvLine("Height", getHeight(), indent)); + sb.append(MoneroUtils.kvLine("Version", getVersion(), indent)); + sb.append(MoneroUtils.kvLine("Is miner tx", isMinerTx(), indent)); + sb.append(MoneroUtils.kvLine("Payment ID", getPaymentId(), indent)); + sb.append(MoneroUtils.kvLine("Fee", getFee(), indent)); + sb.append(MoneroUtils.kvLine("Mixin", getMixin(), indent)); + sb.append(MoneroUtils.kvLine("Do not relay", getDoNotRelay(), indent)); + sb.append(MoneroUtils.kvLine("Is relayed", isRelayed(), indent)); + sb.append(MoneroUtils.kvLine("Is confirmed", isConfirmed(), indent)); + sb.append(MoneroUtils.kvLine("In tx pool", inTxPool(), indent)); + sb.append(MoneroUtils.kvLine("Num confirmations", getNumConfirmations(), indent)); + sb.append(MoneroUtils.kvLine("Unlock time", getUnlockTime(), indent)); + sb.append(MoneroUtils.kvLine("Last relayed time", getLastRelayedTimestamp(), indent)); + sb.append(MoneroUtils.kvLine("Received time", getReceivedTimestamp(), indent)); + sb.append(MoneroUtils.kvLine("Is double spend", isDoubleSpendSeen(), indent)); + sb.append(MoneroUtils.kvLine("Key", getKey(), indent)); + sb.append(MoneroUtils.kvLine("Full hex", getFullHex(), indent)); + sb.append(MoneroUtils.kvLine("Pruned hex", getPrunedHex(), indent)); + sb.append(MoneroUtils.kvLine("Prunable hex", getPrunableHex(), indent)); + sb.append(MoneroUtils.kvLine("Prunable hash", getPrunableHash(), indent)); + sb.append(MoneroUtils.kvLine("Size", getSize(), indent)); + sb.append(MoneroUtils.kvLine("Weight", getWeight(), indent)); + sb.append(MoneroUtils.kvLine("Output indices", getOutputIndices(), indent)); + sb.append(MoneroUtils.kvLine("Metadata", getMetadata(), indent)); + sb.append(MoneroUtils.kvLine("Extra", Arrays.toString(getExtra()), indent)); + sb.append(MoneroUtils.kvLine("RCT signatures", getRctSignatures(), indent)); + sb.append(MoneroUtils.kvLine("RCT sig prunable", getRctSigPrunable(), indent)); + sb.append(MoneroUtils.kvLine("Kept by block", isKeptByBlock(), indent)); + sb.append(MoneroUtils.kvLine("Is failed", isFailed(), indent)); + sb.append(MoneroUtils.kvLine("Last failed height", getLastFailedHeight(), indent)); + sb.append(MoneroUtils.kvLine("Last failed id", getLastFailedId(), indent)); + sb.append(MoneroUtils.kvLine("Max used block height", getMaxUsedBlockHeight(), indent)); + sb.append(MoneroUtils.kvLine("Max used block id", getMaxUsedBlockId(), indent)); + sb.append(MoneroUtils.kvLine("Signatures", getSignatures(), indent)); + if (getVins() != null) { + sb.append(MoneroUtils.kvLine("Vins", "", indent)); + for (int i = 0; i < getVins().size(); i++) { + sb.append(MoneroUtils.kvLine(i + 1, "", indent + 1)); + sb.append(getVins().get(i).toString(indent + 2)); + sb.append('\n'); + } + } + if (getVouts() != null) { + sb.append(MoneroUtils.kvLine("Vouts", "", indent)); + for (int i = 0; i < getVouts().size(); i++) { + sb.append(MoneroUtils.kvLine(i + 1, "", indent + 1)); + sb.append(getVouts().get(i).toString(indent + 2)); + sb.append('\n'); + } + } + String str = sb.toString(); + return str.substring(0, str.length() - 1); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((doNotRelay == null) ? 0 : doNotRelay.hashCode()); + result = prime * result + Arrays.hashCode(extra); + result = prime * result + ((fee == null) ? 0 : fee.hashCode()); + result = prime * result + ((fullHex == null) ? 0 : fullHex.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((inTxPool == null) ? 0 : inTxPool.hashCode()); + result = prime * result + ((isMinerTx == null) ? 0 : isMinerTx.hashCode()); + result = prime * result + ((isConfirmed == null) ? 0 : isConfirmed.hashCode()); + result = prime * result + ((isDoubleSpendSeen == null) ? 0 : isDoubleSpendSeen.hashCode()); + result = prime * result + ((isFailed == null) ? 0 : isFailed.hashCode()); + result = prime * result + ((isKeptByBlock == null) ? 0 : isKeptByBlock.hashCode()); + result = prime * result + ((isRelayed == null) ? 0 : isRelayed.hashCode()); + result = prime * result + ((key == null) ? 0 : key.hashCode()); + result = prime * result + ((lastFailedHeight == null) ? 0 : lastFailedHeight.hashCode()); + result = prime * result + ((lastFailedId == null) ? 0 : lastFailedId.hashCode()); + result = prime * result + ((lastRelayedTimestamp == null) ? 0 : lastRelayedTimestamp.hashCode()); + result = prime * result + ((maxUsedBlockHeight == null) ? 0 : maxUsedBlockHeight.hashCode()); + result = prime * result + ((maxUsedBlockId == null) ? 0 : maxUsedBlockId.hashCode()); + result = prime * result + ((metadata == null) ? 0 : metadata.hashCode()); + result = prime * result + ((mixin == null) ? 0 : mixin.hashCode()); + result = prime * result + ((numConfirmations == null) ? 0 : numConfirmations.hashCode()); + result = prime * result + ((outputIndices == null) ? 0 : outputIndices.hashCode()); + result = prime * result + ((paymentId == null) ? 0 : paymentId.hashCode()); + result = prime * result + ((prunableHash == null) ? 0 : prunableHash.hashCode()); + result = prime * result + ((prunableHex == null) ? 0 : prunableHex.hashCode()); + result = prime * result + ((prunedHex == null) ? 0 : prunedHex.hashCode()); + result = prime * result + ((rctSigPrunable == null) ? 0 : rctSigPrunable.hashCode()); + result = prime * result + ((rctSignatures == null) ? 0 : rctSignatures.hashCode()); + result = prime * result + ((receivedTimestamp == null) ? 0 : receivedTimestamp.hashCode()); + result = prime * result + ((signatures == null) ? 0 : signatures.hashCode()); + result = prime * result + ((size == null) ? 0 : size.hashCode()); + result = prime * result + ((unlockTime == null) ? 0 : unlockTime.hashCode()); + result = prime * result + ((version == null) ? 0 : version.hashCode()); + result = prime * result + ((vins == null) ? 0 : vins.hashCode()); + result = prime * result + ((vouts == null) ? 0 : vouts.hashCode()); + result = prime * result + ((weight == null) ? 0 : weight.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + MoneroTx other = (MoneroTx) obj; + if (doNotRelay == null) { + if (other.doNotRelay != null) return false; + } else if (!doNotRelay.equals(other.doNotRelay)) return false; + if (!Arrays.equals(extra, other.extra)) return false; + if (fee == null) { + if (other.fee != null) return false; + } else if (!fee.equals(other.fee)) return false; + if (fullHex == null) { + if (other.fullHex != null) return false; + } else if (!fullHex.equals(other.fullHex)) return false; + if (id == null) { + if (other.id != null) return false; + } else if (!id.equals(other.id)) return false; + if (inTxPool == null) { + if (other.inTxPool != null) return false; + } else if (!inTxPool.equals(other.inTxPool)) return false; + if (isMinerTx == null) { + if (other.isMinerTx != null) return false; + } else if (!isMinerTx.equals(other.isMinerTx)) return false; + if (isConfirmed == null) { + if (other.isConfirmed != null) return false; + } else if (!isConfirmed.equals(other.isConfirmed)) return false; + if (isDoubleSpendSeen == null) { + if (other.isDoubleSpendSeen != null) return false; + } else if (!isDoubleSpendSeen.equals(other.isDoubleSpendSeen)) return false; + if (isFailed == null) { + if (other.isFailed != null) return false; + } else if (!isFailed.equals(other.isFailed)) return false; + if (isKeptByBlock == null) { + if (other.isKeptByBlock != null) return false; + } else if (!isKeptByBlock.equals(other.isKeptByBlock)) return false; + if (isRelayed == null) { + if (other.isRelayed != null) return false; + } else if (!isRelayed.equals(other.isRelayed)) return false; + if (key == null) { + if (other.key != null) return false; + } else if (!key.equals(other.key)) return false; + if (lastFailedHeight == null) { + if (other.lastFailedHeight != null) return false; + } else if (!lastFailedHeight.equals(other.lastFailedHeight)) return false; + if (lastFailedId == null) { + if (other.lastFailedId != null) return false; + } else if (!lastFailedId.equals(other.lastFailedId)) return false; + if (lastRelayedTimestamp == null) { + if (other.lastRelayedTimestamp != null) return false; + } else if (!lastRelayedTimestamp.equals(other.lastRelayedTimestamp)) return false; + if (maxUsedBlockHeight == null) { + if (other.maxUsedBlockHeight != null) return false; + } else if (!maxUsedBlockHeight.equals(other.maxUsedBlockHeight)) return false; + if (maxUsedBlockId == null) { + if (other.maxUsedBlockId != null) return false; + } else if (!maxUsedBlockId.equals(other.maxUsedBlockId)) return false; + if (metadata == null) { + if (other.metadata != null) return false; + } else if (!metadata.equals(other.metadata)) return false; + if (mixin == null) { + if (other.mixin != null) return false; + } else if (!mixin.equals(other.mixin)) return false; + if (numConfirmations == null) { + if (other.numConfirmations != null) return false; + } else if (!numConfirmations.equals(other.numConfirmations)) return false; + if (outputIndices == null) { + if (other.outputIndices != null) return false; + } else if (!outputIndices.equals(other.outputIndices)) return false; + if (paymentId == null) { + if (other.paymentId != null) return false; + } else if (!paymentId.equals(other.paymentId)) return false; + if (prunableHash == null) { + if (other.prunableHash != null) return false; + } else if (!prunableHash.equals(other.prunableHash)) return false; + if (prunableHex == null) { + if (other.prunableHex != null) return false; + } else if (!prunableHex.equals(other.prunableHex)) return false; + if (prunedHex == null) { + if (other.prunedHex != null) return false; + } else if (!prunedHex.equals(other.prunedHex)) return false; + if (rctSigPrunable == null) { + if (other.rctSigPrunable != null) return false; + } else if (!rctSigPrunable.equals(other.rctSigPrunable)) return false; + if (rctSignatures == null) { + if (other.rctSignatures != null) return false; + } else if (!rctSignatures.equals(other.rctSignatures)) return false; + if (receivedTimestamp == null) { + if (other.receivedTimestamp != null) return false; + } else if (!receivedTimestamp.equals(other.receivedTimestamp)) return false; + if (signatures == null) { + if (other.signatures != null) return false; + } else if (!signatures.equals(other.signatures)) return false; + if (size == null) { + if (other.size != null) return false; + } else if (!size.equals(other.size)) return false; + if (unlockTime == null) { + if (other.unlockTime != null) return false; + } else if (!unlockTime.equals(other.unlockTime)) return false; + if (version == null) { + if (other.version != null) return false; + } else if (!version.equals(other.version)) return false; + if (vins == null) { + if (other.vins != null) return false; + } else if (!vins.equals(other.vins)) return false; + if (vouts == null) { + if (other.vouts != null) return false; + } else if (!vouts.equals(other.vouts)) return false; + if (weight == null) { + if (other.weight != null) return false; + } else if (!weight.equals(other.weight)) return false; + return true; + } +} diff --git a/core/src/main/java/monero/daemon/model/MoneroTxBacklogEntry.java b/core/src/main/java/monero/daemon/model/MoneroTxBacklogEntry.java new file mode 100644 index 00000000000..853c730c429 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroTxBacklogEntry.java @@ -0,0 +1,8 @@ +package monero.daemon.model; + +/** + * TODO. + */ +public class MoneroTxBacklogEntry { + +} diff --git a/core/src/main/java/monero/daemon/model/MoneroTxPoolStats.java b/core/src/main/java/monero/daemon/model/MoneroTxPoolStats.java new file mode 100644 index 00000000000..3f133857f93 --- /dev/null +++ b/core/src/main/java/monero/daemon/model/MoneroTxPoolStats.java @@ -0,0 +1,125 @@ +package monero.daemon.model; + +/** + * Models transaction pool statistics. + */ +public class MoneroTxPoolStats { + + private Integer numTxs; + private Integer numNotRelayed; + private Integer numFailing; + private Integer numDoubleSpends; + private Integer num10m; + private Long feeTotal; + private Long bytesMax; + private Long bytesMed; + private Long bytesMin; + private Long bytesTotal; + private Object histo; + private Long histo98pc; + private Long oldestTimestamp; + + public Integer getNumTxs() { + return numTxs; + } + + public void setNumTxs(Integer numTxs) { + this.numTxs = numTxs; + } + + public Integer getNumNotRelayed() { + return numNotRelayed; + } + + public void setNumNotRelayed(Integer numNotRelayed) { + this.numNotRelayed = numNotRelayed; + } + + public Integer getNumFailing() { + return numFailing; + } + + public void setNumFailing(Integer numFailing) { + this.numFailing = numFailing; + } + + public Integer getNumDoubleSpends() { + return numDoubleSpends; + } + + public void setNumDoubleSpends(Integer numDoubleSpends) { + this.numDoubleSpends = numDoubleSpends; + } + + public Integer getNum10m() { + return num10m; + } + + public void setNum10m(Integer num10m) { + this.num10m = num10m; + } + + public Long getFeeTotal() { + return feeTotal; + } + + public void setFeeTotal(Long feeTotal) { + this.feeTotal = feeTotal; + } + + public Long getBytesMax() { + return bytesMax; + } + + public void setBytesMax(Long bytesMax) { + this.bytesMax = bytesMax; + } + + public Long getBytesMed() { + return bytesMed; + } + + public void setBytesMed(Long bytesMed) { + this.bytesMed = bytesMed; + } + + public Long getBytesMin() { + return bytesMin; + } + + public void setBytesMin(Long bytesMin) { + this.bytesMin = bytesMin; + } + + public Long getBytesTotal() { + return bytesTotal; + } + + public void setBytesTotal(Long bytesTotal) { + this.bytesTotal = bytesTotal; + } + + public Object getHisto() { + return histo; + } + + public void setHisto(Object histo) { + this.histo = histo; + } + + public Long getHisto98pc() { + return histo98pc; + } + + public void setHisto98pc(Long histo98pc) { + this.histo98pc = histo98pc; + } + + public Long getOldestTimestamp() { + return oldestTimestamp; + } + + public void setOldestTimestamp(Long oldestTimestamp) { + this.oldestTimestamp = oldestTimestamp; + } +} diff --git a/core/src/main/java/monero/rpc/MoneroRpcConnection.java b/core/src/main/java/monero/rpc/MoneroRpcConnection.java new file mode 100644 index 00000000000..b73801b7f9a --- /dev/null +++ b/core/src/main/java/monero/rpc/MoneroRpcConnection.java @@ -0,0 +1,296 @@ +package monero.rpc; + +import java.math.BigInteger; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.apache.log4j.Logger; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import common.types.HttpException; +import common.utils.JsonUtils; +import common.utils.StreamUtils; +import monero.utils.MoneroCppUtils; +import monero.utils.MoneroException; +import monero.utils.MoneroUtils; + +/** + * Maintains a connection and sends requests to a Monero RPC API. + */ +public class MoneroRpcConnection { + + // logger + private static final Logger LOGGER = Logger.getLogger(MoneroRpcConnection.class); + + // custom mapper to deserialize integers to BigIntegers + public static ObjectMapper MAPPER; + static { + MAPPER = new ObjectMapper(); + MAPPER.setSerializationInclusion(Include.NON_NULL); + MAPPER.configure(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS, true); + } + + // instance variables + private String uri; + private HttpClient client; + private String username; + private String password; + + public MoneroRpcConnection(URI uri) { + this(uri, null, null); + } + + public MoneroRpcConnection(String uri) { + this(uri, null, null); + } + + public MoneroRpcConnection(String uri, String username, String password) { + this((URI) (uri == null ? null : MoneroUtils.parseUri(uri)), username, password); + } + + public MoneroRpcConnection(URI uri, String username, String password) { + if (uri == null) throw new MoneroException("Must provide URI of RPC endpoint"); + this.uri = uri.toString(); + this.username = username; + this.password = password; + if (username != null || password != null) { + CredentialsProvider creds = new BasicCredentialsProvider(); + creds.setCredentials(new AuthScope(uri.getHost(), uri.getPort()), new UsernamePasswordCredentials(username, password)); + this.client = HttpClients.custom().setDefaultCredentialsProvider(creds).build(); + } else { + this.client = HttpClients.createDefault(); + } + } + + public String getUri() { + return uri; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + /** + * Sends a request to the RPC API. + * + * @param method specifies the method to request + * @return the RPC API response as a map + */ + public Map sendJsonRequest(String method) { + return sendJsonRequest(method, (Map) null); + } + + /** + * Sends a request to the RPC API. + * + * @param method specifies the method to request + * @param params specifies input parameters (Map, List, String, etc) + * @return the RPC API response as a map + */ + public Map sendJsonRequest(String method, Object params) { + try { + + // build request body + Map body = new HashMap(); + body.put("jsonrpc", "2.0"); + body.put("id", "0"); + body.put("method", method); + if (params != null) body.put("params", params); + LOGGER.debug("Sending json request with method '" + method + "' and body: " + JsonUtils.serialize(body)); + + // send http request and validate response + HttpPost post = new HttpPost(uri.toString() + "/json_rpc"); + HttpEntity entity = new StringEntity(JsonUtils.serialize(body)); + post.setEntity(entity); + HttpResponse resp = client.execute(post); + validateHttpResponse(resp); + + // deserialize response + Map respMap = JsonUtils.toMap(MAPPER, StreamUtils.streamToString(resp.getEntity().getContent())); + LOGGER.debug("Received response to method '" + method + "': " + JsonUtils.serialize(respMap)); + EntityUtils.consume(resp.getEntity()); + + // check RPC response for errors + validateRpcResponse(respMap, method, params); + return respMap; + } catch (HttpException e1) { + throw e1; + } catch (MoneroRpcException e2) { + throw e2; + } catch (Exception e3) { + //e3.printStackTrace(); + throw new MoneroException(e3); + } + } + + /** + * Sends a RPC request to the given path and with the given paramters. + * + * E.g. "/get_transactions" with params + * + * @param path is the url path of the request to invoke + * @return the request's deserialized response + */ + public MapsendPathRequest(String path) { + return sendPathRequest(path, null); + } + + /** + * Sends a RPC request to the given path and with the given paramters. + * + * E.g. "/get_transactions" with params + * + * @param path is the url path of the request to invoke + * @param params are request parameters sent in the body + * @return the request's deserialized response + */ + public Map sendPathRequest(String path, Map params) { + //System.out.println("sendPathRequest(" + path + ", " + JsonUtils.serialize(params) + ")"); + + try { + + // build request + HttpPost post = new HttpPost(uri.toString() + "/" + path); + if (params != null) { + HttpEntity entity = new StringEntity(JsonUtils.serialize(params)); + post.setEntity(entity); + } + LOGGER.debug("Sending path request with path '" + path + "' and params: " + JsonUtils.serialize(params)); + + // send request and validate response + HttpResponse resp = client.execute(post); + validateHttpResponse(resp); + + // deserialize response + Map respMap = JsonUtils.toMap(MAPPER, StreamUtils.streamToString(resp.getEntity().getContent())); + LOGGER.debug("Received response to path '" + path + "': " + JsonUtils.serialize(respMap)); + EntityUtils.consume(resp.getEntity()); + + // check RPC response for errors + validateRpcResponse(respMap, path, params); + return respMap; + } catch (HttpException e1) { + throw e1; + } catch (MoneroRpcException e2) { + throw e2; + } catch (Exception e3) { + e3.printStackTrace(); + throw new MoneroException(e3); + } + } + + /** + * Sends a binary RPC request. + * + * @param path is the path of the binary RPC method to invoke + * @param params are the request parameters + * @return byte[] is the binary response + */ + public byte[] sendBinaryRequest(String path, Map params) { + + // serialize params to monero's portable binary storage format + byte[] paramsBin = MoneroCppUtils.mapToBinary(params); + + try { + + // build request + HttpPost post = new HttpPost(uri.toString() + "/" + path); + if (paramsBin != null) { + HttpEntity entity = new ByteArrayEntity(paramsBin); + post.setEntity(entity); + } + LOGGER.debug("Sending binary request with path '" + path + "' and params: " + JsonUtils.serialize(params)); + + // send request and validate response + HttpResponse resp = client.execute(post); + validateHttpResponse(resp); + + // deserialize response + return EntityUtils.toByteArray(resp.getEntity()); + +// // send request and store binary response as Uint8Array +// let resp = await this._throttledRequest(opts); +// if (resp.error) throw new MoneroRpcError(resp.error.code, resp.error.message, opts); +// return new Uint8Array(resp, 0, resp.length); + } catch (HttpException e1) { + throw e1; + } catch (MoneroRpcException e2) { + throw e2; + } catch (Exception e3) { + e3.printStackTrace(); + throw new MoneroException(e3); + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((password == null) ? 0 : password.hashCode()); + result = prime * result + ((uri == null) ? 0 : uri.hashCode()); + result = prime * result + ((username == null) ? 0 : username.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + MoneroRpcConnection other = (MoneroRpcConnection) obj; + if (password == null) { + if (other.password != null) return false; + } else if (!password.equals(other.password)) return false; + if (uri == null) { + if (other.uri != null) return false; + } else if (!uri.equals(other.uri)) return false; + if (username == null) { + if (other.username != null) return false; + } else if (!username.equals(other.username)) return false; + return true; + } + + // ------------------------------ STATIC UTILITIES -------------------------- + + private static void validateHttpResponse(HttpResponse resp) { + int code = resp.getStatusLine().getStatusCode(); + if (code < 200 || code > 299) { + String content = null; + try { + content = StreamUtils.streamToString(resp.getEntity().getContent()); + } catch (Exception e) { + // could not get content + } + throw new HttpException(code, code + " " + resp.getStatusLine().getReasonPhrase() + (content == null || content.isEmpty() ? "" : (": " + content))); + } + } + + @SuppressWarnings("unchecked") + private static void validateRpcResponse(Map respMap, String method, Object params) { + Map error = (Map) respMap.get("error"); + if (error == null) return; + String msg = (String) error.get("message"); + int code = ((BigInteger) error.get("code")).intValue(); + throw new MoneroRpcException(msg, code, method, params); + } +} diff --git a/core/src/main/java/monero/rpc/MoneroRpcException.java b/core/src/main/java/monero/rpc/MoneroRpcException.java new file mode 100644 index 00000000000..fa7d9b486fe --- /dev/null +++ b/core/src/main/java/monero/rpc/MoneroRpcException.java @@ -0,0 +1,35 @@ +package monero.rpc; + +import common.utils.JsonUtils; +import monero.utils.MoneroException; + +/** + * Exception when interacting with the Monero daemon or wallet RPC API. + */ +public class MoneroRpcException extends MoneroException { + + private static final long serialVersionUID = -6282368684634114151L; + + private String rpcMethod; + private Object rpcParams; + + public MoneroRpcException(String rpcDescription, Integer rpcCode, String rpcMethod, Object rpcParams) { + super(rpcDescription, rpcCode); + this.rpcMethod = rpcMethod; + this.rpcParams = rpcParams; + } + + public String getRpcMethod() { + return rpcMethod; + } + + public Object getRpcParams() { + return rpcParams; + } + + public String toString() { + String str = super.toString(); + str += "\nRPC request: '" + rpcMethod + "' with params: " + JsonUtils.serialize(rpcParams); + return str; + } +} diff --git a/core/src/main/java/monero/utils/MoneroCppUtils.java b/core/src/main/java/monero/utils/MoneroCppUtils.java new file mode 100644 index 00000000000..90ff4a7cc53 --- /dev/null +++ b/core/src/main/java/monero/utils/MoneroCppUtils.java @@ -0,0 +1,82 @@ +package monero.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.core.type.TypeReference; + +import common.utils.JsonUtils; +import monero.rpc.MoneroRpcConnection; + +/** + * Collection of utilties bridged from Monero Core C++ to Java. + */ +public class MoneroCppUtils { + + static { + System.loadLibrary("monero-java"); + } + + public static byte[] mapToBinary(Map map) { + return jsonToBinaryJni(JsonUtils.serialize(map)); + } + + public static Map binaryToMap(byte[] bin) { + return JsonUtils.deserialize(binaryToJsonJni(bin), new TypeReference>(){}); + } + + @SuppressWarnings("unchecked") + public static Map binaryBlocksToMap(byte[] binBlocks) { + + // convert binary blocks to json then to map + Map map = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, binaryBlocksToJsonJni(binBlocks), new TypeReference>(){}); + + // parse blocks to maps + List> blockMaps = new ArrayList>(); + for (String blockStr : (List) map.get("blocks")) { + blockMaps.add(JsonUtils.deserialize(MoneroRpcConnection.MAPPER, blockStr, new TypeReference>(){})); + } + map.put("blocks", blockMaps); // overwrite block strings + + // parse txs to maps, one array of txs per block + List>> allTxs = new ArrayList>>(); + List rpcAllTxs = (List) map.get("txs"); + for (Object rpcTxs : rpcAllTxs) { + if ("".equals(rpcTxs)) { + allTxs.add(new ArrayList>()); + } else { + List> txs = new ArrayList>(); + allTxs.add(txs); + for (String rpcTx : (List) rpcTxs) { + txs.add(JsonUtils.deserialize(MoneroRpcConnection.MAPPER, rpcTx.replaceFirst(",", "{") + "}", new TypeReference>(){})); // modify tx string to proper json and parse // TODO: more efficient way than this json manipulation? + } + } + } + map.put("txs", allTxs); // overwrite tx strings + + // return map containing blocks and txs as maps + return map; + } + + public static void initLogging(String path, int level, boolean console) { + initLoggingJni(path, console); + setLogLevelJni(level); + } + + public static void setLogLevel(int level) { + setLogLevelJni(level); + } + + // ------------------------------- NATIVE METHODS --------------------------- + + private native static byte[] jsonToBinaryJni(String json); + + private native static String binaryToJsonJni(byte[] bin); + + private native static String binaryBlocksToJsonJni(byte[] binBlocks); + + private native static void initLoggingJni(String path, boolean console); + + private native static void setLogLevelJni(int level); +} diff --git a/core/src/main/java/monero/utils/MoneroException.java b/core/src/main/java/monero/utils/MoneroException.java new file mode 100644 index 00000000000..6af5020370b --- /dev/null +++ b/core/src/main/java/monero/utils/MoneroException.java @@ -0,0 +1,52 @@ +package monero.utils; + +import static org.junit.Assert.assertNotNull; + +/** + * Exception when interacting with a Monero wallet or daemon. + */ +public class MoneroException extends RuntimeException { + + private static final long serialVersionUID = -6282368684634114151L; + + private Integer code; + + /** + * Construct the exception with an existing exception. + * + * @param e is the existing exception + */ + public MoneroException(Throwable e) { + super(e); + } + + /** + * Construct the exception. + * + * @param message is a human-readable description of the error + */ + public MoneroException(String message) { + this(message, null); + } + + /** + * Construct the exception. + * + * @param message is a human-readable description of the error + * @param code is the error code (optional) + */ + public MoneroException(String message, Integer code) { + super(message); + assertNotNull("Exeption message cannot be null", message); + this.code = code; + } + + public Integer getCode() { + return code; + } + + public String toString() { + if (code == null) return getMessage(); + return code + ": " + getMessage(); + } +} diff --git a/core/src/main/java/monero/utils/MoneroUtils.java b/core/src/main/java/monero/utils/MoneroUtils.java new file mode 100644 index 00000000000..06310497aaa --- /dev/null +++ b/core/src/main/java/monero/utils/MoneroUtils.java @@ -0,0 +1,312 @@ +package monero.utils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.math.BigInteger; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import common.utils.GenUtils; +import monero.daemon.model.MoneroTx; +import monero.wallet.model.MoneroTxWallet; + +/** + * Collection of Monero utilities. + */ +public class MoneroUtils { + + // core wallet2 syncs on a fixed intervals + public static final long WALLET2_REFRESH_INTERVAL = 10000; + + private static final int NUM_MNEMONIC_WORDS = 25; + private static final int VIEW_KEY_LENGTH = 64; + private static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + private static final List CHARS = new ArrayList(); + static { + for (char c : ALPHABET) { + CHARS.add(c); + } + } + + /** + * Validates a wallet seed. + * + * TODO: beef this up + * + * @param seed is the seed to validate + */ + public static void validateSeed(String seed) { + assertNotNull(seed); + assertEquals(64, seed.length()); + } + + /** + * Validates the given mnemonic phrase. + * + * @param mnemonic is the mnemonic to validate + * @throws MoneroException if the given mnemonic is invalid + */ + public static void validateMnemonic(String mnemonic) { + assertNotNull("Mnemonic phrase is not initialized", mnemonic); + assertFalse("Mnemonic phrase is empty", mnemonic.isEmpty()); + String[] words = mnemonic.split(" "); + if (words.length != MoneroUtils.NUM_MNEMONIC_WORDS) throw new Error("Mnemonic phrase is " + words.length + " words but must be " + MoneroUtils.NUM_MNEMONIC_WORDS); + } + + // TODO: improve validation + public static void validatePrivateViewKey(String privateViewKey) { + assertNotNull(privateViewKey); + assertEquals(64, privateViewKey.length()); + } + + // TODO: improve validation + public static void validatePrivateSpendKey(String privateSpendKey) { + assertNotNull(privateSpendKey); + assertEquals(64, privateSpendKey.length()); + } + + // TODO: improve validation + public static void validatePublicViewKey(String publicViewKey) { + assertNotNull(publicViewKey); + assertEquals(64, publicViewKey.length()); + } + + // TODO: improve validation + public static void validatePublicSpendKey(String publicSpendKey) { + assertNotNull(publicSpendKey); + assertEquals(64, publicSpendKey.length()); + } + + // TODO: improve validation + public static void validateAddress(String address) { + assertNotNull(address); + assertFalse(address.isEmpty()); + } + + // TODO: improve validation + public static void validatePaymentId(String paymentId) { + assertTrue(paymentId.length() == 16 || paymentId.length() == 64); + } + + /** + * Validates the given view key. + * + * @param viewKey is the view key to validate + * @throws MoneroException if the given view key is invalid + */ + public static void validateViewKey(String viewKey) { + if (viewKey == null) throw new MoneroException("View key is null"); + if (viewKey.length() != VIEW_KEY_LENGTH) throw new MoneroException("View key is " + viewKey.length() + " characters but must be " + VIEW_KEY_LENGTH); + } + + /** + * Converts the string to a URI. Throws MoneroException if exception. + * + * @param endpoint is the string to convert to a URI + * @return URI is the initialized object from the string endpoint + */ + public static URI parseUri(String endpoint) { + try { + return new URI(endpoint); + } catch (Exception e) { + throw new MoneroException(e); + } + } + + public static void validateHex(String str) { + if (!str.matches("^([0-9A-Fa-f]{2})+$")) throw new MoneroException("Invalid hex: " + str); + } + + public static void validateBase58(String standardAddress) { + for (char c : standardAddress.toCharArray()) { + if (!CHARS.contains((Character) c)) throw new MoneroException("Invalid Base58 " + standardAddress); + } + } + + /** + * Determines if two payment ids are functionally equal. + * + * For example, 03284e41c342f032 and 03284e41c342f032000000000000000000000000000000000000000000000000 are considered equal. + * + * @param paymentId1 is a payment id to compare + * @param paymentId2 is a payment id to compare + * @return true if the payment ids are equal, false otherwise + */ + public static boolean paymentIdsEqual(String paymentId1, String paymentId2) { + int maxLength = Math.max(paymentId1.length(), paymentId2.length()); + for (int i = 0; i < maxLength; i++) { + if (i < paymentId1.length() && i < paymentId2.length() && paymentId1.charAt(i) != paymentId2.charAt(i)) return false; + if (i >= paymentId1.length() && paymentId2.charAt(i) != '0') return false; + if (i >= paymentId2.length() && paymentId1.charAt(i) != '0') return false; + } + return true; + } + + /** + * Convenience method to reconcile two values with default configuration by + * calling reconcile(val1, val2, null, null, null). + * + * @param val1 is a value to reconcile + * @param val2 is a value to reconcile + * @return the reconciled value if reconcilable + * @throws Exception if the values cannot be reconciled + */ + public static T reconcile(T val1, T val2) { + return reconcile(val1, val2, null, null, null); + } + + /** + * Reconciles two values. + * + * @param val1 is a value to reconcile + * @param val2 is a value to reconcile + * @param resolveDefined uses defined value if true or null, null if false + * @param resolveTrue uses true over false if true, false over true if false, must be equal if null + * @param resolveMax uses max over min if true, min over max if false, must be equal if null + * @returns the reconciled value if reconcilable + * @throws Exception if the values cannot be reconciled + */ + @SuppressWarnings("unchecked") + public static T reconcile(T val1, T val2, Boolean resolveDefined, Boolean resolveTrue, Boolean resolveMax) { + + // check for same reference + if (val1 == val2) return val1; + + // check for BigInteger equality + Integer comparison = null; // save comparison for later if applicable + if (val1 instanceof BigInteger && val2 instanceof BigInteger) { + comparison = ((BigInteger) val1).compareTo((BigInteger) val2); + if (comparison == 0) return val1; + } + + // resolve one value null + if (val1 == null || val2 == null) { + if (Boolean.FALSE.equals(resolveDefined)) return null; // use null + else return val1 == null ? val2 : val1; // use defined value + } + + // resolve different booleans + if (resolveTrue != null && Boolean.class.isInstance(val1) && Boolean.class.isInstance(val2)) { + return (T) resolveTrue; + } + + // resolve different numbers + if (resolveMax != null) { + + // resolve BigIntegers + if (val1 instanceof BigInteger && val2 instanceof BigInteger) { + return resolveMax ? (comparison < 0 ? val2 : val1) : (comparison < 0 ? val1 : val2); + } + + // resolve integers + if (val1 instanceof Integer && val2 instanceof Integer) { + return (T) (Integer) (resolveMax ? Math.max((Integer) val1, (Integer) val2) : Math.min((Integer) val1, (Integer) val2)); + } + + // resolve longs + if (val1 instanceof Long && val2 instanceof Long) { + return (T) (Long) (resolveMax ? Math.max((Long) val1, (Long) val2) : Math.min((Long) val1, (Long) val2)); + } + + throw new RuntimeException("Need to resolve primitives and object versions"); +// // resolve js numbers +// if (typeof val1 === "number" && typeof val2 === "number") { +// return config.resolveMax ? Math.max(val1, val2) : Math.min(val1, val2); +// } + } + + // assert deep equality + assertEquals("Cannot reconcile values " + val1 + " and " + val2 + " with config: [" + resolveDefined + ", " + resolveTrue + ", " + resolveMax + "]", val1, val2); + return val1; + } + + /** + * Reconciles two int arrays. The arrays must be identical or an + * exception is thrown. + * + * @param val1 + * @param val2 + * @return + */ + public static int[] reconcileIntArrays(int[] arr1, int[] arr2) { + + // check for same reference or null + if (arr1 == arr2) return arr1; + + // resolve one value defined + if (arr1 == null || arr2 == null) { + return arr1 == null ? arr2 : arr1; + } + + // assert deep equality + assertTrue("Cannot reconcile arrays", Arrays.equals(arr1, arr2)); + return arr1; + } + + + /** + * Returns a human-friendly key value line. + * + * @param key is the key + * @param value is the value + * @param indent indents the line + * @return the human-friendly key value line + */ + public static String kvLine(Object key, Object value, int indent) { + return kvLine(key, value, indent, true, true); + } + + /** + * Returns a human-friendly key value line. + * + * @param key is the key + * @param value is the value + * @param indent indents the line + * @param newline specifies if the string should be terminated with a newline or not + * @param ignoreUndefined specifies if undefined values should return an empty string + * @return the human-friendly key value line + */ + public static String kvLine(Object key, Object value, int indent, boolean newline, boolean ignoreUndefined) { + if (value == null && ignoreUndefined) return ""; + return GenUtils.getIndent(indent) + key + ": " + value + (newline ? '\n' : ""); + } + + /** + * Merges a transaction into a list of existing transactions. + * + * TODO: collapse into MoneroUtils.mergeTx(List txs, ...)? + * + * @param txs are existing transactions to merge into + * @param tx is the transaction to merge into the list + */ + public static void mergeTx(List txs, MoneroTx tx) { + for (MoneroTx aTx : txs) { + if (aTx.getId().equals(tx.getId())) { + aTx.merge(tx); + return; + } + } + txs.add(tx); + } + + /** + * Merges a transaction into a list of existing transactions. + * + * @param txs are existing transactions to merge into + * @param tx is the transaction to merge into the list + */ + public static void mergeTx(List txs, MoneroTxWallet tx) { + for (MoneroTx aTx : txs) { + if (aTx.getId().equals(tx.getId())) { + aTx.merge(tx); + return; + } + } + txs.add(tx); + } +} diff --git a/core/src/main/java/monero/wallet/MoneroWallet.java b/core/src/main/java/monero/wallet/MoneroWallet.java new file mode 100644 index 00000000000..02b69fbe845 --- /dev/null +++ b/core/src/main/java/monero/wallet/MoneroWallet.java @@ -0,0 +1,1161 @@ +/** + * Copyright (c) 2017-2019 woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package monero.wallet; + +import java.math.BigInteger; +import java.util.Collection; +import java.util.List; + +import monero.daemon.model.MoneroKeyImage; +import monero.wallet.model.MoneroAccount; +import monero.wallet.model.MoneroAccountTag; +import monero.wallet.model.MoneroAddressBookEntry; +import monero.wallet.model.MoneroCheckReserve; +import monero.wallet.model.MoneroCheckTx; +import monero.wallet.model.MoneroIncomingTransfer; +import monero.wallet.model.MoneroIntegratedAddress; +import monero.wallet.model.MoneroKeyImageImportResult; +import monero.wallet.model.MoneroMultisigInfo; +import monero.wallet.model.MoneroMultisigInitResult; +import monero.wallet.model.MoneroMultisigSignResult; +import monero.wallet.model.MoneroOutgoingTransfer; +import monero.wallet.model.MoneroOutputQuery; +import monero.wallet.model.MoneroOutputWallet; +import monero.wallet.model.MoneroSendPriority; +import monero.wallet.model.MoneroSendRequest; +import monero.wallet.model.MoneroSubaddress; +import monero.wallet.model.MoneroSyncListener; +import monero.wallet.model.MoneroSyncResult; +import monero.wallet.model.MoneroTransfer; +import monero.wallet.model.MoneroTransferQuery; +import monero.wallet.model.MoneroTxQuery; +import monero.wallet.model.MoneroTxSet; +import monero.wallet.model.MoneroTxWallet; + +/** + * Monero wallet interface. + */ +public interface MoneroWallet { + + public static final String DEFAULT_LANGUAGE = "English"; + + /** + * Get the wallet's path. + * + * @return the path the wallet can be opened with + */ + public String getPath(); + + /** + * Get the wallet's seed. + * + * @return the wallet's seed + */ + public String getSeed(); + + /** + * Get the wallet's mnemonic phrase derived from the seed. + * + * @return the wallet's mnemonic phrase + */ + public String getMnemonic(); + + /** + * Get a list of available languages for the wallet's mnemonic phrase. + * + * @return the available languages for the wallet's mnemonic phrase + */ + public List getLanguages(); + + /** + * Get the wallet's private view key. + * + * @return the wallet's private view key + */ + public String getPrivateViewKey(); + + /** + * Get the wallet's private spend key. + * + * @return the wallet's private spend key + */ + public String getPrivateSpendKey(); + + /** + * Get the wallet's primary address. + * + * @return the wallet's primary address + */ + public String getPrimaryAddress(); + + /** + * Get the address of a specific subaddress. + * + * @param accountIdx specifies the account index of the address's subaddress + * @param subaddressIdx specifies the subaddress index within the account + * @return the receive address of the specified subaddress + */ + public String getAddress(int accountIdx, int subaddressIdx); + + /** + * Get the account and subaddress index of the given address. + * + * @param address is the address to get the account and subaddress index from + * @return the account and subaddress indices + */ + public MoneroSubaddress getAddressIndex(String address); + + /** + * Get an integrated address based on this wallet's primary address and a + * randomly generated payment ID. Generates a random payment ID if none is + * given. + * + * @return the integrated address + */ + public MoneroIntegratedAddress getIntegratedAddress(); + + /** + * Get an integrated address based on this wallet's primary address and the + * given payment ID. Generates a random payment ID if none is given. + * + * @param paymentId is the payment ID to generate an integrated address from (randomly generated if null) + * @return the integrated address + */ + public MoneroIntegratedAddress getIntegratedAddress(String paymentId); + + /** + * Decode an integrated address to get its standard address and payment id. + * + * @param integratedAddress is an integrated address to decode + * @return the decoded integrated address including standard address and payment id + */ + public MoneroIntegratedAddress decodeIntegratedAddress(String integratedAddress); + + /** + * Get the height of the last block processed by the wallet (its index + 1). + * + * @return the height of the last block processed by the wallet + */ + public long getHeight(); + + /** + * Get the blockchain's height. + * + * @return the blockchain's height + */ + public long getDaemonHeight(); + + /** + * Synchronize the wallet with the daemon as a one-time synchronous process. + * + * @return the sync result + */ + public MoneroSyncResult sync(); + + /** + * Synchronize the wallet with the daemon as a one-time synchronous process. + * + * @param listener is invoked as sync progress is made + * @return the sync result + */ + public MoneroSyncResult sync(MoneroSyncListener listener); + + /** + * Synchronize the wallet with the daemon as a one-time synchronous process. + * + * @param startHeight is the start height to sync from (defaults to the last synced block) + * @return the sync result + */ + public MoneroSyncResult sync(Long startHeight); + + /** + * Synchronize the wallet with the daemon as a one-time synchronous process. + * + * @param startHeight is the start height to sync from (defaults to the last synced block) + * @param listener is invoked as sync progress is made + * @return the sync result + */ + public MoneroSyncResult sync(Long startHeight, MoneroSyncListener listener); + + /** + * Start an asynchronous thread to continuously synchronize the wallet with the daemon. + */ + public void startSyncing(); + + /** + * Stop the asynchronous thread to continuously synchronize the wallet with the daemon. + */ + public void stopSyncing(); + + /** + * Rescan the blockchain for spent outputs. + * + * Note: this can only be called with a trusted daemon. + * + * Example use case: peer multisig hex is import when connected to an untrusted daemon, + * so the wallet will not rescan spent outputs. Then the wallet connects to a trusted + * daemon. This method should be manually invoked to rescan outputs. + */ + public void rescanSpent(); + + /** + * Rescan the blockchain from scratch, losing any information which cannot be recovered from + * the blockchain itself. + * + * WARNING: This method discards local wallet data like destination addresses, tx secret keys, + * tx notes, etc. + */ + public void rescanBlockchain(); + + /** + * Get the wallet's balance. + * + * @return the wallet's balance + */ + public BigInteger getBalance(); + + /** + * Get an account's balance. + * + * @param accountIdx is the index of the account to get the balance of + * @return the account's balance + */ + public BigInteger getBalance(int accountIdx); + + /** + * Get a subaddress's balance. + * + * @param accountIdx is the index of the subaddress's account to get the balance of + * @param subaddressIdx is the index of the subaddress to get the balance of + * @return the subaddress's balance + */ + public BigInteger getBalance(int accountIdx, int subaddressIdx); + + /** + * Get the wallet's unlocked balance. + * + * @return the wallet's unlocked balance + */ + public BigInteger getUnlockedBalance(); + + /** + * Get an account's unlocked balance. + * + * @param accountIdx is the index of the account to get the unlocked balance of + * @return the account's unlocked balance + */ + public BigInteger getUnlockedBalance(int accountIdx); + + /** + * Get a subaddress's unlocked balance. + * + * @param accountIdx is the index of the subaddress's account to get the unlocked balance of + * @param subaddressIdx is the index of the subaddress to get the unlocked balance of + * @return the subaddress's balance + */ + public BigInteger getUnlockedBalance(int accountIdx, int subaddressIdx); + + /** + * Get all accounts. + * + * @return all accounts + */ + public List getAccounts(); + + /** + * Get all accounts. + * + * @param includeSubaddresses specifies if subaddresses should be included + * @return all accounts + */ + public List getAccounts(boolean includeSubaddresses); + + /** + * Get accounts with a given tag. + * + * @param tag is the tag for filtering accounts, all accounts if null + * @return all accounts with the given tag + */ + public List getAccounts(String tag); + + /** + * Get accounts with a given tag. + * + * @param includeSubaddresses specifies if subaddresses should be included + * @param tag is the tag for filtering accounts, all accounts if null + * @return all accounts with the given tag + */ + public List getAccounts(boolean includeSubaddresses, String tag); + + /** + * Get an account without subaddress information. + * + * @param accountIdx specifies the account to get + * @return the retrieved account + */ + public MoneroAccount getAccount(int accountIdx); + + /** + * Get an account. + * + * @param accountIdx specifies the account to get + * @param includeSubaddresses specifies if subaddresses should be included + * @return the retrieved account + */ + public MoneroAccount getAccount(int accountIdx, boolean includeSubaddresses); + + /** + * Create a new account. + * + * @return the created account + */ + public MoneroAccount createAccount(); + + /** + * Create a new account with a label for the first subaddress. + * + * @param label specifies the label for account's first subaddress (optional) + * @return the created account + */ + public MoneroAccount createAccount(String label); + + /** + * Get all subaddresses in an account. + * + * @param accountIdx specifies the account to get subaddresses within + * @return the retrieved subaddresses + */ + public List getSubaddresses(int accountIdx); + + /** + * Get subaddresses in an account. + * + * @param accountIdx specifies the account to get subaddresses within + * @param subaddressIndices are specific subaddresses to get (optional) + * @return the retrieved subaddresses + */ + public List getSubaddresses(int accountIdx, List subaddressIndices); + + /** + * Get a subaddress. + * + * @param accountIdx specifies the index of the subaddress's account + * @param subaddressIdx specifies index of the subaddress within the account + * @return the retrieved subaddress + */ + public MoneroSubaddress getSubaddress(int accountIdx, int subaddressIdx); + + /** + * Create a subaddress within an account and without a label. + * + * @param accountIdx specifies the index of the account to create the subaddress within + * @return the created subaddress + */ + public MoneroSubaddress createSubaddress(int accountIdx); + + /** + * Create a subaddress within an account. + * + * @param accountIdx specifies the index of the account to create the subaddress within + * @param label specifies the the label for the subaddress (optional) + * @return the created subaddress + */ + public MoneroSubaddress createSubaddress(int accountIdx, String label); + + /** + * Get a wallet transaction by id. + * + * @param txId is an id of a transaction to get + * @return the identified transactions + */ + public MoneroTxWallet getTx(String txId); + + /** + * Get all wallet transactions. Wallet transactions contain one or more + * transfers that are either incoming or outgoing to the wallet. + * + * @return all wallet transactions + */ + public List getTxs(); + + /** + * Get wallet transactions by id. + * + * @param txIds are ids of transactions to get + * @return the identified transactions + */ + public List getTxs(String... txIds); + + /** + * Get wallet transactions by id. + * + * @param txIds are ids of transactions to get + * @return the identified transactions + */ + public List getTxs(List txIds); + + /** + * Get wallet transactions. Wallet transactions contain one or more + * transfers that are either incoming or outgoing to the wallet. + * + * Query results can be filtered by passing a transaction query. + * Transactions must meet every criteria defined in the query in order to + * be returned. All filtering is optional and no filtering is applied when + * not defined. + * + * @param query specifies attributes of transactions to get + * @return wallet transactions per the query + */ + public List getTxs(MoneroTxQuery query); + + /** + * Get all incoming and outgoing transfers to and from this wallet. An + * outgoing transfer represents a total amount sent from one or more + * subaddresses within an account to individual destination addresses, each + * with their own amount. An incoming transfer represents a total amount + * received into a subaddress within an account. Transfers belong to + * transactions which are stored on the blockchain. + * + * @return all wallet transfers + */ + public List getTransfers(); + + /** + * Get incoming and outgoing transfers to and from an account. An outgoing + * transfer represents a total amount sent from one or more subaddresses + * within an account to individual destination addresses, each with their + * own amount. An incoming transfer represents a total amount received into + * a subaddress within an account. Transfers belong to transactions which + * are stored on the blockchain. + * + * @param accountIdx is the index of the account to get transfers from + * @return transfers to/from the account + */ + public List getTransfers(int accountIdx); + + /** + * Get incoming and outgoing transfers to and from a subaddress. An outgoing + * transfer represents a total amount sent from one or more subaddresses + * within an account to individual destination addresses, each with their + * own amount. An incoming transfer represents a total amount received into + * a subaddress within an account. Transfers belong to transactions which + * are stored on the blockchain. + * + * @param accountIdx is the index of the account to get transfers from + * @param subaddressIdx is the index of the subaddress to get transfers from + * @return transfers to/from the subaddress + */ + public List getTransfers(int accountIdx, int subaddressIdx); + + /** + * Get incoming and outgoing transfers to and from this wallet. An outgoing + * transfer represents a total amount sent from one or more subaddresses + * within an account to individual destination addresses, each with their + * own amount. An incoming transfer represents a total amount received into + * a subaddress within an account. Transfers belong to transactions which + * are stored on the blockchain. + * + * Query results can be filtered by passing in a MoneroTransferQuery. + * Transfers must meet every criteria defined in the query in order to be + * returned. All filtering is optional and no filtering is applied when not + * defined. + * + * @param query specifies attributes of transfers to get + * @return wallet transfers per the query + */ + public List getTransfers(MoneroTransferQuery query); + + /** + * Get all of the wallet's incoming transfers. + * + * @return the wallet's incoming transfers + */ + public List getIncomingTransfers(); + + /** + * Get the wallet's incoming transfers according to the given query. + * + * @param query specifies which incoming transfers to get + * @return the wallet's incoming transfers according to the given query + */ + public List getIncomingTransfers(MoneroTransferQuery query); + + /** + * Get all of the wallet's outgoing transfers. + * + * @return the wallet's outgoing transfers + */ + public List getOutgoingTransfers(); + + /** + * Get the wallet's outgoing transfers according to the given query. + * + * @param query specifies which outgoing transfers to get + * @return the wallet's outgoing transfers according to the given query + */ + public List getOutgoingTransfers(MoneroTransferQuery query); + + /** + * Get outputs created from previous transactions that belong to the wallet + * (i.e. that the wallet can spend one time). Outputs are part of + * transactions which are stored in blocks on the blockchain. + * + * @return List are all wallet outputs + */ + public List getOutputs(); + + /** + * Get outputs created from previous transactions that belong to the wallet + * (i.e. that the wallet can spend one time). Outputs are part of + * transactions which are stored in blocks on the blockchain. + * + * Results can be configured by passing a MoneroOutputQuery. Outputs must + * meet every criteria defined in the query in order to be returned. All + * filtering is optional and no filtering is applied when not defined. + * + * @param query specifies attributes of outputs to get + * @return List are wallet outputs per the query + */ + public List getOutputs(MoneroOutputQuery query); + + /** + * Export all outputs in hex format. + * + * @return all outputs in hex format, null if no outputs + */ + public String getOutputsHex(); + + /** + * Import outputs in hex format. + * + * @param outputsHex are outputs in hex format + * @return the number of outputs imported + */ + public int importOutputsHex(String outputsHex); + + /** + * Get all signed key images. + * + * @return the wallet's signed key images + */ + public List getKeyImages(); + + /** + * Import signed key images and verify their spent status. + * + * @param keyImages are key images to import and verify (requires hex and signature) + * @return results of the import + */ + public MoneroKeyImageImportResult importKeyImages(List keyImages); + + /** + * Get new key images from the last imported outputs. + * + * @return the key images from the last imported outputs + */ + public List getNewKeyImagesFromLastImport(); + + /** + * Create a transaction to transfer funds from this wallet according to the + * given request. The transaction may be relayed later. + * + * @param request configures the transaction to create + * @return a tx set for the requested transaction if possible + */ + public MoneroTxSet createTx(MoneroSendRequest request); + + /** + * Create a transaction to transfers funds from this wallet to a destination address. + * The transaction may be relayed later. + * + * @param accountIndex is the index of the account to withdraw funds from + * @param address is the destination address to send funds to + * @param amount is the amount being sent + * @return a tx set for the requested transaction if possible + */ + public MoneroTxSet createTx(int accountIndex, String address, BigInteger amount); + + /** + * Create a transaction to transfers funds from this wallet to a destination address. + * The transaction may be relayed later. + * + * @param accountIndex is the index of the account to withdraw funds from + * @param address is the destination address to send funds to + * @param amount is the amount being sent + * @param priority is the send priority (default normal) + * @return a tx set for the requested transaction if possible + */ + public MoneroTxSet createTx(int accountIndex, String address, BigInteger amount, MoneroSendPriority priority); + + /** + * Create one or more transactions to transfer funds from this wallet + * according to the given request. The transactions may later be relayed. + * + * @param request configures the transactions to create + * @return a tx set for the requested transactions if possible + */ + public MoneroTxSet createTxs(MoneroSendRequest request); + + /** + * Relay a previously created transaction. + * + * @param txMetadata is transaction metadata previously created without relaying + * @return the id of the relayed tx + */ + public String relayTx(String txMetadata); + + /** + * Relay a previously created transaction. + * + * @param tx is the transaction to relay + * @return the id of the relayed tx + */ + public String relayTx(MoneroTxWallet tx); + + /** + * Relay previously created transactions. + * + * @param txMetadatas are transaction metadata previously created without relaying + * @return the ids of the relayed txs + */ + public List relayTxs(Collection txMetadatas); + + /** + * Relay previously created transactions. + * + * @param txs are the transactions to relay + * @return the ids of the relayed txs + */ + public List relayTxs(List txs); + + /** + * Create and relay a transaction to transfer funds from this wallet + * according to the given request. + * + * @param request configures the transaction + * @return a tx set with the requested transaction if possible + */ + public MoneroTxSet send(MoneroSendRequest request); + + /** + * Create and relay a transaction to transfers funds from this wallet to + * a destination address. + * + * @param accountIndex is the index of the account to withdraw funds from + * @param address is the destination address to send funds to + * @param amount is the amount being sent + * @return a tx set with the requested transaction if possible + */ + public MoneroTxSet send(int accountIndex, String address, BigInteger amount); + + /** + * Create and relay a transaction to transfers funds from this wallet to + * a destination address. + * + * @param accountIndex is the index of the account to withdraw funds from + * @param address is the destination address to send funds to + * @param amount is the amount being sent + * @param priority is the send priority (default normal) + * @return a tx set with the requested transaction if possible + */ + public MoneroTxSet send(int accountIndex, String address, BigInteger amount, MoneroSendPriority priority); + + /** + * Create and relay one or more transactions to transfer funds from this + * wallet according to the given request. + * + * @param request configures the transactions + * @return a tx set with the requested transaction if possible + */ + public MoneroTxSet sendSplit(MoneroSendRequest request); + + /** + * Create and relay one or more transactions which transfer funds from this + * wallet to a destination address. + * + * @param accountIndex is the index of the account to withdraw funds from + * @param address is the destination address to send funds to + * @param amount is the amount being sent + * @return a tx set with the requested transaction if possible + */ + public MoneroTxSet sendSplit(int accountIndex, String address, BigInteger amount); + + /** + * Create and relay one or more transactions to transfer funds from this + * wallet to a destination address with a priority. + * + * @param accountIndex is the index of the account to withdraw funds from + * @param address is the destination address to send funds to + * @param amount is the amount being sent + * @param priority is the send priority (default normal) + * @return a tx set with the requested transaction if possible + */ + public MoneroTxSet sendSplit(int accountIndex, String address, BigInteger amount, MoneroSendPriority priority); + + /** + * Sweep an output with a given key image. + * + * @param request configures the sweep transaction + * @return a tx set with the requested transaction if possible + */ + public MoneroTxSet sweepOutput(MoneroSendRequest request); + + /** + * Sweep an output with a given key image. + * + * @param address is the destination address to send to + * @param keyImage is the key image hex of the output to sweep + * @return a tx set with the requested transaction if possible + */ + public MoneroTxSet sweepOutput(String address, String keyImage); + + /** + * Sweep an output with a given key image. + * + * @param address is the destination address to send to + * @param keyImage is the key image hex of the output to sweep + * @param priority is the transaction priority (optional) + * @return a tx set with the requested transaction if possible + */ + public MoneroTxSet sweepOutput(String address, String keyImage, MoneroSendPriority priority); + + /** + * Sweep a subaddress's unlocked funds to an address. + * + * @param accountIdx is the index of the account + * @param subaddressIdx is the index of the subaddress + * @param address is the address to sweep the subaddress's funds to + * @return a tx set with the requested transactions if possible + */ + public MoneroTxSet sweepSubaddress(int accountIdx, int subaddressIdx, String address); + + /** + * Sweep an acount's unlocked funds to an address. + * + * @param accountIdx is the index of the account + * @param address is the address to sweep the account's funds to + * @return a tx set with the requested transactions if possible + */ + public MoneroTxSet sweepAccount(int accountIdx, String address); + + /** + * Sweep the wallet's unlocked funds to an address. + * + * @param address is the address to sweep the wallet's funds to + * @return the tx sets with the transactions which sweep the wallet + */ + public List sweepWallet(String address); + + /** + * Sweep all unlocked funds according to the given request. + * + * @param request is the sweep configuration + * @return the tx sets with the requested transactions + */ + public List sweepUnlocked(MoneroSendRequest request); + + /** + * Sweep all unmixable dust outputs back to the wallet to make them easier to spend and mix. + * + * NOTE: Dust only exists pre RCT, so this method will throw "no dust to sweep" on new wallets. + * + * @return a tx set with the requested transactions if possible + */ + public MoneroTxSet sweepDust(); + + /** + * Sweep all unmixable dust outputs back to the wallet to make them easier to spend and mix. + * + * @param doNotRelay specifies if the resulting transaction should not be relayed (defaults to false i.e. relayed) + * @return a tx set with the requested transactions if possible + */ + public MoneroTxSet sweepDust(boolean doNotRelay); + + /** + * Sign a message. + * + * @param message is the message to sign + * @return the signature + */ + public String sign(String message); + + /** + * Verify a signature on a message. + * + * @param message is the signed message + * @param address is the signing address + * @param signature is the signature + * @return true if the signature is good, false otherwise + */ + public boolean verify(String message, String address, String signature); + + /** + * Get a transaction's secret key from its id. + * + * @param txId is the transaction's id + * @return is the transaction's secret key + */ + public String getTxKey(String txId); + + /** + * Check a transaction in the blockchain with its secret key. + * + * @param txId specifies the transaction to check + * @param txKey is the transaction's secret key + * @param address is the destination public address of the transaction + * @return the result of the check + */ + public MoneroCheckTx checkTxKey(String txId, String txKey, String address); + + /** + * Get a transaction signature to prove it. + * + * @param txId specifies the transaction to prove + * @param address is the destination public address of the transaction + * @return the transaction signature + */ + public String getTxProof(String txId, String address); + + /** + * Get a transaction signature to prove it. + * + * @param txId specifies the transaction to prove + * @param address is the destination public address of the transaction + * @param message is a message to include with the signature to further authenticate the proof (optional) + * @return the transaction signature + */ + public String getTxProof(String txId, String address, String message); + + /** + * Prove a transaction by checking its signature. + * + * @param txId specifies the transaction to prove + * @param address is the destination public address of the transaction + * @param message is a message included with the signature to further authenticate the proof (optional) + * @param signature is the transaction signature to confirm + * @return the result of the check + */ + public MoneroCheckTx checkTxProof(String txId, String address, String message, String signature); + + /** + * Generate a signature to prove a spend. Unlike proving a transaction, it does not require the destination public address. + * + * @param txId specifies the transaction to prove + * @return the transaction signature + */ + public String getSpendProof(String txId); + + /** + * Generate a signature to prove a spend. Unlike proving a transaction, it does not require the destination public address. + * + * @param txId specifies the transaction to prove + * @param message is a message to include with the signature to further authenticate the proof (optional) + * @return the transaction signature + */ + public String getSpendProof(String txId, String message); + + /** + * Prove a spend using a signature. Unlike proving a transaction, it does not require the destination public address. + * + * @param txId specifies the transaction to prove + * @param message is a message included with the signature to further authenticate the proof (optional) + * @param signature is the transaction signature to confirm + * @return true if the signature is good, false otherwise + */ + public boolean checkSpendProof(String txId, String message, String signature); + + /** + * Generate a signature to prove the entire balance of the wallet. + * + * @param message is a message included with the signature to further authenticate the proof (optional) + * @return the reserve proof signature + */ + public String getReserveProofWallet(String message); + + /** + * Generate a signature to prove an available amount in an account. + * + * @param accountIdx specifies the account to prove ownership of the amount + * @param amount is the minimum amount to prove as available in the account + * @param message is a message to include with the signature to further authenticate the proof (optional) + * @return the reserve proof signature + */ + public String getReserveProofAccount(int accountIdx, BigInteger amount, String message); + + /** + * Proves a wallet has a disposable reserve using a signature. + * + * @param address is the public wallet address + * @param message is a message included with the signature to further authenticate the proof (optional) + * @param signature is the reserve proof signature to check + * @return the result of checking the signature proof + */ + public MoneroCheckReserve checkReserveProof(String address, String message, String signature); + + /** + * Get a transaction note. + * + * @param txId specifies the transaction to get the note of + * @return the tx note + */ + public String getTxNote(String txId); + + /** + * Get notes for multiple transactions. + * + * @param txIds identify the transactions to get notes for + * @return notes for the transactions + */ + public List getTxNotes(Collection txIds); + + /** + * Set a note for a specific transaction. + * + * @param txId specifies the transaction + * @param note specifies the note + */ + public void setTxNote(String txId, String note); + + /** + * Set notes for multiple transactions. + * + * @param txIds specify the transactions to set notes for + * @param notes are the notes to set for the transactions + */ + public void setTxNotes(Collection txIds, Collection notes); + + /** + * Get all address book entries. + * + * @return the address book entries + */ + public List getAddressBookEntries(); + + /** + * Get address book entries. + * + * @param entryIndices are indices of the entries to get + * @return the address book entries + */ + public List getAddressBookEntries(Collection entryIndices); + + /** + * Add an address book entry. + * + * @param address is the entry address + * @param description is the entry description (optional) + * @return the index of the added entry + */ + public int addAddressBookEntry(String address, String description); + + /** + * Add an address book entry. + * + * @param address is the entry address + * @param description is the entry description (optional) + * @param paymentId is the entry paymet id (optional) + * @return the index of the added entry + */ + public int addAddressBookEntry(String address, String description, String paymentId); + + /** + * Delete an address book entry. + * + * @param entryIdx is the index of the entry to delete + */ + public void deleteAddressBookEntry(int entryIdx); + + /** + * Tag accounts. + * + * @param tag is the tag to apply to the specified accounts + * @param accountIndices are the indices of the accounts to tag + */ + public void tagAccounts(String tag, Collection accountIndices); + + /** + * Untag acconts. + * + * @param accountIndices are the indices of the accounts to untag + */ + public void untagAccounts(Collection accountIndices); + + /** + * Return all account tags. + * + * @return the wallet's account tags + */ + public List getAccountTags(); + + /** + * Sets a human-readable description for a tag. + * + * @param tag is the tag to set a description for + * @param label is the label to set for the tag + */ + public void setAccountTagLabel(String tag, String label); + + /** + * Creates a payment URI from a send configuration. + * + * @param request specifies configuration for a potential tx + * @return the payment uri + */ + public String createPaymentUri(MoneroSendRequest request); + + /** + * Parses a payment URI to a send request. + * + * @param uri is the payment uri to parse + * @return the send configuration parsed from the uri + */ + public MoneroSendRequest parsePaymentUri(String uri); + + /** + * Get an attribute. + * + * @param key is the attribute to get the value of + * @return the attribute's value + */ + public String getAttribute(String key); + + /** + * Set an arbitrary attribute. + * + * @param key is the attribute key + * @param val is the attribute value + */ + public void setAttribute(String key, String val); + + /** + * Start mining. + * + * @param numThreads is the number of threads created for mining (optional) + * @param backgroundMining specifies if mining should occur in the background (optional) + * @param ignoreBattery specifies if the battery should be ignored for mining (optional) + */ + public void startMining(Long numThreads, Boolean backgroundMining, Boolean ignoreBattery); + + /** + * Stop mining. + */ + public void stopMining(); + + /** + * Indicates if importing multisig data is needed for returning a correct balance. + * + * @return true if importing multisig data is needed for returning a correct balance, false otherwise + */ + public boolean isMultisigImportNeeded(); + + /** + * Indicates if this wallet is a multisig wallet. + * + * @return true if this is a multisig wallet, false otherwise + */ + public boolean isMultisig(); + + /** + * Get multisig info about this wallet. + * + * @return multisig info about this wallet + */ + public MoneroMultisigInfo getMultisigInfo(); + + /** + * Get multisig info as hex to share with participants to begin creating a + * multisig wallet. + * + * @return this wallet's multisig hex to share with participants + */ + public String prepareMultisig(); + + /** + * Make this wallet multisig by importing multisig hex from participants. + * + * @param multisigHexes are multisig hex from each participant + * @param threshold is the number of signatures needed to sign transfers + * @param password is the wallet password + * @return the result which has the multisig's address xor this wallet's multisig hex to share with participants iff not N/N + */ + public MoneroMultisigInitResult makeMultisig(List multisigHexes, int threshold, String password); + + /** + * Exchange multisig hex with participants in a M/N multisig wallet. + * + * This process must be repeated with participants exactly N-M times. + * + * @param multisigHexes are multisig hex from each participant + * @param password is the wallet's password // TODO monero core: redundant? wallet is created with password + * @return the result which has the multisig's address xor this wallet's multisig hex to share with participants iff not done + */ + public MoneroMultisigInitResult exchangeMultisigKeys(List multisigHexes, String password); + + /** + * Export this wallet's multisig info as hex for other participants. + * + * @return this wallet's multisig info as hex for other participants + */ + public String getMultisigHex(); + + /** + * Import multisig info as hex from other participants. + * + * @param multisigHexes are multisig hex from each participant + * @return the number of outputs signed with the given multisig hex + */ + public int importMultisigHex(List multisigHexes); + + /** + * Sign previously created multisig transactions as represented by hex. + * + * @param multisigTxHex is the hex shared among the multisig transactions when they were created + * @return the result of signing the multisig transactions + */ + public MoneroMultisigSignResult signMultisigTxHex(String multisigTxHex); + + /** + * Submit signed multisig transactions as represented by a hex string. + * + * @param signedMultisigTxHex is the signed multisig hex returned from signMultisigTxs() + * @return the resulting transaction ids + */ + public List submitMultisigTxHex(String signedMultisigTxHex); + + /** + * Save the wallet at its current path. + */ + public void save(); + + /** + * Close the wallet (does not save). + */ + public void close(); + + /** + * Optionally save then close the wallet. + * + * @param save specifies if the wallet should be saved before being closed (default false) + */ + public void close(boolean save); +} \ No newline at end of file diff --git a/core/src/main/java/monero/wallet/MoneroWalletDefault.java b/core/src/main/java/monero/wallet/MoneroWalletDefault.java new file mode 100644 index 00000000000..14eae753222 --- /dev/null +++ b/core/src/main/java/monero/wallet/MoneroWalletDefault.java @@ -0,0 +1,386 @@ +/** + * Copyright (c) 2017-2019 woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package monero.wallet; + +import static org.junit.Assert.assertEquals; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import monero.utils.MoneroException; +import monero.wallet.model.MoneroAccount; +import monero.wallet.model.MoneroAddressBookEntry; +import monero.wallet.model.MoneroIncomingTransfer; +import monero.wallet.model.MoneroIntegratedAddress; +import monero.wallet.model.MoneroOutgoingTransfer; +import monero.wallet.model.MoneroOutputWallet; +import monero.wallet.model.MoneroSendPriority; +import monero.wallet.model.MoneroSendRequest; +import monero.wallet.model.MoneroSubaddress; +import monero.wallet.model.MoneroSyncListener; +import monero.wallet.model.MoneroSyncResult; +import monero.wallet.model.MoneroTransfer; +import monero.wallet.model.MoneroTransferQuery; +import monero.wallet.model.MoneroTxQuery; +import monero.wallet.model.MoneroTxSet; +import monero.wallet.model.MoneroTxWallet; + +/** + * Default implementation of a Monero Wallet. + */ +public abstract class MoneroWalletDefault implements MoneroWallet { + + @Override + public String getPrimaryAddress() { + return getAddress(0, 0); + } + + @Override + public MoneroIntegratedAddress getIntegratedAddress() { + return getIntegratedAddress(null); + } + + @Override + public MoneroSyncResult sync() { + return sync(null, null); + } + + @Override + public MoneroSyncResult sync(MoneroSyncListener listener) { + return sync(null, listener); + } + + @Override + public MoneroSyncResult sync(Long startHeight) { + return sync(startHeight, null); + } + + @Override + public MoneroSyncResult sync(Long startHeight, MoneroSyncListener listener) { + return sync(startHeight, listener); + } + + @Override + public List getAccounts() { + return getAccounts(false, null); + } + + @Override + public List getAccounts(boolean includeSubaddresses) { + return getAccounts(includeSubaddresses, null); + } + + @Override + public List getAccounts(String tag) { + return getAccounts(false, tag); + } + + @Override + public MoneroAccount getAccount(int accountIdx) { + return getAccount(accountIdx, false); + } + + @Override + public MoneroAccount createAccount() { + return createAccount(null); + } + + @Override + public List getSubaddresses(int accountIdx) { + return getSubaddresses(accountIdx, null); + } + + @Override + public MoneroSubaddress getSubaddress(int accountIdx, int subaddressIdx) { + List subaddresses = getSubaddresses(accountIdx, Arrays.asList(subaddressIdx)); + if (subaddresses.isEmpty()) throw new MoneroException("Subaddress at index " + subaddressIdx + " is not initialized"); + assertEquals("Only 1 subaddress should be returned", 1, subaddresses.size()); + return subaddresses.get(0); + } + + @Override + public MoneroSubaddress createSubaddress(int accountIdx) { + return createSubaddress(accountIdx, null); + } + + @Override + public MoneroTxWallet getTx(String txId) { + return getTxs(txId).get(0); + } + + @Override + public List getTxs() { + return getTxs(new MoneroTxQuery()); + } + + public List getTxs(String... txIds) { + return getTxs(new MoneroTxQuery().setTxIds(txIds)); + } + + public List getTxs(List txIds) { + return getTxs(new MoneroTxQuery().setTxIds(txIds)); + } + + @Override + public List getTransfers() { + return getTransfers(null); + } + + @Override + public List getTransfers(int accountIdx) { + MoneroTransferQuery query = new MoneroTransferQuery().setAccountIndex(accountIdx); + return getTransfers(query); + } + + @Override + public List getTransfers(int accountIdx, int subaddressIdx) { + MoneroTransferQuery query = new MoneroTransferQuery().setAccountIndex(accountIdx).setSubaddressIndex(subaddressIdx); + return getTransfers(query); + } + + + @Override + public List getIncomingTransfers() { + return getIncomingTransfers(null); + } + + @Override + public List getIncomingTransfers(MoneroTransferQuery query) { + + // copy query and set direction + MoneroTransferQuery _query; + if (query == null) _query = new MoneroTransferQuery(); + else { + if (Boolean.FALSE.equals(query.isIncoming())) throw new MoneroException("Transfer query contradicts getting incoming transfers"); + _query = query.copy(); + } + _query.setIsIncoming(true); + + // fetch and cast transfers + List inTransfers = new ArrayList(); + for (MoneroTransfer transfer : getTransfers(_query)) { + inTransfers.add((MoneroIncomingTransfer) transfer); + } + return inTransfers; + } + + @Override + public List getOutgoingTransfers() { + return getOutgoingTransfers(null); + } + + @Override + public List getOutgoingTransfers(MoneroTransferQuery query) { + + // copy query and set direction + MoneroTransferQuery _query; + if (query == null) _query = new MoneroTransferQuery(); + else { + if (Boolean.FALSE.equals(query.isOutgoing())) throw new MoneroException("Transfer query contradicts getting outgoing transfers"); + _query = query.copy(); + } + _query.setIsOutgoing(true); + + // fetch and cast transfers + List outTransfers = new ArrayList(); + for (MoneroTransfer transfer : getTransfers(_query)) { + outTransfers.add((MoneroOutgoingTransfer) transfer); + } + return outTransfers; + } + + @Override + public List getOutputs() { + return getOutputs(null); + } + + @Override + public MoneroTxSet createTx(MoneroSendRequest request) { + if (request == null) throw new MoneroException("Send request cannot be null"); + if (Boolean.TRUE.equals(request.getCanSplit())) throw new MoneroException("Cannot request split transactions with createTx() which prevents splitting; use createTxs() instead"); + request = request.copy(); + request.setCanSplit(false); + return createTxs(request); + } + + @Override + public MoneroTxSet createTx(int accountIndex, String address, BigInteger sendAmount) { + return createTx(accountIndex, address, sendAmount, null); + } + + @Override + public MoneroTxSet createTx(int accountIndex, String address, BigInteger sendAmount, MoneroSendPriority priority) { + return createTx(new MoneroSendRequest(accountIndex, address, sendAmount, priority)); + } + + @Override + public MoneroTxSet createTxs(MoneroSendRequest request) { + if (request == null) throw new MoneroException("Send request cannot be null"); + + // modify request to not relay + Boolean requestedDoNotRelay = request.getDoNotRelay(); + request.setDoNotRelay(true); + + // invoke common method which doesn't relay + MoneroTxSet txSet = sendSplit(request); + + // restore doNotRelay of request and txs + request.setDoNotRelay(requestedDoNotRelay); + if (txSet.getTxs() != null) { + for (MoneroTxWallet tx : txSet.getTxs()) tx.setDoNotRelay(requestedDoNotRelay); + } + + // return results + return txSet; + } + + @Override + public String relayTx(String txMetadata) { + return relayTxs(Arrays.asList(txMetadata)).get(0); + } + + @Override + public String relayTx(MoneroTxWallet tx) { + return relayTx(tx.getMetadata()); + } + + // TODO: this method is not tested + @Override + public List relayTxs(List txs) { + List txHexes = new ArrayList(); + for (MoneroTxWallet tx : txs) txHexes.add(tx.getMetadata()); + return relayTxs(txHexes); + } + + @Override + public MoneroTxSet send(MoneroSendRequest request) { + if (request == null) throw new MoneroException("Send request cannot be null"); + if (Boolean.TRUE.equals(request.getCanSplit())) throw new MoneroException("Cannot request split transactions with send() which prevents splitting; use sendSplit() instead"); + request = request.copy(); + request.setCanSplit(false); + return sendSplit(request); + } + + @Override + public MoneroTxSet send(int accountIndex, String address, BigInteger sendAmount) { + return send(accountIndex, address, sendAmount, null); + } + + @Override + public MoneroTxSet send(int accountIndex, String address, BigInteger sendAmount, MoneroSendPriority priority) { + return send(new MoneroSendRequest(accountIndex, address, sendAmount, priority)); + } + + @Override + public MoneroTxSet sendSplit(int accountIndex, String address, BigInteger sendAmount) { + return sendSplit(new MoneroSendRequest(accountIndex, address, sendAmount)); + } + + @Override + public MoneroTxSet sendSplit(int accountIndex, String address, BigInteger sendAmount, MoneroSendPriority priority) { + return sendSplit(new MoneroSendRequest(accountIndex, address, sendAmount, priority)); + } + + @Override + public MoneroTxSet sweepOutput(String address, String keyImage) { + return sweepOutput(address, keyImage, null); + } + + @Override + public MoneroTxSet sweepOutput(String address, String keyImage, MoneroSendPriority priority) { + MoneroSendRequest request = new MoneroSendRequest(address).setPriority(priority); + request.setKeyImage(keyImage); + return sweepOutput(request); + } + + @Override + public MoneroTxSet sweepSubaddress(int accountIdx, int subaddressIdx, String address) { + MoneroSendRequest request = new MoneroSendRequest(address); + request.setAccountIndex(accountIdx); + request.setSubaddressIndices(subaddressIdx); + List txSets = sweepUnlocked(request); + assertEquals("Only one tx set should be created when sweeping from a subaddress", 1, (int) txSets.size()); + return txSets.get(0); + } + + @Override + public MoneroTxSet sweepAccount(int accountIdx, String address) { + MoneroSendRequest request = new MoneroSendRequest(address); + request.setAccountIndex(accountIdx); + List txSets = sweepUnlocked(request); + assertEquals("Only one tx set should be created when sweeping from an account", 1, (int) txSets.size()); + return txSets.get(0); + } + + @Override + public List sweepWallet(String address) { + return sweepUnlocked(new MoneroSendRequest(address)); + } + + @Override + public MoneroTxSet sweepDust() { + return sweepDust(false); + } + + @Override + public String getTxProof(String txId, String address) { + return getTxProof(txId, address, null); + } + + @Override + public String getSpendProof(String txId) { + return getSpendProof(txId, null); + } + + @Override + public String getTxNote(String txId) { + return getTxNotes(Arrays.asList(txId)).get(0); + } + + @Override + public void setTxNote(String txId, String note) { + setTxNotes(Arrays.asList(txId), Arrays.asList(note)); + } + + @Override + public List getAddressBookEntries() { + return getAddressBookEntries(null); + } + + @Override + public int addAddressBookEntry(String address, String description) { + return addAddressBookEntry(address, description, null); + } + + @Override + public boolean isMultisig() { + return getMultisigInfo().isMultisig(); + } + + @Override + public void close() { + close(false); // close without saving + } +} diff --git a/core/src/main/java/monero/wallet/MoneroWalletJni.java b/core/src/main/java/monero/wallet/MoneroWalletJni.java new file mode 100644 index 00000000000..955c74fc72d --- /dev/null +++ b/core/src/main/java/monero/wallet/MoneroWalletJni.java @@ -0,0 +1,1635 @@ +/** + * Copyright (c) 2017-2019 woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package monero.wallet; + +import static org.junit.Assert.assertNull; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.log4j.Logger; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import common.utils.GenUtils; +import common.utils.JsonUtils; +import monero.daemon.model.MoneroBlock; +import monero.daemon.model.MoneroKeyImage; +import monero.daemon.model.MoneroNetworkType; +import monero.daemon.model.MoneroTx; +import monero.rpc.MoneroRpcConnection; +import monero.utils.MoneroException; +import monero.wallet.model.MoneroAccount; +import monero.wallet.model.MoneroAccountTag; +import monero.wallet.model.MoneroAddressBookEntry; +import monero.wallet.model.MoneroCheckReserve; +import monero.wallet.model.MoneroCheckTx; +import monero.wallet.model.MoneroIncomingTransfer; +import monero.wallet.model.MoneroIntegratedAddress; +import monero.wallet.model.MoneroKeyImageImportResult; +import monero.wallet.model.MoneroMultisigInfo; +import monero.wallet.model.MoneroMultisigInitResult; +import monero.wallet.model.MoneroMultisigSignResult; +import monero.wallet.model.MoneroOutputQuery; +import monero.wallet.model.MoneroOutputWallet; +import monero.wallet.model.MoneroSendRequest; +import monero.wallet.model.MoneroSubaddress; +import monero.wallet.model.MoneroSyncListener; +import monero.wallet.model.MoneroSyncResult; +import monero.wallet.model.MoneroTransfer; +import monero.wallet.model.MoneroTransferQuery; +import monero.wallet.model.MoneroTxQuery; +import monero.wallet.model.MoneroTxSet; +import monero.wallet.model.MoneroTxWallet; +import monero.wallet.model.MoneroWalletListener; +import monero.wallet.model.MoneroWalletListenerI; + +/** + * Implements a Monero wallet using JNI to bridge to Monero Core C++. + */ +public class MoneroWalletJni extends MoneroWalletDefault { + + // ----------------------------- PRIVATE SETUP ------------------------------ + + // load Monero Core C++ as a dynamic library + static { + System.loadLibrary("monero-java"); + } + + // logger + private static final Logger LOGGER = Logger.getLogger(MoneroWalletJni.class); + + // instance variables + private long jniWalletHandle; // memory address of the wallet in c++; this variable is read directly by name in c++ + private long jniListenerHandle; // memory address of the wallet listener in c++; this variable is read directly by name in c++ + private WalletJniListener jniListener; // receives notifications from jni c++ + private Set listeners; // externally subscribed wallet listeners + private boolean isClosed; // whether or not wallet is closed + + /** + * Private constructor with a handle to the memory address of the wallet in c++. + * + * @param jniWalletHandle is the memory address of the wallet in c++ + */ + private MoneroWalletJni(long jniWalletHandle) { + this.jniWalletHandle = jniWalletHandle; + this.jniListener = new WalletJniListener(); + this.listeners = new LinkedHashSet(); + this.isClosed = false; + } + + // --------------------- WALLET MANAGEMENT UTILITIES ------------------------ + + /** + * Indicates if a wallet exists at the given path. + * + * @param path is the path to check for a wallet + * @return true if a wallet exists at the given path, false otherwise + */ + public static boolean walletExists(String path) { + return walletExistsJni(path); + } + + /** + * Open an existing wallet. + * + * @param path is the path to the wallet file to open + * @param password is the password of the wallet file to open + * @param networkType is the wallet's network type + * @param daemonConnection is connection configuration to a daemon (default = an unconnected wallet) + * @return the opened wallet + */ + public static MoneroWalletJni openWallet(String path, String password, MoneroNetworkType networkType) { return openWallet(path, password, networkType, (MoneroRpcConnection) null); } + public static MoneroWalletJni openWallet(String path, String password, MoneroNetworkType networkType, String daemonUri) { return openWallet(path, password, networkType, daemonUri == null ? null : new MoneroRpcConnection(daemonUri)); } + public static MoneroWalletJni openWallet(String path, String password, MoneroNetworkType networkType, MoneroRpcConnection daemonConnection) { + if (!walletExistsJni(path)) throw new MoneroException("Wallet does not exist at path: " + path); + if (networkType == null) throw new MoneroException("Must provide a network type"); + long jniWalletHandle = openWalletJni(path, password, networkType.ordinal()); + MoneroWalletJni wallet = new MoneroWalletJni(jniWalletHandle); + if (daemonConnection != null) wallet.setDaemonConnection(daemonConnection); + return wallet; + } + + /** + * Create a new wallet with a randomly generated seed. + * + * @param path is the path to create the wallet + * @param password is the password encrypt the wallet + * @param networkType is the wallet's network type (default = MoneroNetworkType.MAINNET) + * @param daemonConnection is connection configuration to a daemon (default = an unconnected wallet) + * @param language is the wallet and mnemonic's language (default = "English") + * @return the newly created wallet + */ + public static MoneroWalletJni createWalletRandom(String path, String password) { return createWalletRandom(path, password, null, null, null); } + public static MoneroWalletJni createWalletRandom(String path, String password, MoneroNetworkType networkType) { return createWalletRandom(path, password, networkType, null, null); } + public static MoneroWalletJni createWalletRandom(String path, String password, MoneroNetworkType networkType, String daemonUri) { return createWalletRandom(path, password, networkType, daemonUri == null ? null : new MoneroRpcConnection(daemonUri), null); } + public static MoneroWalletJni createWalletRandom(String path, String password, MoneroNetworkType networkType, MoneroRpcConnection daemonConnection) { return createWalletRandom(path, password, networkType, daemonConnection, null); } + public static MoneroWalletJni createWalletRandom(String path, String password, MoneroNetworkType networkType, MoneroRpcConnection daemonConnection, String language) { + if (networkType == null) networkType = MoneroNetworkType.MAINNET; + if (language == null) language = DEFAULT_LANGUAGE; + long jniWalletHandle; + if (daemonConnection == null) jniWalletHandle = createWalletRandomJni(path, password, networkType.ordinal(), null, null, null, language); + else jniWalletHandle = createWalletRandomJni(path, password, networkType.ordinal(), daemonConnection.getUri(), daemonConnection.getUsername(), daemonConnection.getPassword(), language); + return new MoneroWalletJni(jniWalletHandle); + } + + /** + * Create a wallet from an existing mnemonic phrase. + * + * @param path is the path to create the wallet + * @param password is the password encrypt the wallet + * @param networkType is the wallet's network type + * @param mnemonic is the mnemonic of the wallet to construct + * @param daemonConnection is connection configuration to a daemon (default = an unconnected wallet) + * @param restoreHeight is the block height to restore from (default = 0) + */ + public static MoneroWalletJni createWalletFromMnemonic(String path, String password, MoneroNetworkType networkType, String mnemonic) { return createWalletFromMnemonic(path, password, networkType, mnemonic, null, null); } + public static MoneroWalletJni createWalletFromMnemonic(String path, String password, MoneroNetworkType networkType, String mnemonic, MoneroRpcConnection daemonConnection) { return createWalletFromMnemonic(path, password, networkType, mnemonic, daemonConnection, null); } + public static MoneroWalletJni createWalletFromMnemonic(String path, String password, MoneroNetworkType networkType, String mnemonic, MoneroRpcConnection daemonConnection, Long restoreHeight) { + if (networkType == null) throw new MoneroException("Must provide a network type"); + if (restoreHeight == null) restoreHeight = 0l; + long jniWalletHandle = createWalletFromMnemonicJni(path, password, networkType.ordinal(), mnemonic, restoreHeight); + MoneroWalletJni wallet = new MoneroWalletJni(jniWalletHandle); + wallet.setDaemonConnection(daemonConnection); + return wallet; + } + + /** + * Create a wallet from an address, view key, and spend key. + * + * @param path is the path to create the wallet + * @param password is the password encrypt the wallet + * @param networkType is the wallet's network type + * @param address is the address of the wallet to construct + * @param viewKey is the view key of the wallet to construct + * @param spendKey is the spend key of the wallet to construct + * @param daemonConnection is connection configuration to a daemon (default = an unconnected wallet) + * @param restoreHeight is the block height to restore (i.e. scan the chain) from (default = 0) + * @param language is the wallet and mnemonic's language (default = "English") + */ + public static MoneroWalletJni createWalletFromKeys(String path, String password, MoneroNetworkType networkType, String address, String viewKey, String spendKey) { return createWalletFromKeys(path, password, networkType, address, viewKey, spendKey, null, null, null); } + public static MoneroWalletJni createWalletFromKeys(String path, String password, MoneroNetworkType networkType, String address, String viewKey, String spendKey, MoneroRpcConnection daemonConnection, Long restoreHeight) { return createWalletFromKeys(path, password, networkType, address, viewKey, spendKey, daemonConnection, restoreHeight, null); } + public static MoneroWalletJni createWalletFromKeys(String path, String password, MoneroNetworkType networkType, String address, String viewKey, String spendKey, MoneroRpcConnection daemonConnection, Long restoreHeight, String language) { + if (restoreHeight == null) restoreHeight = 0l; + if (networkType == null) throw new MoneroException("Must provide a network type"); + if (language == null) language = DEFAULT_LANGUAGE; + try { + long jniWalletHandle = createWalletFromKeysJni(path, password, networkType.ordinal(), address, viewKey, spendKey, restoreHeight, language); + MoneroWalletJni wallet = new MoneroWalletJni(jniWalletHandle); + wallet.setDaemonConnection(daemonConnection); + return wallet; + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + // ------------ WALLET METHODS SPECIFIC TO JNI IMPLEMENTATION --------------- + + /** + * Set the wallet's daemon connection. + * + * @param uri is the uri of the daemon for the wallet to use + */ + public void setDaemonConnection(String uri) { + setDaemonConnection(uri, null, null); + } + + /** + * Set the wallet's daemon connection. + * + * @param uri is the daemon's URI + * @param username is the username to authenticate with the daemon (optional) + * @param password is the password to authenticate with the daemon (optional) + */ + public void setDaemonConnection(String uri, String username, String password) { + if (uri == null) setDaemonConnection((MoneroRpcConnection) null); + else setDaemonConnection(new MoneroRpcConnection(uri, username, password)); + } + + /** + * Set the wallet's daemon connection + * + * @param daemonConnection manages daemon connection information + */ + public void setDaemonConnection(MoneroRpcConnection daemonConnection) { + assertNotClosed(); + if (daemonConnection == null) setDaemonConnectionJni("", "", ""); + else { + try { + setDaemonConnectionJni(daemonConnection.getUri() == null ? "" : daemonConnection.getUri().toString(), daemonConnection.getUsername(), daemonConnection.getPassword()); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + } + + /** + * Get the wallet's daemon connection. + * + * @return the wallet's daemon connection + */ + public MoneroRpcConnection getDaemonConnection() { + assertNotClosed(); + try { + String[] vals = getDaemonConnectionJni(); + return vals == null ? null : new MoneroRpcConnection(vals[0], vals[1], vals[2]); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + /** + * Indicates if the wallet is connected a daemon. + * + * @return true if the wallet is connected to a daemon, false otherwise + */ + public boolean isConnected() { + assertNotClosed(); + try { + return isConnectedJni(); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + /** + * Get the maximum height of the peers the wallet's daemon is connected to. + * + * @return the maximum height of the peers the wallet's daemon is connected to + */ + public long getDaemonMaxPeerHeight() { + assertNotClosed(); + try { + return getDaemonMaxPeerHeightJni(); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + /** + * Indicates if the wallet's daemon is synced with the network. + * + * @return true if the daemon is synced with the network, false otherwise + */ + public boolean isDaemonSynced() { + assertNotClosed(); + try { + return isDaemonSyncedJni(); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + /** + * Indicates if the wallet is synced with the daemon. + * + * @return true if the wallet is synced with the daemon, false otherwise + */ + public boolean isSynced() { + assertNotClosed(); + try { + return isSyncedJni(); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + /** + * Get the wallet's network type (mainnet, testnet, or stagenet). + * + * @return the wallet's network type + */ + public MoneroNetworkType getNetworkType() { + assertNotClosed(); + return MoneroNetworkType.values()[getNetworkTypeJni()]; + } + + /** + * Get the height of the first block that the wallet scans. + * + * @return the height of the first block that the wallet scans + */ + public long getRestoreHeight() { + assertNotClosed(); + return getRestoreHeightJni(); + } + + /** + * Set the height of the first block that the wallet scans. + * + * @param restoreHeight is the height of the first block that the wallet scans + */ + public void setRestoreHeight(long restoreHeight) { + assertNotClosed(); + setRestoreHeightJni(restoreHeight); + } + + /** + * Get the language of the wallet's mnemonic phrase. + * + * @return the language of the wallet's mnemonic phrase + */ + public String getLanguage() { + assertNotClosed(); + return getLanguageJni(); + } + + /** + * Get the wallet's public view key. + * + * @return the wallet's public view key + */ + public String getPublicViewKey() { + assertNotClosed(); + return getPublicViewKeyJni(); + } + + /** + * Get the wallet's public spend key. + * + * @return the wallet's public spend key + */ + public String getPublicSpendKey() { + assertNotClosed(); + return getPublicSpendKeyJni(); + } + + /** + * Register a listener receive wallet notifications. + * + * @param listener is the listener to receive wallet notifications + */ + public void addListener(MoneroWalletListenerI listener) { + assertNotClosed(); + listeners.add(listener); + jniListener.setIsListening(true); + } + + /** + * Unregister a listener to receive wallet notifications. + * + * @param listener is the listener to unregister + */ + public void removeListener(MoneroWalletListenerI listener) { + assertNotClosed(); + if (!listeners.contains(listener)) throw new MoneroException("Listener is not registered to wallet"); + listeners.remove(listener); + if (listeners.isEmpty()) jniListener.setIsListening(false); + } + + /** + * Get the listeners registered with the wallet. + */ + public Set getListeners() { + assertNotClosed(); + return listeners; + } + + /** + * Move the wallet from its current path to the given path. + * + * @param path is the new wallet's path + * @param password is the new wallet's password + */ + public void moveTo(String path, String password) { + assertNotClosed(); + moveToJni(path, password); + } + + /** + * Indicates if this wallet is closed or not. + * + * @return true if the wallet is closed, false otherwise + */ + public boolean isClosed() { + return isClosed; + } + + // -------------------------- COMMON WALLET METHODS ------------------------- + + @Override + public String getPath() { + assertNotClosed(); + String path = getPathJni(); + return path.isEmpty() ? null : path; + } + + @Override + public String getSeed() { + assertNotClosed(); + throw new RuntimeException("Not implemented"); + } + + @Override + public String getMnemonic() { + assertNotClosed(); + return getMnemonicJni(); + } + + @Override + public List getLanguages() { + assertNotClosed(); + return Arrays.asList(getLanguagesJni()); + } + + @Override + public String getPrivateViewKey() { + assertNotClosed(); + return getPrivateViewKeyJni(); + } + + @Override + public String getPrivateSpendKey() { + assertNotClosed(); + return getPrivateSpendKeyJni(); + } + + @Override + public MoneroIntegratedAddress getIntegratedAddress(String paymentId) { + assertNotClosed(); + try { + String integratedAddressJson = getIntegratedAddressJni("", paymentId); + return JsonUtils.deserialize(MoneroRpcConnection.MAPPER, integratedAddressJson, MoneroIntegratedAddress.class); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public MoneroIntegratedAddress decodeIntegratedAddress(String integratedAddress) { + assertNotClosed(); + try { + String integratedAddressJson = decodeIntegratedAddressJni(integratedAddress); + return JsonUtils.deserialize(MoneroRpcConnection.MAPPER, integratedAddressJson, MoneroIntegratedAddress.class); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public long getHeight() { + assertNotClosed(); + return getHeightJni(); + } + + @Override + public long getDaemonHeight() { + assertNotClosed(); + try { + return getDaemonHeightJni(); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public MoneroSyncResult sync(Long startHeight, MoneroSyncListener listener) { + assertNotClosed(); + if (startHeight == null) startHeight = Math.max(getHeight(), getRestoreHeight()); + + // wrap and register sync listener as wallet listener if given + SyncListenerWrapper syncListenerWrapper = null; + if (listener != null) { + syncListenerWrapper = new SyncListenerWrapper(listener); + addListener(syncListenerWrapper); + } + + // sync wallet and handle exception + try { + Object[] results = syncJni(startHeight); + return new MoneroSyncResult((long) results[0], (boolean) results[1]); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } finally { + if (syncListenerWrapper != null) removeListener(syncListenerWrapper); // unregister sync listener + } + } + + @Override + public void startSyncing() { + assertNotClosed(); + try { + startSyncingJni(); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + public void stopSyncing() { + assertNotClosed(); + try { + stopSyncingJni(); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public void rescanSpent() { + assertNotClosed(); + try { + rescanSpentJni(); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public void rescanBlockchain() { + assertNotClosed(); + try { + rescanBlockchainJni(); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public List getAccounts(boolean includeSubaddresses, String tag) { + assertNotClosed(); + String accountsJson = getAccountsJni(includeSubaddresses, tag); + List accounts = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, accountsJson, AccountsContainer.class).accounts; + for (MoneroAccount account : accounts) sanitizeAccount(account); + return accounts; + } + + @Override + public MoneroAccount getAccount(int accountIdx, boolean includeSubaddresses) { + assertNotClosed(); + String accountJson = getAccountJni(accountIdx, includeSubaddresses); + MoneroAccount account = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, accountJson, MoneroAccount.class); + sanitizeAccount(account); + return account; + } + + @Override + public MoneroAccount createAccount(String label) { + assertNotClosed(); + String accountJson = createAccountJni(label); + MoneroAccount account = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, accountJson, MoneroAccount.class); + sanitizeAccount(account); + return account; + } + + @Override + public List getSubaddresses(int accountIdx, List subaddressIndices) { + assertNotClosed(); + String subaddresses_json = getSubaddressesJni(accountIdx, GenUtils.listToIntArray(subaddressIndices)); + List subaddresses = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, subaddresses_json, SubaddressesContainer.class).subaddresses; + for (MoneroSubaddress subaddress : subaddresses) sanitizeSubaddress(subaddress); + return subaddresses; + } + + @Override + public MoneroSubaddress createSubaddress(int accountIdx, String label) { + assertNotClosed(); + String subaddressJson = createSubaddressJni(accountIdx, label); + MoneroSubaddress subaddress = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, subaddressJson, MoneroSubaddress.class); + sanitizeSubaddress(subaddress); + return subaddress; + } + + @Override + public String getAddress(int accountIdx, int subaddressIdx) { + assertNotClosed(); + return getAddressJni(accountIdx, subaddressIdx); + } + + @Override + public MoneroSubaddress getAddressIndex(String address) { + assertNotClosed(); + try { + String subaddressJson = getAddressIndexJni(address); + MoneroSubaddress subaddress = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, subaddressJson, MoneroSubaddress.class); + return sanitizeSubaddress(subaddress); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public BigInteger getBalance() { + assertNotClosed(); + try { + return new BigInteger(getBalanceWalletJni()); + } catch (MoneroException e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public BigInteger getBalance(int accountIdx) { + assertNotClosed(); + try { + return new BigInteger(getBalanceAccountJni(accountIdx)); + } catch (MoneroException e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public BigInteger getBalance(int accountIdx, int subaddressIdx) { + assertNotClosed(); + try { + return new BigInteger(getBalanceSubaddressJni(accountIdx, subaddressIdx)); + } catch (MoneroException e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public BigInteger getUnlockedBalance() { + assertNotClosed(); + try { + return new BigInteger(getUnlockedBalanceWalletJni()); + } catch (MoneroException e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public BigInteger getUnlockedBalance(int accountIdx) { + assertNotClosed(); + try { + return new BigInteger(getUnlockedBalanceAccountJni(accountIdx)); + } catch (MoneroException e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public BigInteger getUnlockedBalance(int accountIdx, int subaddressIdx) { + assertNotClosed(); + try { + return new BigInteger(getUnlockedBalanceSubaddressJni(accountIdx, subaddressIdx)); + } catch (MoneroException e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public List getTxs(MoneroTxQuery query) { + assertNotClosed(); + + // copy and normalize tx query up to block + query = query == null ? new MoneroTxQuery() : query.copy(); + if (query.getBlock() == null) query.setBlock(new MoneroBlock().setTxs(query)); + + // serialize query from block and fetch txs from jni + String blocksJson; + try { + blocksJson = getTxsJni(JsonUtils.serialize(query.getBlock())); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + + // deserialize blocks + List blocks = deserializeBlocks(blocksJson); + + // collect txs + List txs = new ArrayList(); + for (MoneroBlock block : blocks) { + sanitizeBlock(block); + for (MoneroTx tx : block.getTxs()) { + if (block.getHeight() == null) tx.setBlock(null); // dereference placeholder block for unconfirmed txs + txs.add((MoneroTxWallet) tx); + } + } + + // re-sort txs which is lost over jni serialization + if (query.getTxIds() != null) { + Map txMap = new HashMap(); + for (MoneroTxWallet tx : txs) txMap.put(tx.getId(), tx); + List txsSorted = new ArrayList(); + for (String txId : query.getTxIds()) txsSorted.add(txMap.get(txId)); + txs = txsSorted; + } + LOGGER.debug("getTxs() returning " + txs.size() + " transactions"); + return txs; + } + + @Override + public List getTransfers(MoneroTransferQuery query) { + assertNotClosed(); + + // copy and normalize query up to block + if (query == null) query = new MoneroTransferQuery(); + else { + if (query.getTxQuery() == null) query = query.copy(); + else { + MoneroTxQuery txQuery = query.getTxQuery().copy(); + if (query.getTxQuery().getTransferQuery() == query) query = txQuery.getTransferQuery(); + else { + assertNull("Transfer query's tx query must be circular reference or null", query.getTxQuery().getTransferQuery()); + query = query.copy(); + query.setTxQuery(txQuery); + } + } + } + if (query.getTxQuery() == null) query.setTxQuery(new MoneroTxQuery()); + query.getTxQuery().setTransferQuery(query); + if (query.getTxQuery().getBlock() == null) query.getTxQuery().setBlock(new MoneroBlock().setTxs(query.getTxQuery())); + + // serialize query from block and fetch transfers from jni + String blocksJson; + try { + blocksJson = getTransfersJni(JsonUtils.serialize(query.getTxQuery().getBlock())); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + + // deserialize blocks + List blocks = deserializeBlocks(blocksJson); + + // collect transfers + List transfers = new ArrayList(); + for (MoneroBlock block : blocks) { + sanitizeBlock(block); + for (MoneroTx tx : block.getTxs()) { + if (block.getHeight() == null) tx.setBlock(null); // dereference placeholder block for unconfirmed txs + MoneroTxWallet txWallet = (MoneroTxWallet) tx; + if (txWallet.getOutgoingTransfer() != null) transfers.add(txWallet.getOutgoingTransfer()); + if (txWallet.getIncomingTransfers() != null) { + for (MoneroIncomingTransfer transfer : txWallet.getIncomingTransfers()) transfers.add(transfer); + } + } + } + return transfers; + } + + @Override + public List getOutputs(MoneroOutputQuery query) { + assertNotClosed(); + + // copy and normalize query up to block + if (query == null) query = new MoneroOutputQuery(); + else { + if (query.getTxQuery() == null) query = query.copy(); + else { + MoneroTxQuery txQuery = query.getTxQuery().copy(); + if (query.getTxQuery().getOutputQuery() == query) query = txQuery.getOutputQuery(); + else { + assertNull("Output query's tx query must be circular reference or null", query.getTxQuery().getOutputQuery()); + query = query.copy(); + query.setTxQuery(txQuery); + } + } + } + if (query.getTxQuery() == null) query.setTxQuery(new MoneroTxQuery()); + query.getTxQuery().setOutputQuery(query); + if (query.getTxQuery().getBlock() == null) query.getTxQuery().setBlock(new MoneroBlock().setTxs(query.getTxQuery())); + + // serialize query from block and fetch outputs from jni + String blocksJson = getOutputsJni(JsonUtils.serialize(query.getTxQuery().getBlock())); + + // deserialize blocks + List blocks = deserializeBlocks(blocksJson); + + // collect outputs + List outputs = new ArrayList(); + for (MoneroBlock block : blocks) { + sanitizeBlock(block); + for (MoneroTx tx : block.getTxs()) { + MoneroTxWallet txWallet = (MoneroTxWallet) tx; + outputs.addAll(txWallet.getVoutsWallet()); + } + } + return outputs; + } + + @Override + public String getOutputsHex() { + assertNotClosed(); + String outputsHex = getOutputsHexJni(); + return outputsHex.isEmpty() ? null : outputsHex; + } + + @Override + public int importOutputsHex(String outputsHex) { + assertNotClosed(); + return importOutputsHexJni(outputsHex); + } + + @Override + public List getKeyImages() { + assertNotClosed(); + String keyImagesJson = getKeyImagesJni(); + List keyImages = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, keyImagesJson, KeyImagesContainer.class).keyImages; + return keyImages; + } + + @Override + public MoneroKeyImageImportResult importKeyImages(List keyImages) { + assertNotClosed(); + + // wrap and serialize key images in container for jni + KeyImagesContainer keyImageContainer = new KeyImagesContainer(keyImages); + String importResultJson = importKeyImagesJni(JsonUtils.serialize(keyImageContainer)); + + // deserialize response + return JsonUtils.deserialize(MoneroRpcConnection.MAPPER, importResultJson, MoneroKeyImageImportResult.class); + } + + @Override + public List getNewKeyImagesFromLastImport() { + assertNotClosed(); + throw new RuntimeException("Not implemented"); + } + + @Override + public List relayTxs(Collection txMetadatas) { + assertNotClosed(); + String[] txMetadatasArr = txMetadatas.toArray(new String[txMetadatas.size()]); // convert to array for jni + try { + return Arrays.asList(relayTxsJni(txMetadatasArr)); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public MoneroTxSet sendSplit(MoneroSendRequest request) { + assertNotClosed(); + LOGGER.debug("java sendSplit(request)"); + LOGGER.debug("Send request: " + JsonUtils.serialize(request)); + + // validate request + if (request == null) throw new MoneroException("Send request cannot be null"); + + // submit send request to JNI and get response as json rooted at tx set + String txSetJson; + try { + txSetJson = sendSplitJni(JsonUtils.serialize(request)); + LOGGER.debug("Received sendSplit() response from JNI: " + txSetJson.substring(0, Math.min(5000, txSetJson.length())) + "..."); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + + // deserialize and return tx set + MoneroTxSet txSet = JsonUtils.deserialize(txSetJson, MoneroTxSet.class); + if (txSet.getTxs() == null) LOGGER.debug("Created tx set without txs: " + JsonUtils.serialize(txSet) + " in sendSplit()"); + else LOGGER.debug("Created " + txSet.getTxs().size() + " transaction(s) in last send request"); + return txSet; + } + + @Override + public List sweepUnlocked(MoneroSendRequest request) { + assertNotClosed(); + + // validate request + if (request == null) throw new MoneroException("Send request cannot be null"); + + // submit send request to JNI and get response as json rooted at tx set + String txSetsJson; + try { + txSetsJson = sweepUnlockedJni(JsonUtils.serialize(request)); + LOGGER.debug("Received sweepUnlocked() response from JNI: " + txSetsJson.substring(0, Math.min(5000, txSetsJson.length())) + "..."); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + + // deserialize and return tx sets + return JsonUtils.deserialize(MoneroRpcConnection.MAPPER, txSetsJson, TxSetsContainer.class).txSets; + } + + @Override + public MoneroTxSet sweepOutput(MoneroSendRequest request) { + assertNotClosed(); + try { + String txSetJson = sweepOutputJni(JsonUtils.serialize(request)); + MoneroTxSet txSet = JsonUtils.deserialize(txSetJson, MoneroTxSet.class); + return txSet; + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public MoneroTxSet sweepDust(boolean doNotRelay) { + assertNotClosed(); + String txSetJson; + try { txSetJson = sweepDustJni(doNotRelay); } + catch (Exception e) { throw new MoneroException(e.getMessage()); } + MoneroTxSet txSet = JsonUtils.deserialize(txSetJson, MoneroTxSet.class); + return txSet; + } + + @Override + public MoneroCheckTx checkTxKey(String txId, String txKey, String address) { + assertNotClosed(); + try { + String checkStr = checkTxKeyJni(txId, txKey, address); + return JsonUtils.deserialize(MoneroRpcConnection.MAPPER, checkStr, MoneroCheckTx.class); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public String getTxProof(String txId, String address, String message) { + assertNotClosed(); + try { + return getTxProofJni(txId, address, message); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public MoneroCheckTx checkTxProof(String txId, String address, String message, String signature) { + assertNotClosed(); + try { + String checkStr = checkTxProofJni(txId, address, message, signature); + return JsonUtils.deserialize(MoneroRpcConnection.MAPPER, checkStr, MoneroCheckTx.class); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public String getSpendProof(String txId, String message) { + assertNotClosed(); + try { + return getSpendProofJni(txId, message); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public boolean checkSpendProof(String txId, String message, String signature) { + assertNotClosed(); + try { + return checkSpendProofJni(txId, message, signature); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public String getReserveProofWallet(String message) { + try { + return getReserveProofWalletJni(message); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public String getReserveProofAccount(int accountIdx, BigInteger amount, String message) { + assertNotClosed(); + try { + return getReserveProofAccountJni(accountIdx, amount.toString(), message); + } catch (Exception e) { + throw new MoneroException(e.getMessage(), -1); + } + } + + @Override + public MoneroCheckReserve checkReserveProof(String address, String message, String signature) { + assertNotClosed(); + try { + String checkStr = checkReserveProofJni(address, message, signature); + return JsonUtils.deserialize(MoneroRpcConnection.MAPPER, checkStr, MoneroCheckReserve.class); + } catch (Exception e) { + throw new MoneroException(e.getMessage(), -1); + } + } + + @Override + public String sign(String msg) { + assertNotClosed(); + return signJni(msg); + } + + @Override + public boolean verify(String msg, String address, String signature) { + assertNotClosed(); + return verifyJni(msg, address, signature); + } + + @Override + public String getTxKey(String txId) { + assertNotClosed(); + try { + return getTxKeyJni(txId); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public List getTxNotes(Collection txIds) { + assertNotClosed(); + return Arrays.asList(getTxNotesJni(txIds.toArray(new String[txIds.size()]))); // convert to array for jni + } + + @Override + public void setTxNotes(Collection txIds, Collection notes) { + assertNotClosed(); + setTxNotesJni(txIds.toArray(new String[txIds.size()]), notes.toArray(new String[notes.size()])); + } + + @Override + public List getAddressBookEntries(Collection entryIndices) { + assertNotClosed(); + throw new RuntimeException("Not implemented"); + } + + @Override + public int addAddressBookEntry(String address, String description, String paymentId) { + assertNotClosed(); + throw new RuntimeException("Not implemented"); + } + + @Override + public void deleteAddressBookEntry(int entryIdx) { + assertNotClosed(); + throw new RuntimeException("Not implemented"); + } + + @Override + public void tagAccounts(String tag, Collection accountIndices) { + assertNotClosed(); + throw new RuntimeException("Not implemented"); + } + + @Override + public void untagAccounts(Collection accountIndices) { + assertNotClosed(); + throw new RuntimeException("Not implemented"); + } + + @Override + public List getAccountTags() { + assertNotClosed(); + throw new RuntimeException("Not implemented"); + } + + @Override + public void setAccountTagLabel(String tag, String label) { + assertNotClosed(); + throw new RuntimeException("Not implemented"); + } + + @Override + public String createPaymentUri(MoneroSendRequest request) { + assertNotClosed(); + try { + return createPaymentUriJni(JsonUtils.serialize(request)); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public MoneroSendRequest parsePaymentUri(String uri) { + assertNotClosed(); + try { + String sendRequestJson = parsePaymentUriJni(uri); + return JsonUtils.deserialize(MoneroRpcConnection.MAPPER, sendRequestJson, MoneroSendRequest.class); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public String getAttribute(String key) { + assertNotClosed(); + String value = getAttributeJni(key); + return value.isEmpty() ? null : value; + } + + @Override + public void setAttribute(String key, String val) { + assertNotClosed(); + setAttributeJni(key, val); + } + + @Override + public void startMining(Long numThreads, Boolean backgroundMining, Boolean ignoreBattery) { + assertNotClosed(); + try { + startMiningJni(numThreads == null ? 0l : (long) numThreads, backgroundMining, ignoreBattery); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public void stopMining() { + assertNotClosed(); + try { + stopMiningJni(); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + + @Override + public boolean isMultisigImportNeeded() { + assertNotClosed(); + return isMultisigImportNeededJni(); + } + + @Override + public MoneroMultisigInfo getMultisigInfo() { + try { + String multisigInfoJson = getMultisigInfoJni(); + return JsonUtils.deserialize(multisigInfoJson, MoneroMultisigInfo.class); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public String prepareMultisig() { + return prepareMultisigJni(); + } + + @Override + public MoneroMultisigInitResult makeMultisig(List multisigHexes, int threshold, String password) { + try { + String initMultisigResultJson = makeMultisigJni(multisigHexes.toArray(new String[multisigHexes.size()]), threshold, password); + return JsonUtils.deserialize(initMultisigResultJson, MoneroMultisigInitResult.class); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public MoneroMultisigInitResult exchangeMultisigKeys(List multisigHexes, String password) { + try { + String initMultisigResultJson = exchangeMultisigKeysJni(multisigHexes.toArray(new String[multisigHexes.size()]), password); + return JsonUtils.deserialize(initMultisigResultJson, MoneroMultisigInitResult.class); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public String getMultisigHex() { + try { + return getMultisigHexJni(); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public int importMultisigHex(List multisigHexes) { + try { + return importMultisigHexJni(multisigHexes.toArray(new String[multisigHexes.size()])); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public MoneroMultisigSignResult signMultisigTxHex(String multisigTxHex) { + try { + String signMultisigResultJson = signMultisigTxHexJni(multisigTxHex); + return JsonUtils.deserialize(signMultisigResultJson, MoneroMultisigSignResult.class); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public List submitMultisigTxHex(String signedMultisigTxHex) { + try { + return Arrays.asList(submitMultisigTxHexJni(signedMultisigTxHex)); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + @Override + public void save() { + assertNotClosed(); + saveJni(); + } + + @Override + public void close(boolean save) { + if (isClosed) return; // closing a closed wallet has no effect + isClosed = true; + try { + closeJni(save); + } catch (Exception e) { + throw new MoneroException(e.getMessage()); + } + } + + // ------------------------------ NATIVE METHODS ---------------------------- + + private native static boolean walletExistsJni(String path); + + private native static long openWalletJni(String path, String password, int networkType); + + private native static long createWalletRandomJni(String path, String password, int networkType, String daemonUrl, String daemonUsername, String daemonPassword, String language); + + private native static long createWalletFromMnemonicJni(String path, String password, int networkType, String mnemonic, long restoreHeight); + + private native static long createWalletFromKeysJni(String path, String password, int networkType, String address, String viewKey, String spendKey, long restoreHeight, String language); + + private native long getHeightJni(); + + private native long getRestoreHeightJni(); + + private native void setRestoreHeightJni(long height); + + private native long getDaemonHeightJni(); + + private native long getDaemonMaxPeerHeightJni(); + + private native String[] getDaemonConnectionJni(); // returns [uri, username, password] + + private native void setDaemonConnectionJni(String uri, String username, String password); + + private native boolean isConnectedJni(); + + private native boolean isDaemonSyncedJni(); + + private native boolean isSyncedJni(); + + private native int getNetworkTypeJni(); + + private native String getPathJni(); + + private native String getMnemonicJni(); + + private native String getLanguageJni(); + + private native String[] getLanguagesJni(); + + private native String getPublicViewKeyJni(); + + private native String getPrivateViewKeyJni(); + + private native String getPublicSpendKeyJni(); + + private native String getPrivateSpendKeyJni(); + + private native String getAddressJni(int accountIdx, int subaddressIdx); + + private native String getAddressIndexJni(String address); + + private native String getIntegratedAddressJni(String standardAddress, String paymentId); + + private native String decodeIntegratedAddressJni(String integratedAddress); + + private native long setListenerJni(WalletJniListener listener); + + private native Object[] syncJni(long startHeight); + + private native void startSyncingJni(); + + private native void stopSyncingJni(); + + private native void rescanSpentJni(); + + private native void rescanBlockchainJni(); + + private native String getBalanceWalletJni(); + + private native String getBalanceAccountJni(int accountIdx); + + private native String getBalanceSubaddressJni(int accountIdx, int subaddressIdx); + + private native String getUnlockedBalanceWalletJni(); + + private native String getUnlockedBalanceAccountJni(int accountIdx); + + private native String getUnlockedBalanceSubaddressJni(int accountIdx, int subaddressIdx); + + private native String getAccountsJni(boolean includeSubaddresses, String tag); + + private native String getAccountJni(int accountIdx, boolean includeSubaddresses); + + private native String createAccountJni(String label); + + private native String getSubaddressesJni(int accountIdx, int[] subaddressIndices); + + private native String createSubaddressJni(int accountIdx, String label); + + /** + * Gets txs from the native layer using strings to communicate. + * + * @param txQueryJson is a tx query serialized to a json string + * @return a serialized BlocksContainer to preserve model relationships + */ + private native String getTxsJni(String txQueryJson); + + private native String getTransfersJni(String transferQueryJson); + + private native String getOutputsJni(String outputQueryJson); + + private native String getOutputsHexJni(); + + private native int importOutputsHexJni(String outputsHex); + + private native String getKeyImagesJni(); + + private native String importKeyImagesJni(String keyImagesJson); + + private native String[] relayTxsJni(String[] txMetadatas); + + private native String sendSplitJni(String sendRequestJson); + + private native String sweepUnlockedJni(String sendRequestJson); + + private native String sweepOutputJni(String sendRequestJson); + + private native String sweepDustJni(boolean doNotRelay); + + private native String[] getTxNotesJni(String[] txIds); + + private native void setTxNotesJni(String[] txIds, String[] notes); + + private native String signJni(String msg); + + private native boolean verifyJni(String msg, String address, String signature); + + private native String getTxKeyJni(String txId); + + private native String checkTxKeyJni(String txId, String txKey, String address); + + private native String getTxProofJni(String txId, String address, String message); + + private native String checkTxProofJni(String txId, String address, String message, String signature); + + private native String getSpendProofJni(String txId, String message); + + private native boolean checkSpendProofJni(String txId, String message, String signature); + + private native String getReserveProofWalletJni(String message); + + private native String getReserveProofAccountJni(int accountIdx, String amount, String message); + + private native String checkReserveProofJni(String address, String message, String signature); + + private native String createPaymentUriJni(String sendRequestJson); + + private native String parsePaymentUriJni(String uri); + + private native String getAttributeJni(String key); + + private native void setAttributeJni(String key, String val); + + private native void startMiningJni(long numThreads, Boolean backgroundMining, Boolean ignoreBattery); + + private native void stopMiningJni(); + + private native boolean isMultisigImportNeededJni(); + + private native String getMultisigInfoJni(); + + private native String prepareMultisigJni(); + + private native String makeMultisigJni(String[] multisigHexes, int threshold, String password); + + private native String exchangeMultisigKeysJni(String[] multisigHexes, String password); + + private native String getMultisigHexJni(); + + private native int importMultisigHexJni(String[] multisigHexes); + + private native String signMultisigTxHexJni(String multisigTxHex); + + private native String[] submitMultisigTxHexJni(String signedMultisigTxHex); + + private native void saveJni(); + + private native void moveToJni(String path, String password); + + private native void closeJni(boolean save); + + // ------------------------------- LISTENERS -------------------------------- + + /** + * Receives notifications directly from jni c++. + */ + @SuppressWarnings("unused") // called directly from jni c++ + private class WalletJniListener { + + /** + * Enables or disables listening in the c++ wallet. + */ + public void setIsListening(boolean isEnabled) { + jniListenerHandle = setListenerJni(isEnabled ? this : null); + } + + public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { + for (MoneroWalletListenerI listener : listeners) { + listener.onSyncProgress(height, startHeight, endHeight, percentDone, message); + } + } + + public void onNewBlock(long height) { + for (MoneroWalletListenerI listener : listeners) listener.onNewBlock(height); + } + + public void onOutputReceived(long height, String txId, String amountStr, int accountIdx, int subaddressIdx, int version, long unlockTime) { + + // build received output + MoneroOutputWallet output = new MoneroOutputWallet(); + output.setAmount(new BigInteger(amountStr)); + output.setAccountIndex(accountIdx); + output.setSubaddressIndex(subaddressIdx); + MoneroTxWallet tx = new MoneroTxWallet(); + tx.setId(txId); + tx.setVersion(version); + tx.setUnlockTime(unlockTime); + output.setTx(tx); + tx.setVouts(Arrays.asList(output)); + if (height > 0) { + MoneroBlock block = new MoneroBlock().setHeight(height); + block.setTxs(Arrays.asList(tx)); + tx.setBlock(block); + } + + // announce output + for (MoneroWalletListenerI listener : listeners) listener.onOutputReceived((MoneroOutputWallet) tx.getVouts().get(0)); + } + + public void onOutputSpent(long height, String txId, String amountStr, int accountIdx, int subaddressIdx, int version) { + + // build spent output + MoneroOutputWallet output = new MoneroOutputWallet(); + output.setAmount(new BigInteger(amountStr)); + output.setAccountIndex(accountIdx); + output.setSubaddressIndex(subaddressIdx); + MoneroTxWallet tx = new MoneroTxWallet(); + tx.setId(txId); + tx.setVersion(version); + output.setTx(tx); + tx.setVins(Arrays.asList(output)); + if (height > 0) { + MoneroBlock block = new MoneroBlock().setHeight(height); + block.setTxs(Arrays.asList(tx)); + tx.setBlock(block); + } + + // announce output + for (MoneroWalletListenerI listener : listeners) listener.onOutputSpent((MoneroOutputWallet) tx.getVins().get(0)); + } + } + + /** + * Wraps a sync listener as a general wallet listener. + */ + private class SyncListenerWrapper extends MoneroWalletListener { + + private MoneroSyncListener listener; + + public SyncListenerWrapper(MoneroSyncListener listener) { + this.listener = listener; + } + + @Override + public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { + listener.onSyncProgress(height, startHeight, endHeight, percentDone, message); + } + } + + // ------------------------ RESPONSE DESERIALIZATION ------------------------ + + /** + * Override MoneroBlock with wallet types for polymorphic deserialization. + */ + private static class MoneroBlockWallet extends MoneroBlock { + + // default constructor necessary for serialization + @SuppressWarnings("unused") + public MoneroBlockWallet() { + super(); + } + + @JsonProperty("txs") + public MoneroBlockWallet setTxWallets(List txs) { + super.setTxs(new ArrayList(txs)); + return this; + } + + /** + * Initializes a new MoneroBlock with direct references to this block. + * + * TODO: more efficient way to deserialize directly into MoneroBlock? + * + * @return MoneroBlock is the newly initialized block with direct references to this block + */ + public MoneroBlock toBlock() { + MoneroBlock block = new MoneroBlock(); + block.setId(getId()); + block.setHeight(getHeight()); + block.setTimestamp(getTimestamp()); + block.setSize(getSize()); + block.setWeight(getWeight()); + block.setLongTermWeight(getLongTermWeight()); + block.setDepth(getDepth()); + block.setDifficulty(getDifficulty()); + block.setCumulativeDifficulty(getCumulativeDifficulty()); + block.setMajorVersion(getMajorVersion()); + block.setMinorVersion(getMinorVersion()); + block.setNonce(getNonce()); + block.setMinerTxId(getMinerTxId()); + block.setNumTxs(getNumTxs()); + block.setOrphanStatus(getOrphanStatus()); + block.setPrevId(getPrevId()); + block.setReward(getReward()); + block.setPowHash(getPowHash()); + block.setHex(getHex()); + block.setMinerTx(getMinerTx()); + block.setTxs(getTxs()); + block.setTxIds(getTxIds()); + for (MoneroTx tx : getTxs()) tx.setBlock(block); // re-assign tx block references + return block; + } + } + + private static class AccountsContainer { + public List accounts; + }; + + private static class SubaddressesContainer { + public List subaddresses; + }; + + private static class BlocksContainer { + public List blocks; + } + + private static class TxSetsContainer { + public List txSets; + } + + private static class KeyImagesContainer { + public List keyImages; + @SuppressWarnings("unused") public KeyImagesContainer() { } // necessary for serialization + public KeyImagesContainer(List keyImages) { this.keyImages = keyImages; }; + } + + private static List deserializeBlocks(String blocksJson) { + List blockWallets = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, blocksJson, BlocksContainer.class).blocks; + List blocks = new ArrayList(); + if (blockWallets == null) return blocks; + for (MoneroBlockWallet blockWallet: blockWallets) blocks.add(blockWallet.toBlock()); + return blocks; + } + + // ---------------------------- PRIVATE HELPERS ----------------------------- + + private void assertNotClosed() { + if (isClosed) throw new MoneroException("Wallet is closed"); + } + + private static MoneroAccount sanitizeAccount(MoneroAccount account) { + if (account.getSubaddresses() != null) { + for (MoneroSubaddress subaddress : account.getSubaddresses()) sanitizeSubaddress(subaddress); + } + return account; + } + + private static MoneroSubaddress sanitizeSubaddress(MoneroSubaddress subaddress) { + if ("".equals(subaddress.getLabel())) subaddress.setLabel(null); + return subaddress; + } + + private static MoneroBlock sanitizeBlock(MoneroBlock block) { + for (MoneroTx tx : block.getTxs()) sanitizeTxWallet((MoneroTxWallet) tx); + return block; + } + + private static MoneroTxWallet sanitizeTxWallet(MoneroTxWallet tx) { + return tx; + } +} diff --git a/core/src/main/java/monero/wallet/MoneroWalletRpc.java b/core/src/main/java/monero/wallet/MoneroWalletRpc.java new file mode 100644 index 00000000000..d8632ec5aff --- /dev/null +++ b/core/src/main/java/monero/wallet/MoneroWalletRpc.java @@ -0,0 +1,2207 @@ +/** + * Copyright (c) 2017-2019 woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package monero.wallet; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.math.BigInteger; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.log4j.Logger; + +import monero.daemon.model.MoneroBlock; +import monero.daemon.model.MoneroBlockHeader; +import monero.daemon.model.MoneroKeyImage; +import monero.daemon.model.MoneroOutput; +import monero.daemon.model.MoneroTx; +import monero.rpc.MoneroRpcConnection; +import monero.rpc.MoneroRpcException; +import monero.utils.MoneroException; +import monero.wallet.model.MoneroAccount; +import monero.wallet.model.MoneroAccountTag; +import monero.wallet.model.MoneroAddressBookEntry; +import monero.wallet.model.MoneroCheckReserve; +import monero.wallet.model.MoneroCheckTx; +import monero.wallet.model.MoneroDestination; +import monero.wallet.model.MoneroIncomingTransfer; +import monero.wallet.model.MoneroIntegratedAddress; +import monero.wallet.model.MoneroKeyImageImportResult; +import monero.wallet.model.MoneroMultisigInfo; +import monero.wallet.model.MoneroMultisigInitResult; +import monero.wallet.model.MoneroMultisigSignResult; +import monero.wallet.model.MoneroOutgoingTransfer; +import monero.wallet.model.MoneroOutputQuery; +import monero.wallet.model.MoneroOutputWallet; +import monero.wallet.model.MoneroSendRequest; +import monero.wallet.model.MoneroSubaddress; +import monero.wallet.model.MoneroSyncListener; +import monero.wallet.model.MoneroSyncResult; +import monero.wallet.model.MoneroTransfer; +import monero.wallet.model.MoneroTransferQuery; +import monero.wallet.model.MoneroTxQuery; +import monero.wallet.model.MoneroTxSet; +import monero.wallet.model.MoneroTxWallet; + +/** + * Implements a Monero wallet using monero-wallet-rpc. + */ +public class MoneroWalletRpc extends MoneroWalletDefault { + + private String path; // wallet's path identifier + private MoneroRpcConnection rpc; // handles rpc interactions + private Map> addressCache; // cache static addresses to reduce requests + + // static + private static final int ERROR_CODE_INVALID_PAYMENT_ID = -5; // invalid payment id error code + private static final Logger LOGGER = Logger.getLogger(MoneroWalletRpc.class); // logger + private static final TxHeightComparator TX_HEIGHT_COMPARATOR = new TxHeightComparator(); + + public MoneroWalletRpc(URI uri) { + this(new MoneroRpcConnection(uri)); + } + + public MoneroWalletRpc(String uri) { + this(new MoneroRpcConnection(uri)); + } + + public MoneroWalletRpc(String uri, String username, String password) { + this(new MoneroRpcConnection(uri, username, password)); + } + + public MoneroWalletRpc(URI uri, String username, String password) { + this(new MoneroRpcConnection(uri, username, password)); + } + + public MoneroWalletRpc(MoneroRpcConnection rpc) { + this.rpc = rpc; + addressCache = new HashMap>(); + } + + // --------------------------- RPC WALLET METHODS --------------------------- + + /** + * Get the wallet's RPC connection. + * + * @return the wallet's rpc connection + */ + public MoneroRpcConnection getRpcConnection() { + return rpc; + } + + /** + * Open an existing wallet on the RPC server. + * + * @param name is the name of the wallet file to open + * @param password is the wallet's password + */ + public void openWallet(String name, String password) { + if (name == null || name.isEmpty()) throw new MoneroException("Filename is not initialized"); + if (password == null || password.isEmpty()) throw new MoneroException("Password is not initialized"); + Map params = new HashMap(); + params.put("filename", name); + params.put("password", password); + rpc.sendJsonRequest("open_wallet", params); + addressCache.clear(); + path = name; + } + + /** + * Create and open a new wallet with a randomly generated seed on the RPC server. + * + * @param name is the name of the wallet file to create + * @param password is the wallet's password + * @param language is the language for the wallet's mnemonic seed + */ + public void createWalletRandom(String name, String password) { createWalletRandom(name, password, null); } + public void createWalletRandom(String name, String password, String language) { + if (name == null || name.isEmpty()) throw new MoneroException("Wallet name is not initialized"); + if (password == null || password.isEmpty()) throw new MoneroException("Password is not initialized"); + if (language == null || language.isEmpty()) language = DEFAULT_LANGUAGE; + Map params = new HashMap(); + params.put("filename", name); + params.put("password", password); + params.put("language", language); + rpc.sendJsonRequest("create_wallet", params); + path = name; + } + + /** + * Create and open a wallet from an existing mnemonic phrase on the RPC server, + * closing the currently open wallet if applicable. + * + * @param name is the name of the wallet to create on the RPC server + * @param password is the wallet's password + * @param mnemonic is the mnemonic of the wallet to construct + * @param restoreHeight is the block height to restore from (default = 0) + * @param language is the language of the mnemonic in case the old language is invalid + * @param offset is the offset for restoring from mnemonic + * @param saveCurrent specifies if the current RPC wallet should be saved before being closed + */ + public void createWalletFromMnemonic(String name, String password, String mnemonic) { createWalletFromMnemonic(name, password, mnemonic, null, null, null, null); } + public void createWalletFromMnemonic(String name, String password, String mnemonic, Long restoreHeight) { createWalletFromMnemonic(name, password, mnemonic, restoreHeight, null, null, null); } + public void createWalletFromMnemonic(String name, String password, String mnemonic, Long restoreHeight, String language, String offset, Boolean saveCurrent) { + if (language == null) language = DEFAULT_LANGUAGE; + Map params = new HashMap(); + params.put("filename", name); + params.put("password", password); + params.put("seed", mnemonic); + params.put("seed_offset", offset); + params.put("restore_height", restoreHeight); + params.put("language", language); + params.put("autosave_current", saveCurrent); + rpc.sendJsonRequest("restore_deterministic_wallet", params); + path = name; + } + + /** + * Save and close the current wallet and stop the RPC server. + */ + public void stop() { + rpc.sendJsonRequest("stop_wallet"); + addressCache.clear(); + path = null; + } + + // -------------------------- COMMON WALLET METHODS ------------------------- + + @Override + public String getPath() { + return path; + } + + @Override + public String getSeed() { + throw new MoneroException("monero-wallet-rpc does not support getting the wallet seed"); + } + + @SuppressWarnings("unchecked") + @Override + public String getMnemonic() { + Map params = new HashMap(); + params.put("key_type", "mnemonic"); + Map resp = rpc.sendJsonRequest("query_key", params); + Map result = (Map) resp.get("result"); + return (String) result.get("key"); + } + + @SuppressWarnings("unchecked") + @Override + public List getLanguages() { + Map resp = rpc.sendJsonRequest("get_languages"); + Map result = (Map) resp.get("result"); + return (List) result.get("languages"); + } + + @SuppressWarnings("unchecked") + @Override + public String getPrivateViewKey() { + Map params = new HashMap(); + params.put("key_type", "view_key"); + Map resp = rpc.sendJsonRequest("query_key", params); + Map result = (Map) resp.get("result"); + return (String) result.get("key"); + } + + @SuppressWarnings("unchecked") + @Override + public String getPrivateSpendKey() { + Map params = new HashMap(); + params.put("key_type", "spend_key"); + Map resp = rpc.sendJsonRequest("query_key", params); + Map result = (Map) resp.get("result"); + return (String) result.get("key"); + } + + @Override + public String getAddress(int accountIdx, int subaddressIdx) { + Map subaddressMap = addressCache.get(accountIdx); + if (subaddressMap == null) { + getSubaddresses(accountIdx, null, true); // cache's all addresses at this account + return getAddress(accountIdx, subaddressIdx); // uses cache + } + String address = subaddressMap.get(subaddressIdx); + if (address == null) { + getSubaddresses(accountIdx, null, true); // cache's all addresses at this account + return addressCache.get(accountIdx).get(subaddressIdx); + } + return address; + } + + // TODO: use cache + @SuppressWarnings("unchecked") + @Override + public MoneroSubaddress getAddressIndex(String address) { + + // fetch result and normalize error if address does not belong to the wallet + Map result; + try { + Map params = new HashMap(); + params.put("address", address); + Map resp = rpc.sendJsonRequest("get_address_index", params); + result = (Map) resp.get("result"); + } catch (MoneroRpcException e) { + System.out.println(e.getMessage()); + if (e.getCode() == -2) throw new MoneroException(e.getMessage(), e.getCode()); + throw e; + } + + // convert rpc response + Map rpcIndices = (Map) result.get("index"); + MoneroSubaddress subaddress = new MoneroSubaddress(address); + subaddress.setAccountIndex(rpcIndices.get("major").intValue()); + subaddress.setIndex(rpcIndices.get("minor").intValue()); + return subaddress; + } + + @SuppressWarnings("unchecked") + @Override + public MoneroIntegratedAddress getIntegratedAddress(String paymentId) { + try { + Map params = new HashMap(); + params.put("payment_id", paymentId); + Map resp = rpc.sendJsonRequest("make_integrated_address", params); + Map result = (Map) resp.get("result"); + String integratedAddressStr = (String) result.get("integrated_address"); + return decodeIntegratedAddress(integratedAddressStr); + } catch (MoneroRpcException e) { + if (e.getMessage().contains("Invalid payment ID")) throw new MoneroException("Invalid payment ID: " + paymentId, ERROR_CODE_INVALID_PAYMENT_ID); + throw e; + } + } + + @SuppressWarnings("unchecked") + @Override + public MoneroIntegratedAddress decodeIntegratedAddress(String integratedAddress) { + Map params = new HashMap(); + params.put("integrated_address", integratedAddress); + Map resp = rpc.sendJsonRequest("split_integrated_address", params); + Map result = (Map) resp.get("result"); + return new MoneroIntegratedAddress((String) result.get("standard_address"), (String) result.get("payment_id"), integratedAddress); + } + + @SuppressWarnings("unchecked") + @Override + public long getHeight() { + Map resp = rpc.sendJsonRequest("get_height"); + Map result = (Map) resp.get("result"); + return ((BigInteger) result.get("height")).longValue(); + } + + @Override + public long getDaemonHeight() { + throw new MoneroException("monero-wallet-rpc does not support getting the chain height"); + } + + @SuppressWarnings("unchecked") + @Override + public MoneroSyncResult sync(Long startHeight, MoneroSyncListener listener) { + if (listener != null) throw new MoneroException("Monero Wallet RPC does not support reporting sync progress"); + Map params = new HashMap(); + params.put("start_height", startHeight); + Map resp = rpc.sendJsonRequest("refresh", params); + Map result = (Map) resp.get("result"); + return new MoneroSyncResult(((BigInteger) result.get("blocks_fetched")).longValue(), (Boolean) result.get("received_money")); + } + + @Override + public void startSyncing() { + // nothing to do because wallet rpc syncs automatically + } + + @Override + public void stopSyncing() { + throw new MoneroException("Monero Wallet RPC does not support the ability to stop syncing"); + } + + @Override + public void rescanSpent() { + rpc.sendJsonRequest("rescan_spent"); + } + + public void rescanBlockchain() { + rpc.sendJsonRequest("rescan_blockchain"); + } + + @Override + public BigInteger getBalance() { + return getBalances(null, null)[0]; + } + + @Override + public BigInteger getBalance(int accountIdx) { + return getBalances(accountIdx, null)[0]; + } + + @Override + public BigInteger getBalance(int accountIdx, int subaddressIdx) { + return getBalances(accountIdx, subaddressIdx)[0]; + } + + @Override + public BigInteger getUnlockedBalance() { + return getBalances(null, null)[1]; + } + + @Override + public BigInteger getUnlockedBalance(int accountIdx) { + return getBalances(accountIdx, null)[1]; + } + + @Override + public BigInteger getUnlockedBalance(int accountIdx, int subaddressIdx) { + return getBalances(accountIdx, subaddressIdx)[1]; + } + + @Override + public List getAccounts(boolean includeSubaddresses, String tag) { + return getAccounts(includeSubaddresses, tag, false); + } + + @SuppressWarnings("unchecked") + public List getAccounts(boolean includeSubaddresses, String tag, boolean skipBalances) { + + // fetch accounts from rpc + Map params = new HashMap(); + params.put("tag", tag); + Map resp = rpc.sendJsonRequest("get_accounts", params); + Map result = (Map) resp.get("result"); + + // build account objects and fetch subaddresses per account using get_address + // TODO monero-wallet-rpc: get_address should support all_accounts so not called once per account + List accounts = new ArrayList(); + for (Map rpcAccount : (List>) result.get("subaddress_accounts")) { + MoneroAccount account = convertRpcAccount(rpcAccount); + if (includeSubaddresses) account.setSubaddresses(getSubaddresses(account.getIndex(), null, true)); + accounts.add(account); + } + + // fetch and merge fields from get_balance across all accounts + if (includeSubaddresses && !skipBalances) { + + // these fields are not initialized if subaddress is unused and therefore not returned from `get_balance` + for (MoneroAccount account : accounts) { + for (MoneroSubaddress subaddress : account.getSubaddresses()) { + subaddress.setBalance(BigInteger.valueOf(0)); + subaddress.setUnlockedBalance(BigInteger.valueOf(0)); + subaddress.setNumUnspentOutputs(0l); + subaddress.setNumBlocksToUnlock(0l); + } + } + + // fetch and merge info from get_balance + params.clear(); + params.put("all_accounts", true); + resp = rpc.sendJsonRequest("get_balance", params); + result = (Map) resp.get("result"); + if (result.containsKey("per_subaddress")) { + for (Map rpcSubaddress : (List>) result.get("per_subaddress")) { + MoneroSubaddress subaddress = convertRpcSubaddress(rpcSubaddress); + + // merge info + MoneroAccount account = accounts.get(subaddress.getAccountIndex()); + assertEquals("RPC accounts are out of order", account.getIndex(), subaddress.getAccountIndex()); // would need to switch lookup to loop + MoneroSubaddress tgtSubaddress = account.getSubaddresses().get(subaddress.getIndex()); + assertEquals("RPC subaddresses are out of order", tgtSubaddress.getIndex(), subaddress.getIndex()); + if (subaddress.getBalance() != null) tgtSubaddress.setBalance(subaddress.getBalance()); + if (subaddress.getUnlockedBalance() != null) tgtSubaddress.setUnlockedBalance(subaddress.getUnlockedBalance()); + if (subaddress.getNumUnspentOutputs() != null) tgtSubaddress.setNumUnspentOutputs(subaddress.getNumUnspentOutputs()); + if (subaddress.getNumBlocksToUnlock() != null) tgtSubaddress.setNumBlocksToUnlock(subaddress.getNumBlocksToUnlock()); + } + } + } + + // return accounts + return accounts; + } + + // TODO: getAccountByIndex(), getAccountByTag() + @Override + public MoneroAccount getAccount(int accountIdx, boolean includeSubaddresses) { + return getAccount(accountIdx, includeSubaddresses, false); + } + + public MoneroAccount getAccount(int accountIdx, boolean includeSubaddresses, boolean skipBalances) { + if (accountIdx < 0) throw new MoneroException("Account index must be greater than or equal to 0"); + for (MoneroAccount account : getAccounts()) { + if (account.getIndex() == accountIdx) { + if (includeSubaddresses) account.setSubaddresses(getSubaddresses(accountIdx, null, skipBalances)); + return account; + } + } + throw new MoneroException("Account with index " + accountIdx + " does not exist"); + } + + @SuppressWarnings("unchecked") + @Override + public MoneroAccount createAccount(String label) { + label = label == null || label.isEmpty() ? null : label; + Map params = new HashMap(); + params.put("label", label); + Map resp = rpc.sendJsonRequest("create_account", params); + Map result = (Map) resp.get("result"); + return new MoneroAccount(((BigInteger) result.get("account_index")).intValue(), (String) result.get("address"), BigInteger.valueOf(0), BigInteger.valueOf(0), null); + } + + @Override + public List getSubaddresses(int accountIdx, List subaddressIndices) { + return getSubaddresses(accountIdx, subaddressIndices, false); + } + + @SuppressWarnings("unchecked") + public List getSubaddresses(int accountIdx, List subaddressIndices, boolean skipBalances) { + + // fetch subaddresses + Map params = new HashMap(); + params.put("account_index", accountIdx); + if (subaddressIndices != null && !subaddressIndices.isEmpty()) params.put("address_index", subaddressIndices); + Map resp = rpc.sendJsonRequest("get_address", params); + Map result = (Map) resp.get("result"); + + // initialize subaddresses + List subaddresses = new ArrayList(); + for (Map rpcSubaddress : (List>) result.get("addresses")) { + MoneroSubaddress subaddress = convertRpcSubaddress(rpcSubaddress); + subaddress.setAccountIndex(accountIdx); + subaddresses.add(subaddress); + } + + // fetch and initialize subaddress balances + if (!skipBalances) { + + // these fields are not initialized if subaddress is unused and therefore not returned from `get_balance` + for (MoneroSubaddress subaddress : subaddresses) { + subaddress.setBalance(BigInteger.valueOf(0)); + subaddress.setUnlockedBalance(BigInteger.valueOf(0)); + subaddress.setNumUnspentOutputs(0l); + subaddress.setNumBlocksToUnlock(0l); + } + + // fetch and initialize balances + resp = rpc.sendJsonRequest("get_balance", params); + result = (Map) resp.get("result"); + if (result.containsKey("per_subaddress")) { + for (Map rpcSubaddress : (List>) result.get("per_subaddress")) { + MoneroSubaddress subaddress = convertRpcSubaddress(rpcSubaddress); + + // transfer info to existing subaddress object + for (MoneroSubaddress tgtSubaddress : subaddresses) { + if (!tgtSubaddress.getIndex().equals(subaddress.getIndex())) continue; // skip to subaddress with same index + if (subaddress.getBalance() != null) tgtSubaddress.setBalance(subaddress.getBalance()); + if (subaddress.getUnlockedBalance() != null) tgtSubaddress.setUnlockedBalance(subaddress.getUnlockedBalance()); + if (subaddress.getNumUnspentOutputs() != null) tgtSubaddress.setNumUnspentOutputs(subaddress.getNumUnspentOutputs()); + if (subaddress.getNumBlocksToUnlock() != null) tgtSubaddress.setNumBlocksToUnlock(subaddress.getNumBlocksToUnlock()); + } + } + } + } + + // cache addresses + Map subaddressMap = addressCache.get(accountIdx); + if (subaddressMap == null) { + subaddressMap = new HashMap(); + addressCache.put(accountIdx, subaddressMap); + } + for (MoneroSubaddress subaddress : subaddresses) { + subaddressMap.put(subaddress.getIndex(), subaddress.getAddress()); + } + + // return results + return subaddresses; + } + + @SuppressWarnings("unchecked") + @Override + public MoneroSubaddress createSubaddress(int accountIdx, String label) { + + // send request + Map params = new HashMap(); + params.put("account_index", accountIdx); + params.put("label", label); + Map resp = rpc.sendJsonRequest("create_address", params); + Map result = (Map) resp.get("result"); + + // build subaddress object + MoneroSubaddress subaddress = new MoneroSubaddress(); + subaddress.setAccountIndex(accountIdx); + subaddress.setIndex(((BigInteger) result.get("address_index")).intValue()); + subaddress.setAddress((String) result.get("address")); + subaddress.setLabel(label); + subaddress.setBalance(BigInteger.valueOf(0)); + subaddress.setUnlockedBalance(BigInteger.valueOf(0)); + subaddress.setNumUnspentOutputs(0l); + subaddress.setIsUsed(false); + subaddress.setNumBlocksToUnlock(0l); + return subaddress; + } + + @Override + public List getTxs(MoneroTxQuery query) { + + // copy and normalize tx query + query = query == null ? new MoneroTxQuery() : query.copy(); + if (query.getTransferQuery() == null) query.setTransferQuery(new MoneroTransferQuery()); + if (query.getOutputQuery() == null) query.setOutputQuery(new MoneroOutputQuery()); + + // temporarily disable transfer and output queries in order to collect all tx information + MoneroTransferQuery transferQuery = query.getTransferQuery(); + MoneroOutputQuery outputQuery = query.getOutputQuery(); + query.setTransferQuery(null); + query.setOutputQuery(null); + + // fetch all transfers that meet tx query + List transfers = getTransfers(new MoneroTransferQuery().setTxQuery(query)); + + // collect unique txs from transfers while retaining order + List txs = new ArrayList(); + Set txsSet = new HashSet(); + for (MoneroTransfer transfer : transfers) { + if (!txsSet.contains(transfer.getTx())) { + txs.add(transfer.getTx()); + txsSet.add(transfer.getTx()); + } + } + + // cache types into maps for merging and lookup + Map txMap = new HashMap(); + Map blockMap = new HashMap(); + for (MoneroTxWallet tx : txs) { + mergeTx(tx, txMap, blockMap, false); + } + + // fetch and merge outputs if queried + if (Boolean.TRUE.equals(query.getIncludeOutputs()) || !outputQuery.isDefault()) { + List outputs = getOutputs(new MoneroOutputQuery().setTxQuery(query)); + + // merge output txs one time while retaining order + Set outputTxs = new HashSet(); + for (MoneroOutputWallet output : outputs) { + if (!outputTxs.contains(output.getTx())) { + mergeTx(output.getTx(), txMap, blockMap, true); + outputTxs.add(output.getTx()); + } + } + } + + // restore transfer and output queries + query.setTransferQuery(transferQuery); + query.setOutputQuery(outputQuery); + + // filter txs that don't meet transfer and output queries + List txsQueried = new ArrayList(); + for (MoneroTxWallet tx : txs) { + if (query.meetsCriteria(tx)) txsQueried.add(tx); + else if (tx.getBlock() != null) tx.getBlock().getTxs().remove(tx); + } + txs = txsQueried; + + // verify all specified tx ids found + if (query.getTxIds() != null) { + for (String txId : query.getTxIds()) { + boolean found = false; + for (MoneroTxWallet tx : txs) { + if (txId.equals(tx.getId())) { + found = true; + break; + } + } + if (!found) throw new MoneroException("Tx not found in wallet: " + txId); + } + } + + // special case: re-fetch txs if inconsistency caused by needing to make multiple rpc calls + for (MoneroTxWallet tx : txs) { + if (tx.isConfirmed() && tx.getBlock() == null) return getTxs(query); + } + + // order txs if tx ids given + if (query.getTxIds() != null && !query.getTxIds().isEmpty()) { + Map txsById = new HashMap(); // store txs in temporary map for sorting + for (MoneroTxWallet tx : txs) txsById.put(tx.getId(), tx); + List orderedTxs = new ArrayList(); + for (String txId : query.getTxIds()) orderedTxs.add(txsById.get(txId)); + txs = orderedTxs; + } + return txs; + } + + @SuppressWarnings("unchecked") + @Override + public List getTransfers(MoneroTransferQuery query) { + + // copy and normalize query up to block + if (query == null) query = new MoneroTransferQuery(); + else { + if (query.getTxQuery() == null) query = query.copy(); + else { + MoneroTxQuery txQuery = query.getTxQuery().copy(); + if (query.getTxQuery().getTransferQuery() == query) query = txQuery.getTransferQuery(); + else { + assertNull("Transfer query's tx query must be circular reference or null", query.getTxQuery().getTransferQuery()); + query = query.copy(); + query.setTxQuery(txQuery); + } + } + } + if (query.getTxQuery() == null) query.setTxQuery(new MoneroTxQuery()); + MoneroTxQuery txQuery = query.getTxQuery(); + txQuery.setTransferQuery(null); // break circular link for meetsCriteria() + + // build params for get_transfers rpc call + Map params = new HashMap(); + boolean canBeConfirmed = !Boolean.FALSE.equals(txQuery.isConfirmed()) && !Boolean.TRUE.equals(txQuery.inTxPool()) && !Boolean.TRUE.equals(txQuery.isFailed()) && !Boolean.FALSE.equals(txQuery.isRelayed()); + boolean canBeInTxPool = !Boolean.TRUE.equals(txQuery.isConfirmed()) && !Boolean.FALSE.equals(txQuery.inTxPool()) && !Boolean.TRUE.equals(txQuery.isFailed()) && !Boolean.FALSE.equals(txQuery.isRelayed()) && txQuery.getHeight() == null && txQuery.getMinHeight() == null && txQuery.getMaxHeight() == null; + boolean canBeIncoming = !Boolean.FALSE.equals(query.isIncoming()) && !Boolean.TRUE.equals(query.isOutgoing()) && !Boolean.TRUE.equals(query.hasDestinations()); + boolean canBeOutgoing = !Boolean.FALSE.equals(query.isOutgoing()) && !Boolean.TRUE.equals(query.isIncoming()); + params.put("in", canBeIncoming && canBeConfirmed); + params.put("out", canBeOutgoing && canBeConfirmed); + params.put("pool", canBeIncoming && canBeInTxPool); + params.put("pending", canBeOutgoing && canBeInTxPool); + params.put("failed", !Boolean.FALSE.equals(txQuery.isFailed()) && !Boolean.TRUE.equals(txQuery.isConfirmed()) && !Boolean.TRUE.equals(txQuery.inTxPool())); + if (txQuery.getMinHeight() != null) { + if (txQuery.getMinHeight() > 0) params.put("min_height", txQuery.getMinHeight() - 1); // TODO monero core: wallet2::get_payments() min_height is exclusive, so manually offset to match intended range (issues #5751, #5598) + else params.put("min_height", txQuery.getMinHeight()); + } + if (txQuery.getMaxHeight() != null) params.put("max_height", txQuery.getMaxHeight()); + params.put("filter_by_height", txQuery.getMinHeight() != null || txQuery.getMaxHeight() != null); + if (query.getAccountIndex() == null) { + assertTrue("Filter specifies a subaddress index but not an account index", query.getSubaddressIndex() == null && query.getSubaddressIndices() == null); + params.put("all_accounts", true); + } else { + params.put("account_index", query.getAccountIndex()); + + // set subaddress indices param + Set subaddressIndices = new HashSet(); + if (query.getSubaddressIndex() != null) subaddressIndices.add(query.getSubaddressIndex()); + if (query.getSubaddressIndices() != null) { + for (int subaddressIdx : query.getSubaddressIndices()) subaddressIndices.add(subaddressIdx); + } + if (!subaddressIndices.isEmpty()) params.put("subaddr_indices", new ArrayList(subaddressIndices)); + } + + // cache unique txs and blocks + Map txMap = new HashMap(); + Map blockMap = new HashMap(); + + // build txs using `get_transfers` + Map resp = rpc.sendJsonRequest("get_transfers", params); + Map result = (Map) resp.get("result"); + for (String key : result.keySet()) { + for (Map rpcTx :((List>) result.get(key))) { + MoneroTxWallet tx = convertRpcTxWithTransfer(rpcTx, null, null); + if (tx.isConfirmed()) assertTrue(tx.getBlock().getTxs().contains(tx)); +// if (tx.getId().equals("38436c710dfbebfb24a14cddfd430d422e7282bbe94da5e080643a1bd2880b44")) { +// System.out.println(rpcTx); +// System.out.println(tx.getOutgoingAmount().compareTo(BigInteger.valueOf(0)) == 0); +// } + + // replace transfer amount with destination sum + // TODO monero-wallet-rpc: confirmed tx from/to same account has amount 0 but cached transfers + if (tx.getOutgoingTransfer() != null && Boolean.TRUE.equals(tx.isRelayed()) && !Boolean.TRUE.equals(tx.isFailed()) && + tx.getOutgoingTransfer().getDestinations() != null && tx.getOutgoingAmount().compareTo(BigInteger.valueOf(0)) == 0) { + MoneroOutgoingTransfer outgoingTransfer = tx.getOutgoingTransfer(); + BigInteger transferTotal = BigInteger.valueOf(0); + for (MoneroDestination destination : outgoingTransfer.getDestinations()) transferTotal = transferTotal.add(destination.getAmount()); + tx.getOutgoingTransfer().setAmount(transferTotal); + } + + // merge tx + mergeTx(tx, txMap, blockMap, false); + } + } + + // sort txs by block height + List txs = new ArrayList(txMap.values()); + Collections.sort(txs, new TxHeightComparator()); + + // filter and return transfers + List transfers = new ArrayList(); + for (MoneroTxWallet tx : txs) { + + // sort transfers + if (tx.getIncomingTransfers() != null) Collections.sort(tx.getIncomingTransfers(), new IncomingTransferComparator()); + + // collect outgoing transfer, erase if excluded + if (tx.getOutgoingTransfer() != null && query.meetsCriteria(tx.getOutgoingTransfer())) transfers.add(tx.getOutgoingTransfer()); + else tx.setOutgoingTransfer(null); + + // collect incoming transfers, erase if excluded + if (tx.getIncomingTransfers() != null) { + List toRemoves = new ArrayList(); + for (MoneroIncomingTransfer transfer : tx.getIncomingTransfers()) { + if (query.meetsCriteria(transfer)) transfers.add(transfer); + else toRemoves.add(transfer); + } + tx.getIncomingTransfers().removeAll(toRemoves); + if (tx.getIncomingTransfers().isEmpty()) tx.setIncomingTransfers(null); + } + + // remove excluded txs from block + if (tx.getBlock() != null && tx.getOutgoingTransfer() == null && tx.getIncomingTransfers() == null ) { + tx.getBlock().getTxs().remove(tx); + } + } + return transfers; + } + + @SuppressWarnings("unchecked") + @Override + public List getOutputs(MoneroOutputQuery query) { + + // copy and normalize query up to block + if (query == null) query = new MoneroOutputQuery(); + else { + if (query.getTxQuery() == null) query = query.copy(); + else { + MoneroTxQuery txQuery = query.getTxQuery().copy(); + if (query.getTxQuery().getOutputQuery() == query) query = txQuery.getOutputQuery(); + else { + assertNull("Output request's tx request must be circular reference or null", query.getTxQuery().getOutputQuery()); + query = query.copy(); + query.setTxQuery(txQuery); + } + } + } + if (query.getTxQuery() == null) query.setTxQuery(new MoneroTxQuery()); + MoneroTxQuery txQuery = query.getTxQuery(); + txQuery.setOutputQuery(null); // break circular link for meetsCriteria() + + // determine account and subaddress indices to be queried + Map> indices = new HashMap>(); + if (query.getAccountIndex() != null) { + Set subaddressIndices = new HashSet(); + if (query.getSubaddressIndex() != null) subaddressIndices.add(query.getSubaddressIndex()); + if (query.getSubaddressIndices() != null) for (int subaddressIdx : query.getSubaddressIndices()) subaddressIndices.add(subaddressIdx); + indices.put(query.getAccountIndex(), subaddressIndices.isEmpty() ? null : new ArrayList(subaddressIndices)); // null will fetch from all subaddresses + } else { + assertEquals("Request specifies a subaddress index but not an account index", null, query.getSubaddressIndex()); + assertTrue("Request specifies subaddress indices but not an account index", query.getSubaddressIndices() == null || query.getSubaddressIndices().size() == 0); + indices = getAccountIndices(false); // fetch all account indices without subaddresses + } + + // cache unique txs and blocks + Map txMap = new HashMap(); + Map blockMap = new HashMap(); + + // collect txs with vouts for each indicated account using `incoming_transfers` rpc call + Map params = new HashMap(); + String transferType; + if (Boolean.TRUE.equals(query.isSpent())) transferType = "unavailable"; + else if (Boolean.FALSE.equals(query.isSpent())) transferType = "available"; + else transferType = "all"; + params.put("transfer_type", transferType); + params.put("verbose", true); + for (int accountIdx : indices.keySet()) { + + // send request + params.put("account_index", accountIdx); + params.put("subaddr_indices", indices.get(accountIdx)); + Map resp = rpc.sendJsonRequest("incoming_transfers", params); + Map result = (Map) resp.get("result"); + + // convert response to txs with vouts and merge + if (!result.containsKey("transfers")) continue; + for (Map rpcVout : (List>) result.get("transfers")) { + MoneroTxWallet tx = convertRpcTxWithVout(rpcVout); + mergeTx(tx, txMap, blockMap, false); + } + } + + // sort txs by block height + List txs = new ArrayList(txMap.values()); + Collections.sort(txs, new TxHeightComparator()); + + // collect queried vouts + List vouts = new ArrayList(); + for (MoneroTxWallet tx : txs) { + + // sort vouts + if (tx.getVouts() != null) Collections.sort(tx.getVouts(), new VoutComparator()); + + // collect queried vouts + List toRemoves = new ArrayList(); + for (MoneroOutput vout : tx.getVouts()) { + if (query.meetsCriteria((MoneroOutputWallet) vout)) vouts.add((MoneroOutputWallet) vout); + else toRemoves.add(vout); + } + + // remove excluded vouts from tx + tx.getVouts().removeAll(toRemoves); + + // remove excluded txs from block + if (tx.getVouts().isEmpty() && tx.getBlock() != null) tx.getBlock().getTxs().remove(tx); + } + return vouts; + } + + @SuppressWarnings("unchecked") + @Override + public String getOutputsHex() { + Map resp = rpc.sendJsonRequest("export_outputs"); + Map result = (Map) resp.get("result"); + return (String) result.get("outputs_data_hex"); + } + + @SuppressWarnings("unchecked") + @Override + public int importOutputsHex(String outputsHex) { + Map params = new HashMap(); + params.put("outputs_data_hex", outputsHex); + Map resp = rpc.sendJsonRequest("import_outputs", params); + Map result = (Map) resp.get("result"); + return ((BigInteger) result.get("num_imported")).intValue(); + } + + @Override + public List getKeyImages() { + return rpcExportKeyImages(true); + } + + @SuppressWarnings("unchecked") + @Override + public MoneroKeyImageImportResult importKeyImages(List keyImages) { + + // convert key images to rpc parameter + List> rpcKeyImages = new ArrayList>(); + for (MoneroKeyImage keyImage : keyImages) { + Map rpcKeyImage = new HashMap(); + rpcKeyImage.put("key_image", keyImage.getHex()); + rpcKeyImage.put("signature", keyImage.getSignature()); + rpcKeyImages.add(rpcKeyImage); + } + + // send rpc request + Map params = new HashMap(); + params.put("signed_key_images", rpcKeyImages); + Map resp = rpc.sendJsonRequest("import_key_images", params); + Map result = (Map) resp.get("result"); + + // build and return result + MoneroKeyImageImportResult importResult = new MoneroKeyImageImportResult(); + importResult.setHeight(((BigInteger) result.get("height")).longValue()); + importResult.setSpentAmount((BigInteger) result.get("spent")); + importResult.setUnspentAmount((BigInteger) result.get("unspent")); + return importResult; + } + + @Override + public List getNewKeyImagesFromLastImport() { + return rpcExportKeyImages(false); + } + + @SuppressWarnings("unchecked") + @Override + public List relayTxs(Collection txMetadatas) { + if (txMetadatas == null || txMetadatas.isEmpty()) throw new MoneroException("Must provide an array of tx metadata to relay"); + List txIds = new ArrayList(); + for (String txMetadata : txMetadatas) { + Map params = new HashMap(); + params.put("hex", txMetadata); + Map resp = rpc.sendJsonRequest("relay_tx", params); + Map result = (Map) resp.get("result"); + txIds.add((String) result.get("tx_hash")); + } + return txIds; + } + + @SuppressWarnings("unchecked") + public MoneroTxSet sendSplit(MoneroSendRequest request) { + + // validate, copy, and sanitize request + if (request == null) throw new MoneroException("Send request cannot be null"); + assertNotNull(request.getDestinations()); + assertNull(request.getSweepEachSubaddress()); + assertNull(request.getBelowAmount()); + if (request.getCanSplit() == null) { + request = request.copy(); + request.setCanSplit(true); + } + + // determine account and subaddresses to send from + Integer accountIdx = request.getAccountIndex(); + if (accountIdx == null) throw new MoneroException("Must specify the account index to send from"); + List subaddressIndices = request.getSubaddressIndices() == null ? null : new ArrayList(request.getSubaddressIndices()); // fetch all or copy given indices + + // build request parameters + Map params = new HashMap(); + List> destinationMaps = new ArrayList>(); + params.put("destinations", destinationMaps); + for (MoneroDestination destination : request.getDestinations()) { + assertNotNull("Destination address is not defined", destination.getAddress()); + assertNotNull("Destination amount is not defined", destination.getAmount()); + Map destinationMap = new HashMap(); + destinationMap.put("address", destination.getAddress()); + destinationMap.put("amount", destination.getAmount().toString()); + destinationMaps.add(destinationMap); + } + params.put("account_index", accountIdx); + params.put("subaddr_indices", subaddressIndices); + params.put("payment_id", request.getPaymentId()); + params.put("mixin", request.getMixin()); + params.put("ring_size", request.getRingSize()); + params.put("unlock_time", request.getUnlockTime()); + params.put("do_not_relay", request.getDoNotRelay()); + params.put("priority", request.getPriority() == null ? null : request.getPriority().ordinal()); + params.put("get_tx_hex", true); + params.put("get_tx_metadata", true); + if (request.getCanSplit()) params.put("get_tx_keys", true); // param to get tx key(s) depends if split + else params.put("get_tx_key", true); + + // send request + Map resp = rpc.sendJsonRequest(request.getCanSplit() ? "transfer_split" : "transfer", params); + Map result = (Map) resp.get("result"); + + // pre-initialize txs iff present. multisig and watch-only wallets will have tx set without transactions + List txs = null; + int numTxs = request.getCanSplit() ? (result.containsKey("fee_list") ? ((List) result.get("fee_list")).size() : 0) : (result.containsKey("fee") ? 1 : 0); + if (numTxs > 0) txs = new ArrayList(); + for (int i = 0; i < numTxs; i++) { + MoneroTxWallet tx = new MoneroTxWallet(); + initSentTxWallet(request, tx); + tx.getOutgoingTransfer().setAccountIndex(accountIdx); + if (subaddressIndices != null && subaddressIndices.size() == 1) tx.getOutgoingTransfer().setSubaddressIndices(subaddressIndices); + txs.add(tx); + } + + // initialize tx set from rpc response with pre-initialized txs + if (request.getCanSplit()) return convertRpcSentTxsToTxSet(result, txs); + else return convertRpcTxToTxSet(result, txs == null ? null : txs.get(0), true); + } + + @SuppressWarnings("unchecked") + @Override + public MoneroTxSet sweepOutput(MoneroSendRequest request) { + + // validate request + assertNull(request.getSweepEachSubaddress()); + assertNull(request.getBelowAmount()); + assertNull("Splitting is not applicable when sweeping output", request.getCanSplit()); + + // build request parameters + Map params = new HashMap(); + params.put("address", request.getDestinations().get(0).getAddress()); + params.put("account_index", request.getAccountIndex()); + params.put("subaddr_indices", request.getSubaddressIndices()); + params.put("key_image", request.getKeyImage()); + params.put("mixin", request.getMixin()); + params.put("ring_size", request.getRingSize()); + params.put("unlock_time", request.getUnlockTime()); + params.put("do_not_relay", request.getDoNotRelay()); + params.put("priority", request.getPriority() == null ? null : request.getPriority().ordinal()); + params.put("payment_id", request.getPaymentId()); + params.put("get_tx_key", true); + params.put("get_tx_hex", true); + params.put("get_tx_metadata", true); + + // send request + Map resp = (Map) rpc.sendJsonRequest("sweep_single", params); + Map result = (Map) resp.get("result"); + + // build and return tx response + MoneroTxWallet tx = initSentTxWallet(request, null); + MoneroTxSet txSet = convertRpcTxToTxSet(result, tx, true); + tx.getOutgoingTransfer().getDestinations().get(0).setAmount(tx.getOutgoingTransfer().getAmount()); // initialize destination amount + return txSet; + } + + @Override + public List sweepUnlocked(MoneroSendRequest request) { + + // validate request + if (request == null) throw new MoneroException("Sweep request cannot be null"); + if (request.getDestinations() == null || request.getDestinations().size() != 1) throw new MoneroException("Must specify exactly one destination to sweep to"); + if (request.getDestinations().get(0).getAddress() == null) throw new MoneroException("Must specify destination address to sweep to"); + if (request.getDestinations().get(0).getAmount() != null) throw new MoneroException("Cannot specify amount in sweep request"); + if (request.getKeyImage() != null) throw new MoneroException("Key image defined; use sweepOutput() to sweep an output by its key image"); + if (request.getSubaddressIndices() != null && request.getSubaddressIndices().isEmpty()) request.setSubaddressIndices((List) null); + if (request.getAccountIndex() == null && request.getSubaddressIndices() != null) throw new MoneroException("Must specify account index if subaddress indices are specified"); + + // determine account and subaddress indices to sweep; default to all with unlocked balance if not specified + LinkedHashMap> indices = new LinkedHashMap>(); // java type preserves insertion order + if (request.getAccountIndex() != null) { + if (request.getSubaddressIndices() != null) { + indices.put(request.getAccountIndex(), request.getSubaddressIndices()); + } else { + List subaddressIndices = new ArrayList(); + indices.put(request.getAccountIndex(), subaddressIndices); + for (MoneroSubaddress subaddress : getSubaddresses(request.getAccountIndex())) { + if (subaddress.getUnlockedBalance().compareTo(BigInteger.valueOf(0)) > 0) subaddressIndices.add(subaddress.getIndex()); + } + } + } else { + List accounts = getAccounts(true); + for (MoneroAccount account : accounts) { + if (account.getUnlockedBalance().compareTo(BigInteger.valueOf(0)) > 0) { + List subaddressIndices = new ArrayList(); + indices.put(account.getIndex(), subaddressIndices); + for (MoneroSubaddress subaddress : account.getSubaddresses()) { + if (subaddress.getUnlockedBalance().compareTo(BigInteger.valueOf(0)) > 0) subaddressIndices.add(subaddress.getIndex()); + } + } + } + } + + // sweep from each account and collect resulting tx sets + List txSets = new ArrayList(); + for (Integer accountIdx : indices.keySet()) { + + // copy and modify the original request + MoneroSendRequest copy = request.copy(); + copy.setAccountIndex(accountIdx); + copy.setSweepEachSubaddress(false); + + // sweep all subaddresses together // TODO monero core: can this reveal outputs belong to the same wallet? + if (!Boolean.TRUE.equals(copy.getSweepEachSubaddress())) { + copy.setSubaddressIndices(indices.get(accountIdx)); + txSets.add(rpcSweepAccount(copy)); + } + + // otherwise sweep each subaddress individually + else { + for (int subaddressIdx : indices.get(accountIdx)) { + copy.setSubaddressIndices(subaddressIdx); + txSets.add(rpcSweepAccount(copy)); + } + } + } + + // return resulting tx sets + return txSets; + } + + @SuppressWarnings("unchecked") + @Override + public MoneroTxSet sweepDust(boolean doNotRelay) { + Map params = new HashMap(); + params.put("do_not_relay", doNotRelay); + Map resp = rpc.sendJsonRequest("sweep_dust", params); + Map result = (Map) resp.get("result"); + MoneroTxSet txSet = convertRpcSentTxsToTxSet(result, null); + if (txSet.getTxs() != null) { + for (MoneroTxWallet tx : txSet.getTxs()) { + tx.setIsRelayed(!doNotRelay); + tx.setInTxPool(tx.isRelayed()); + } + } else if (txSet.getMultisigTxHex() == null && txSet.getSignedTxHex() == null && txSet.getUnsignedTxHex() == null) { + throw new MoneroException("No dust to sweep"); + } + return txSet; + } + + @SuppressWarnings("unchecked") + @Override + public String sign(String msg) { + Map params = new HashMap(); + params.put("data", msg); + Map resp = rpc.sendJsonRequest("sign", params); + Map result = (Map) resp.get("result"); + return (String) result.get("signature"); + } + + @SuppressWarnings("unchecked") + @Override + public boolean verify(String msg, String address, String signature) { + Map params = new HashMap(); + params.put("data", msg); + params.put("address", address); + params.put("signature", signature); + Map resp = rpc.sendJsonRequest("verify", params); + Map result = (Map) resp.get("result"); + return (boolean) result.get("good"); + } + + @SuppressWarnings("unchecked") + @Override + public String getTxKey(String txId) { + Map params = new HashMap(); + params.put("txid", txId); + Map resp = rpc.sendJsonRequest("get_tx_key", params); + Map result = (Map) resp.get("result"); + return (String) result.get("tx_key"); + } + + @SuppressWarnings("unchecked") + @Override + public MoneroCheckTx checkTxKey(String txId, String txKey, String address) { + + // send request + Map params = new HashMap(); + params.put("txid", txId); + params.put("tx_key", txKey); + params.put("address", address); + Map resp = rpc.sendJsonRequest("check_tx_key", params); + + // interpret result + Map result = (Map) resp.get("result"); + MoneroCheckTx check = new MoneroCheckTx(); + check.setIsGood(true); + check.setNumConfirmations(((BigInteger) result.get("confirmations")).longValue()); + check.setInTxPool((Boolean) result.get("in_pool")); + check.setReceivedAmount((BigInteger) result.get("received")); + return check; + } + + @SuppressWarnings("unchecked") + @Override + public String getTxProof(String txId, String address, String message) { + Map params = new HashMap(); + params.put("txid", txId); + params.put("address", address); + params.put("message", message); + Map resp = rpc.sendJsonRequest("get_tx_proof", params); + Map result = (Map) resp.get("result"); + return (String) result.get("signature"); + } + + @SuppressWarnings("unchecked") + @Override + public MoneroCheckTx checkTxProof(String txId, String address, String message, String signature) { + + // send request + Map params = new HashMap(); + params.put("txid", txId); + params.put("address", address); + params.put("message", message); + params.put("signature", signature); + Map resp = rpc.sendJsonRequest("check_tx_proof", params); + + // interpret response + Map result = (Map) resp.get("result"); + boolean isGood = (boolean) result.get("good"); + MoneroCheckTx check = new MoneroCheckTx(); + check.setIsGood(isGood); + if (isGood) { + check.setNumConfirmations(((BigInteger) result.get("confirmations")).longValue()); + check.setInTxPool((boolean) result.get("in_pool")); + check.setReceivedAmount((BigInteger) result.get("received")); + } + return check; + } + + @SuppressWarnings("unchecked") + @Override + public String getSpendProof(String txId, String message) { + Map params = new HashMap(); + params.put("txid", txId); + params.put("message", message); + Map resp = rpc.sendJsonRequest("get_spend_proof", params); + Map result = (Map) resp.get("result"); + return (String) result.get("signature"); + } + + @SuppressWarnings("unchecked") + @Override + public boolean checkSpendProof(String txId, String message, String signature) { + Map params = new HashMap(); + params.put("txid", txId); + params.put("message", message); + params.put("signature", signature); + Map resp = rpc.sendJsonRequest("check_spend_proof", params); + Map result = (Map) resp.get("result"); + return (boolean) result.get("good"); + } + + @SuppressWarnings("unchecked") + @Override + public String getReserveProofWallet(String message) { + Map params = new HashMap(); + params.put("all", true); + params.put("message", message); + Map resp = rpc.sendJsonRequest("get_reserve_proof", params); + Map result = (Map) resp.get("result"); + return (String) result.get("signature"); + } + + @SuppressWarnings("unchecked") + @Override + public String getReserveProofAccount(int accountIdx, BigInteger amount, String message) { + Map params = new HashMap(); + params.put("account_index", accountIdx); + params.put("amount", amount.toString()); + params.put("message", message); + Map resp = rpc.sendJsonRequest("get_reserve_proof", params); + Map result = (Map) resp.get("result"); + return (String) result.get("signature"); + } + + @SuppressWarnings("unchecked") + @Override + public MoneroCheckReserve checkReserveProof(String address, String message, String signature) { + + // send request + Map params = new HashMap(); + params.put("address", address); + params.put("message", message); + params.put("signature", signature); + Map resp = rpc.sendJsonRequest("check_reserve_proof", params); + Map result = (Map) resp.get("result"); + + // interpret results + boolean isGood = (boolean) result.get("good"); + MoneroCheckReserve check = new MoneroCheckReserve(); + check.setIsGood(isGood); + if (isGood) { + check.setTotalAmount((BigInteger) result.get("total")); + check.setUnconfirmedSpentAmount((BigInteger) result.get("spent")); + } + return check; + } + + @SuppressWarnings("unchecked") + @Override + public List getTxNotes(Collection txIds) { + Map params = new HashMap(); + params.put("txids", txIds); + Map resp = rpc.sendJsonRequest("get_tx_notes", params); + Map result = (Map) resp.get("result"); + return (List) result.get("notes"); + } + + @Override + public void setTxNotes(Collection txIds, Collection notes) { + Map params = new HashMap(); + params.put("txids", txIds); + params.put("notes", notes); + rpc.sendJsonRequest("set_tx_notes", params); + } + + @SuppressWarnings("unchecked") + @Override + public List getAddressBookEntries(Collection entryIndices) { + Map params = new HashMap(); + params.put("entries", entryIndices); + Map respMap = rpc.sendJsonRequest("get_address_book", params); + Map resultMap = (Map) respMap.get("result"); + List entries = new ArrayList(); + if (!resultMap.containsKey("entries")) return entries; + for (Map entryMap : (List>) resultMap.get("entries")) { + MoneroAddressBookEntry entry = new MoneroAddressBookEntry( + ((BigInteger) entryMap.get("index")).intValue(), + (String) entryMap.get("address"), + (String) entryMap.get("payment_id"), + (String) entryMap.get("description") + ); + entries.add(entry); + } + return entries; + } + + @SuppressWarnings("unchecked") + @Override + public int addAddressBookEntry(String address, String description, String paymentId) { + Map params = new HashMap(); + params.put("address", address); + params.put("payment_id", paymentId); + params.put("description", description); + Map respMap = rpc.sendJsonRequest("add_address_book", params); + Map resultMap = (Map) respMap.get("result"); + return ((BigInteger) resultMap.get("index")).intValue(); + } + + @Override + public void deleteAddressBookEntry(int entryIdx) { + Map params = new HashMap(); + params.put("index", entryIdx); + rpc.sendJsonRequest("delete_address_book", params); + } + + @Override + public void tagAccounts(String tag, Collection accountIndices) { + Map params = new HashMap(); + params.put("tag", tag); + params.put("accounts", accountIndices); + rpc.sendJsonRequest("tag_accounts", params); + } + + @Override + public void untagAccounts(Collection accountIndices) { + Map params = new HashMap(); + params.put("accounts", accountIndices); + rpc.sendJsonRequest("untag_accounts", params); + } + + @SuppressWarnings("unchecked") + @Override + public List getAccountTags() { + List tags = new ArrayList(); + Map respMap = rpc.sendJsonRequest("get_account_tags"); + Map resultMap = (Map) respMap.get("result"); + List> accountTagMaps = (List>) resultMap.get("account_tags"); + if (accountTagMaps != null) { + for (Map accountTagMap : accountTagMaps) { + MoneroAccountTag tag = new MoneroAccountTag(); + tags.add(tag); + tag.setTag((String) accountTagMap.get("tag")); + tag.setLabel((String) accountTagMap.get("label")); + List accountIndicesBI = (List) accountTagMap.get("accounts"); + List accountIndices = new ArrayList(); + for (BigInteger idx : accountIndicesBI) accountIndices.add(idx.intValue()); + tag.setAccountIndices(accountIndices); + } + } + return tags; + } + + @Override + public void setAccountTagLabel(String tag, String label) { + Map params = new HashMap(); + params.put("tag", tag); + params.put("description", label); + rpc.sendJsonRequest("set_account_tag_description", params); + } + + @SuppressWarnings("unchecked") + @Override + public String createPaymentUri(MoneroSendRequest request) { + assertNotNull("Must provide send request to create a payment URI", request); + Map params = new HashMap(); + params.put("address", request.getDestinations().get(0).getAddress()); + params.put("amount", request.getDestinations().get(0).getAmount() != null ? request.getDestinations().get(0).getAmount().toString() : null); + params.put("payment_id", request.getPaymentId()); + params.put("recipient_name", request.getRecipientName()); + params.put("tx_description", request.getNote()); + Map resp = rpc.sendJsonRequest("make_uri", params); + Map result = (Map) resp.get("result"); + return (String) result.get("uri"); + } + + @SuppressWarnings("unchecked") + @Override + public MoneroSendRequest parsePaymentUri(String uri) { + assertNotNull("Must provide URI to parse", uri); + Map params = new HashMap(); + params.put("uri", uri); + Map resp = rpc.sendJsonRequest("parse_uri", params); + Map result = (Map) resp.get("result"); + Map rpcUri = (Map) result.get("uri"); + MoneroSendRequest request = new MoneroSendRequest((String) rpcUri.get("address"), (BigInteger) rpcUri.get("amount")); + request.setPaymentId((String) rpcUri.get("payment_id")); + request.setRecipientName((String) rpcUri.get("recipient_name")); + request.setNote((String) rpcUri.get("tx_description")); + if ("".equals(request.getDestinations().get(0).getAddress())) request.getDestinations().get(0).setAddress(null); + if ("".equals(request.getPaymentId())) request.setPaymentId(null); + if ("".equals(request.getRecipientName())) request.setRecipientName(null); + if ("".equals(request.getNote())) request.setNote(null); + return request; + } + + @SuppressWarnings("unchecked") + @Override + public String getAttribute(String key) { + Map params = new HashMap(); + params.put("key", key); + Map resp = rpc.sendJsonRequest("get_attribute", params); + Map result = (Map) resp.get("result"); + String value = (String) result.get("value"); + return value.isEmpty() ? null : value; + } + + @Override + public void setAttribute(String key, String val) { + Map params = new HashMap(); + params.put("key", key); + params.put("value", val); + rpc.sendJsonRequest("set_attribute", params); + } + + @Override + public void startMining(Long numThreads, Boolean backgroundMining, Boolean ignoreBattery) { + Map params = new HashMap(); + params.put("threads_count", numThreads); + params.put("backgroundMining", backgroundMining); + params.put("ignoreBattery", ignoreBattery); + rpc.sendJsonRequest("start_mining", params); + } + + @Override + public void stopMining() { + rpc.sendJsonRequest("stop_mining"); + } + + @SuppressWarnings("unchecked") + @Override + public boolean isMultisigImportNeeded() { + Map resp = rpc.sendJsonRequest("get_balance"); + Map result = (Map) resp.get("result"); + return Boolean.TRUE.equals((Boolean) result.get("multisig_import_needed")); + } + + @Override + @SuppressWarnings("unchecked") + public MoneroMultisigInfo getMultisigInfo() { + Map resp = rpc.sendJsonRequest("is_multisig"); + Map result = (Map) resp.get("result"); + MoneroMultisigInfo info = new MoneroMultisigInfo(); + info.setIsMultisig((boolean) result.get("multisig")); + info.setIsReady((boolean) result.get("ready")); + info.setThreshold(((BigInteger) result.get("threshold")).intValue()); + info.setNumParticipants(((BigInteger) result.get("total")).intValue()); + return info; + } + + @Override + @SuppressWarnings("unchecked") + public String prepareMultisig() { + Map resp = rpc.sendJsonRequest("prepare_multisig"); + Map result = (Map) resp.get("result"); + return (String) result.get("multisig_info"); + } + + @Override + @SuppressWarnings("unchecked") + public MoneroMultisigInitResult makeMultisig(List multisigHexes, int threshold, String password) { + Map params = new HashMap(); + params.put("multisig_info", multisigHexes); + params.put("threshold", threshold); + params.put("password", password); + Map resp = rpc.sendJsonRequest("make_multisig", params); + Map result = (Map) resp.get("result"); + MoneroMultisigInitResult msResult = new MoneroMultisigInitResult(); + msResult.setAddress((String) result.get("address")); + msResult.setMultisigHex((String) result.get("multisig_info")); + if (msResult.getAddress().isEmpty()) msResult.setAddress(null); + if (msResult.getMultisigHex().isEmpty()) msResult.setMultisigHex(null); + return msResult; + } + + @Override + @SuppressWarnings("unchecked") + public MoneroMultisigInitResult exchangeMultisigKeys(List multisigHexes, String password) { + Map params = new HashMap(); + params.put("multisig_info", multisigHexes); + params.put("password", password); + Map resp = rpc.sendJsonRequest("exchange_multisig_keys", params); + Map result = (Map) resp.get("result"); + MoneroMultisigInitResult msResult = new MoneroMultisigInitResult(); + msResult.setAddress((String) result.get("address")); + msResult.setMultisigHex((String) result.get("multisig_info")); + if (msResult.getAddress().isEmpty()) msResult.setAddress(null); + if (msResult.getMultisigHex().isEmpty()) msResult.setMultisigHex(null); + return msResult; + } + + @Override + @SuppressWarnings("unchecked") + public String getMultisigHex() { + Map resp = rpc.sendJsonRequest("export_multisig_info"); + Map result = (Map) resp.get("result"); + return (String) result.get("info"); + } + + @Override + @SuppressWarnings("unchecked") + public int importMultisigHex(List multisigHexes) { + Map params = new HashMap(); + params.put("info", multisigHexes); + Map resp = rpc.sendJsonRequest("import_multisig_info", params); + Map result = (Map) resp.get("result"); + return ((BigInteger) result.get("n_outputs")).intValue(); + } + + @Override + @SuppressWarnings("unchecked") + public MoneroMultisigSignResult signMultisigTxHex(String multisigTxHex) { + Map params = new HashMap(); + params.put("tx_data_hex", multisigTxHex); + Map resp = rpc.sendJsonRequest("sign_multisig", params); + Map result = (Map) resp.get("result"); + MoneroMultisigSignResult signResult = new MoneroMultisigSignResult(); + signResult.setSignedMultisigTxHex((String) result.get("tx_data_hex")); + signResult.setTxIds((List) result.get("tx_hash_list")); + return signResult; + } + + @Override + @SuppressWarnings("unchecked") + public List submitMultisigTxHex(String signedMultisigTxHex) { + Map params = new HashMap(); + params.put("tx_data_hex", signedMultisigTxHex); + Map resp = rpc.sendJsonRequest("submit_multisig", params); + Map result = (Map) resp.get("result"); + return (List) result.get("tx_hash_list"); + } + + @Override + public void save() { + rpc.sendJsonRequest("store"); + } + + @Override + public void close(boolean save) { + addressCache.clear(); + path = null; + Map params = new HashMap(); + params.put("autosave_current", save); + rpc.sendJsonRequest("close_wallet", params); + } + + // ------------------------------ PRIVATE ----------------------------------- + + private Map> getAccountIndices(boolean getSubaddressIndices) { + Map> indices = new HashMap>(); + for (MoneroAccount account : getAccounts()) { + indices.put(account.getIndex(), getSubaddressIndices ? getSubaddressIndices(account.getIndex()) : null); + } + return indices; + } + + @SuppressWarnings("unchecked") + private List getSubaddressIndices(int accountIdx) { + List subaddressIndices = new ArrayList(); + Map params = new HashMap(); + params.put("account_index", accountIdx); + Map resp = rpc.sendJsonRequest("get_address", params); + Map result = (Map) resp.get("result"); + for (Map address : (List>) result.get("addresses")) { + subaddressIndices.add(((BigInteger) address.get("address_index")).intValue()); + } + return subaddressIndices; + } + + /** + * Common method to get key images. + * + * @param all specifies to get all xor only new images from last import + * @return {MoneroKeyImage[]} are the key images + */ + @SuppressWarnings("unchecked") + private List rpcExportKeyImages(boolean all) { + Map params = new HashMap(); + params.put("all", all); + Map resp = rpc.sendJsonRequest("export_key_images", params); + Map result = (Map) resp.get("result"); + List images = new ArrayList(); + if (!result.containsKey("signed_key_images")) return images; + for (Map rpcImage : (List>) result.get("signed_key_images")) { + images.add(new MoneroKeyImage((String) rpcImage.get("key_image"), (String) rpcImage.get("signature"))); + } + return images; + } + + @SuppressWarnings("unchecked") + private BigInteger[] getBalances(Integer accountIdx, Integer subaddressIdx) { + if (accountIdx == null) { + assertNull("Must provide account index with subaddress index", subaddressIdx); + BigInteger balance = BigInteger.valueOf(0); + BigInteger unlockedBalance = BigInteger.valueOf(0); + for (MoneroAccount account : getAccounts()) { + balance = balance.add(account.getBalance()); + unlockedBalance = unlockedBalance.add(account.getUnlockedBalance()); + } + return new BigInteger[] { balance, unlockedBalance }; + } else { + Map params = new HashMap(); + params.put("account_index", accountIdx); + params.put("address_indices", subaddressIdx == null ? null : new Integer[] { subaddressIdx }); + Map resp = rpc.sendJsonRequest("get_balance", params); + Map result = (Map) resp.get("result"); + if (subaddressIdx == null) return new BigInteger[] { (BigInteger) result.get("balance"), (BigInteger) result.get("unlocked_balance") }; + else { + List> rpcBalancesPerSubaddress = (List>) result.get("per_subaddress"); + return new BigInteger[] { (BigInteger) rpcBalancesPerSubaddress.get(0).get("balance"), (BigInteger) rpcBalancesPerSubaddress.get(0).get("unlocked_balance") }; + } + } + } + + @SuppressWarnings("unchecked") + private MoneroTxSet rpcSweepAccount(MoneroSendRequest request) { + + // validate request + if (request == null) throw new MoneroException("Sweep request cannot be null"); + if (request.getAccountIndex() == null) throw new MoneroException("Must specify an account index to sweep from"); + if (request.getDestinations() == null || request.getDestinations().size() != 1) throw new MoneroException("Must specify exactly one destination to sweep to"); + if (request.getDestinations().get(0).getAddress() == null) throw new MoneroException("Must specify destination address to sweep to"); + if (request.getDestinations().get(0).getAmount() != null) throw new MoneroException("Cannot specify amount in sweep request"); + if (request.getKeyImage() != null) throw new MoneroException("Key image defined; use sweepOutput() to sweep an output by its key image"); + if (request.getSubaddressIndices() != null && request.getSubaddressIndices().isEmpty()) request.setSubaddressIndices((List) null); + if (Boolean.TRUE.equals(request.getSweepEachSubaddress())) throw new MoneroException("Cannot sweep each subaddress with RPC `sweep_all`"); + + // sweep from all subaddresses if not otherwise defined + if (request.getSubaddressIndices() == null) { + request.setSubaddressIndices(new ArrayList()); + for (MoneroSubaddress subaddress : getSubaddresses(request.getAccountIndex())) { + request.getSubaddressIndices().add(subaddress.getIndex()); + } + } + if (request.getSubaddressIndices().size() == 0) throw new MoneroException("No subaddresses to sweep from"); + + // common request params + boolean doNotRelay = request.getDoNotRelay() != null && request.getDoNotRelay(); + Map params = new HashMap(); + params.put("account_index", request.getAccountIndex()); + params.put("subaddr_indices", request.getSubaddressIndices()); + params.put("address", request.getDestinations().get(0).getAddress()); + params.put("priority", request.getPriority() == null ? null : request.getPriority().ordinal()); + params.put("mixin", request.getMixin()); + params.put("ring_size", request.getRingSize()); + params.put("unlock_time", request.getUnlockTime()); + params.put("payment_id", request.getPaymentId()); + params.put("do_not_relay", doNotRelay); + params.put("below_amount", request.getBelowAmount()); + params.put("get_tx_keys", true); + params.put("get_tx_hex", true); + params.put("get_tx_metadata", true); + + // invoke wallet rpc `sweep_all` + Map resp = rpc.sendJsonRequest("sweep_all", params); + Map result = (Map) resp.get("result"); + + // initialize txs from response + MoneroTxSet txSet = convertRpcSentTxsToTxSet(result, null); + + // initialize remaining known fields + for (MoneroTxWallet tx : txSet.getTxs()) { + tx.setIsConfirmed(false); + tx.setNumConfirmations(0l); + tx.setDoNotRelay(doNotRelay); + tx.setInTxPool(!doNotRelay); + tx.setIsRelayed(!doNotRelay); + tx.setIsMinerTx(false); + tx.setIsFailed(false); + tx.setMixin(request.getMixin()); + MoneroOutgoingTransfer transfer = tx.getOutgoingTransfer(); + transfer.setAccountIndex(request.getAccountIndex()); + if (request.getSubaddressIndices().size() == 1) transfer.setSubaddressIndices(new ArrayList(request.getSubaddressIndices())); + MoneroDestination destination = new MoneroDestination(request.getDestinations().get(0).getAddress(), transfer.getAmount()); + transfer.setDestinations(Arrays.asList(destination)); + tx.setOutgoingTransfer(transfer); + tx.setPaymentId(request.getPaymentId()); + if (tx.getUnlockTime() == null) tx.setUnlockTime(request.getUnlockTime() == null ? 0l : request.getUnlockTime()); + if (!tx.getDoNotRelay()) { + if (tx.getLastRelayedTimestamp() == null) tx.setLastRelayedTimestamp(System.currentTimeMillis()); // TODO (monero-wallet-rpc): provide timestamp on response; unconfirmed timestamps vary + if (tx.isDoubleSpendSeen() == null) tx.setIsDoubleSpendSeen(false); + } + } + return txSet; + } + + // ---------------------------- PRIVATE STATIC ------------------------------ + + private static MoneroAccount convertRpcAccount(Map rpcAccount) { + MoneroAccount account = new MoneroAccount(); + for (String key : rpcAccount.keySet()) { + Object val = rpcAccount.get(key); + if (key.equals("account_index")) account.setIndex(((BigInteger) val).intValue()); + else if (key.equals("balance")) account.setBalance((BigInteger) val); + else if (key.equals("unlocked_balance")) account.setUnlockedBalance((BigInteger) val); + else if (key.equals("base_address")) account.setPrimaryAddress((String) val); + else if (key.equals("tag")) account.setTag((String) val); + else if (key.equals("label")) { } // label belongs to first subaddress + else LOGGER.warn("WARNING: ignoring unexpected account field: " + key + ": " + val); + } + if ("".equals(account.getTag())) account.setTag(null); + return account; + } + + private static MoneroSubaddress convertRpcSubaddress(Map rpcSubaddress) { + MoneroSubaddress subaddress = new MoneroSubaddress(); + for (String key : rpcSubaddress.keySet()) { + Object val = rpcSubaddress.get(key); + if (key.equals("account_index")) subaddress.setAccountIndex(((BigInteger) val).intValue()); + else if (key.equals("address_index")) subaddress.setIndex(((BigInteger) val).intValue()); + else if (key.equals("address")) subaddress.setAddress((String) val); + else if (key.equals("balance")) subaddress.setBalance((BigInteger) val); + else if (key.equals("unlocked_balance")) subaddress.setUnlockedBalance((BigInteger) val); + else if (key.equals("num_unspent_outputs")) subaddress.setNumUnspentOutputs(((BigInteger) val).longValue()); + else if (key.equals("label")) { if (!"".equals(val)) subaddress.setLabel((String) val); } + else if (key.equals("used")) subaddress.setIsUsed((Boolean) val); + else if (key.equals("blocks_to_unlock")) subaddress.setNumBlocksToUnlock(((BigInteger) val).longValue()); + else LOGGER.warn("WARNING: ignoring unexpected subaddress field: " + key + ": " + val); + } + return subaddress; + } + + /** + * Initializes a sent transaction. + * + * @param request is the send configuration + * @param tx is an existing transaction to initialize (optional) + * @return tx is the initialized send tx + */ + private static MoneroTxWallet initSentTxWallet(MoneroSendRequest request, MoneroTxWallet tx) { + if (tx == null) tx = new MoneroTxWallet(); + tx.setIsConfirmed(false); + tx.setNumConfirmations(0l); + tx.setInTxPool(Boolean.TRUE.equals(request.getDoNotRelay()) ? false : true); + tx.setDoNotRelay(Boolean.TRUE.equals(request.getDoNotRelay()) ? true : false); + tx.setIsRelayed(!Boolean.TRUE.equals(tx.getDoNotRelay())); + tx.setIsMinerTx(false); + tx.setIsFailed(false); + tx.setMixin(request.getMixin()); + MoneroOutgoingTransfer transfer = new MoneroOutgoingTransfer().setTx(tx); + if (request.getSubaddressIndices() != null && request.getSubaddressIndices().size() == 1) transfer.setSubaddressIndices(new ArrayList(request.getSubaddressIndices())); // we know src subaddress indices iff request specifies 1 + List destCopies = new ArrayList(); + for (MoneroDestination dest : request.getDestinations()) destCopies.add(dest.copy()); + transfer.setDestinations(destCopies); + tx.setOutgoingTransfer(transfer); + tx.setPaymentId(request.getPaymentId()); + if (tx.getUnlockTime() == null) tx.setUnlockTime(request.getUnlockTime() == null ? 0l : request.getUnlockTime()); + if (!Boolean.TRUE.equals(tx.getDoNotRelay())) { + if (tx.getLastRelayedTimestamp() == null) tx.setLastRelayedTimestamp(System.currentTimeMillis()); // TODO (monero-wallet-rpc): provide timestamp on response; unconfirmed timestamps vary + if (tx.isDoubleSpendSeen() == null) tx.setIsDoubleSpendSeen(false); + } + return tx; + } + + /** + * Initializes a tx set from a RPC map excluding txs. + * + * @param rpcMap is the map to initialize the tx set from + * @return MoneroTxSet is the initialized tx set + */ + private static MoneroTxSet convertRpcMapToTxSet(Map rpcMap) { + MoneroTxSet txSet = new MoneroTxSet(); + txSet.setMultisigTxHex((String) rpcMap.get("multisig_txset")); + txSet.setUnsignedTxHex((String) rpcMap.get("unsigned_txset")); + txSet.setSignedTxHex((String) rpcMap.get("signed_txset")); + if (txSet.getMultisigTxHex() != null && txSet.getMultisigTxHex().isEmpty()) txSet.setMultisigTxHex(null); + if (txSet.getUnsignedTxHex() != null && txSet.getUnsignedTxHex().isEmpty()) txSet.setUnsignedTxHex(null); + if (txSet.getSignedTxHex() != null && txSet.getSignedTxHex().isEmpty()) txSet.setSignedTxHex(null); + return txSet; + } + + /** + * Initializes a MoneroTxSet from from a list of rpc txs. + * + * @param rpcTxs are sent rpc txs to initialize the set from + * @param txs are existing txs to further initialize (optional) + * @return the converted tx set + */ + @SuppressWarnings("unchecked") + private static MoneroTxSet convertRpcSentTxsToTxSet(Map rpcTxs, List txs) { + + // build shared tx set + MoneroTxSet txSet = convertRpcMapToTxSet(rpcTxs); + + // done if rpc contains no txs + if (!rpcTxs.containsKey("fee_list")) { + assertNull(txs); + return txSet; + } + + // get lists + List ids = (List) rpcTxs.get("tx_hash_list"); + List keys = (List) rpcTxs.get("tx_key_list"); + List blobs = (List) rpcTxs.get("tx_blob_list"); + List metadatas = (List) rpcTxs.get("tx_metadata_list"); + List fees = (List) rpcTxs.get("fee_list"); + List amounts = (List) rpcTxs.get("amount_list"); + + // ensure all lists are the same size + Set sizes = new HashSet(); + if (amounts != null) sizes.add(amounts.size()); + if (ids != null) sizes.add(ids.size()); + if (keys != null) sizes.add(keys.size()); + if (blobs != null) sizes.add(blobs.size()); + if (metadatas != null) sizes.add(metadatas.size()); + if (fees != null) sizes.add(fees.size()); + if (amounts != null) sizes.add(amounts.size()); + assertEquals("RPC lists are different sizes", 1, sizes.size()); + + // pre-initialize txs if none given + if (txs != null) txSet.setTxs(txs); + else { + txs = new ArrayList(); + for (int i = 0; i < fees.size(); i++) txs.add(new MoneroTxWallet()); + txSet.setTxs(txs); + } + + // build transactions + for (int i = 0; i < fees.size(); i++) { + MoneroTxWallet tx = txs.get(i); + if (ids != null) tx.setId(ids.get(i)); + if (keys != null) tx.setKey(keys.get(i)); + if (blobs != null) tx.setFullHex(blobs.get(i)); + if (metadatas != null) tx.setMetadata(metadatas.get(i)); + tx.setFee((BigInteger) fees.get(i)); + if (tx.getOutgoingTransfer() != null) tx.getOutgoingTransfer().setAmount((BigInteger) amounts.get(i)); + else tx.setOutgoingTransfer(new MoneroOutgoingTransfer().setTx(tx).setAmount((BigInteger) amounts.get(i))); + tx.setTxSet(txSet); // link tx to parent set + } + + return txSet; + } + + /** + * Converts a rpc tx with a transfer to a tx set with a tx and transfer. + * + * @param rpcTx is the rpc tx to build from + * @param tx is an existing tx to continue initializing (optional) + * @param isOutgoing specifies if the tx is outgoing if true, incoming if false, or decodes from type if undefined + * @returns the initialized tx set with a tx + */ + private static MoneroTxSet convertRpcTxToTxSet(Map rpcTx, MoneroTxWallet tx, Boolean isOutgoing) { + MoneroTxSet txSet = convertRpcMapToTxSet(rpcTx); + txSet.setTxs(Arrays.asList(convertRpcTxWithTransfer(rpcTx, tx, isOutgoing).setTxSet(txSet))); + return txSet; + } + + /** + * Builds a MoneroTxWallet from a RPC tx. + * + * @param rpcTx is the rpc tx to build from + * @param tx is an existing tx to continue initializing (optional) + * @param isOutgoing specifies if the tx is outgoing if true, incoming if false, or decodes from type if undefined + * @returns the initialized tx with a transfer + */ + @SuppressWarnings("unchecked") + private static MoneroTxWallet convertRpcTxWithTransfer(Map rpcTx, MoneroTxWallet tx, Boolean isOutgoing) { // TODO: change everything to safe set + + // initialize tx to return + if (tx == null) tx = new MoneroTxWallet(); + + // initialize tx state from rpc type + if (rpcTx.containsKey("type")) isOutgoing = decodeRpcType((String) rpcTx.get("type"), tx); + else { + assertNotNull("Must indicate if tx is outgoing (true) xor incoming (false) since unknown", isOutgoing); + assertNotNull(tx.isConfirmed()); + assertNotNull(tx.inTxPool()); + assertNotNull(tx.isMinerTx()); + assertNotNull(tx.isFailed()); + assertNotNull(tx.getDoNotRelay()); + } + + // TODO: safe set + // initialize remaining fields TODO: seems this should be part of common function with DaemonRpc._convertRpcTx + MoneroBlockHeader header = null; + MoneroTransfer transfer = null; + for (String key : rpcTx.keySet()) { + Object val = rpcTx.get(key); + if (key.equals("txid")) tx.setId((String) val); + else if (key.equals("tx_hash")) tx.setId((String) val); + else if (key.equals("fee")) tx.setFee((BigInteger) val); + else if (key.equals("note")) { if (!"".equals(val)) tx.setNote((String) val); } + else if (key.equals("tx_key")) tx.setKey((String) val); + else if (key.equals("type")) { } // type already handled + else if (key.equals("tx_size")) tx.setSize(((BigInteger) val).longValue()); + else if (key.equals("unlock_time")) tx.setUnlockTime(((BigInteger) val).longValue()); + else if (key.equals("tx_blob")) tx.setFullHex((String) val); + else if (key.equals("tx_metadata")) tx.setMetadata((String) val); + else if (key.equals("double_spend_seen")) tx.setIsDoubleSpendSeen((Boolean) val); + else if (key.equals("block_height") || key.equals("height")) { + if (tx.isConfirmed()) { + if (header == null) header = new MoneroBlockHeader(); + header.setHeight(((BigInteger) val).longValue()); + } + } + else if (key.equals("timestamp")) { + if (tx.isConfirmed()) { + if (header == null) header = new MoneroBlockHeader(); + header.setTimestamp(((BigInteger) val).longValue()); + } else { + // timestamp of unconfirmed tx is current request time + } + } + else if (key.equals("confirmations")) { + if (!tx.isConfirmed()) tx.setNumConfirmations(0l); + else tx.setNumConfirmations(((BigInteger) val).longValue()); + } + else if (key.equals("suggested_confirmations_threshold")) { + if (transfer == null) transfer = (isOutgoing ? new MoneroOutgoingTransfer() : new MoneroIncomingTransfer()).setTx(tx); + transfer.setNumSuggestedConfirmations(((BigInteger) val).longValue()); + } + else if (key.equals("amount")) { + if (transfer == null) transfer = (isOutgoing ? new MoneroOutgoingTransfer() : new MoneroIncomingTransfer()).setTx(tx); + transfer.setAmount((BigInteger) val); + } + else if (key.equals("address")) { + if (!isOutgoing) { + if (transfer == null) transfer = new MoneroIncomingTransfer().setTx(tx); + ((MoneroIncomingTransfer) transfer).setAddress((String) val); + } + } + else if (key.equals("payment_id")) { + if (!MoneroTxWallet.DEFAULT_PAYMENT_ID.equals(val)) tx.setPaymentId((String) val); // default is undefined + } + else if (key.equals("subaddr_index")) assertTrue(rpcTx.containsKey("subaddr_indices")); // handled by subaddr_indices + else if (key.equals("subaddr_indices")) { + if (transfer == null) transfer = (isOutgoing ? new MoneroOutgoingTransfer() : new MoneroIncomingTransfer()).setTx(tx); + List> rpcIndices = (List>) val; + transfer.setAccountIndex(rpcIndices.get(0).get("major").intValue()); + if (isOutgoing) { + List subaddressIndices = new ArrayList(); + for (Map rpcIndex : rpcIndices) subaddressIndices.add(rpcIndex.get("minor").intValue()); + ((MoneroOutgoingTransfer) transfer).setSubaddressIndices(subaddressIndices); + } else { + assertEquals(1, rpcIndices.size()); + ((MoneroIncomingTransfer) transfer).setSubaddressIndex(rpcIndices.get(0).get("minor").intValue()); + } + } + else if (key.equals("destinations")) { + assertTrue(isOutgoing); + List destinations = new ArrayList(); + for (Map rpcDestination : (List>) val) { + MoneroDestination destination = new MoneroDestination(); + destinations.add(destination); + for (String destinationKey : rpcDestination.keySet()) { + if (destinationKey.equals("address")) destination.setAddress((String) rpcDestination.get(destinationKey)); + else if (destinationKey.equals("amount")) destination.setAmount((BigInteger) rpcDestination.get(destinationKey)); + else throw new MoneroException("Unrecognized transaction destination field: " + destinationKey); + } + } + if (transfer == null) transfer = new MoneroOutgoingTransfer().setTx(tx); + ((MoneroOutgoingTransfer) transfer).setDestinations(destinations); + } + else if (key.equals("multisig_txset") && val != null) {} // handled elsewhere; this method only builds a tx wallet + else if (key.equals("unsigned_txset") && val != null) {} // handled elsewhere; this method only builds a tx wallet + else LOGGER.warn("WARNING: ignoring unexpected transaction field: " + key + ": " + val); + } + + // link block and tx + if (header != null) tx.setBlock(new MoneroBlock(header).setTxs(tx)); + + // initialize final fields + if (transfer != null) { + if (isOutgoing) { + if (tx.getOutgoingTransfer() != null) tx.getOutgoingTransfer().merge(transfer); + else tx.setOutgoingTransfer((MoneroOutgoingTransfer) transfer); + } else { + tx.setIncomingTransfers(new ArrayList(Arrays.asList((MoneroIncomingTransfer) transfer))); + } + } + + // return initialized transaction + return tx; + } + + @SuppressWarnings("unchecked") + private static MoneroTxWallet convertRpcTxWithVout(Map rpcVout) { + + // initialize tx + MoneroTxWallet tx = new MoneroTxWallet(); + tx.setIsConfirmed(true); + tx.setIsRelayed(true); + tx.setIsFailed(false); + + // initialize vout + MoneroOutputWallet vout = new MoneroOutputWallet().setTx(tx); + for (String key : rpcVout.keySet()) { + Object val = rpcVout.get(key); + if (key.equals("amount")) vout.setAmount((BigInteger) val); + else if (key.equals("spent")) vout.setIsSpent((Boolean) val); + else if (key.equals("key_image")) vout.setKeyImage(new MoneroKeyImage((String) val)); + else if (key.equals("global_index")) vout.setIndex(((BigInteger) val).intValue()); + else if (key.equals("tx_hash")) tx.setId((String) val); + else if (key.equals("unlocked")) vout.setIsUnlocked((Boolean) val); + else if (key.equals("frozen")) vout.setIsFrozen((Boolean) val); + else if (key.equals("subaddr_index")) { + Map rpcIndices = (Map) val; + vout.setAccountIndex(rpcIndices.get("major").intValue()); + vout.setSubaddressIndex(rpcIndices.get("minor").intValue()); + } + else if (key.equals("block_height")) { + long height = ((BigInteger) val).longValue(); + tx.setBlock(new MoneroBlock().setHeight(height).setTxs(tx)); + } + else LOGGER.warn("WARNING: ignoring unexpected transaction field with vout: " + key + ": " + val); + } + + // initialize tx with vout + List vouts = new ArrayList(); + vouts.add((MoneroOutput) vout); // have to cast to extended type because Java paramaterized types do not recognize inheritance + tx.setVouts(vouts); + return tx; + } + + /** + * Decodes a "type" from monero-wallet-rpc to initialize type and state + * fields in the given transaction. + * + * TODO: these should be safe set + * + * @param rpcType is the type to decode + * @param tx is the transaction to decode known fields to + * @return {boolean} true if the rpc type indicates outgoing xor incoming + */ + private static boolean decodeRpcType(String rpcType, MoneroTxWallet tx) { + boolean isOutgoing; + if (rpcType.equals("in")) { + isOutgoing = false; + tx.setIsConfirmed(true); + tx.setInTxPool(false); + tx.setIsRelayed(true); + tx.setDoNotRelay(false); + tx.setIsFailed(false); + tx.setIsMinerTx(false); + } else if (rpcType.equals("out")) { + isOutgoing = true; + tx.setIsConfirmed(true); + tx.setInTxPool(false); + tx.setIsRelayed(true); + tx.setDoNotRelay(false); + tx.setIsFailed(false); + tx.setIsMinerTx(false); + } else if (rpcType.equals("pool")) { + isOutgoing = false; + tx.setIsConfirmed(false); + tx.setInTxPool(true); + tx.setIsRelayed(true); + tx.setDoNotRelay(false); + tx.setIsFailed(false); + tx.setIsMinerTx(false); // TODO: but could it be? + } else if (rpcType.equals("pending")) { + isOutgoing = true; + tx.setIsConfirmed(false); + tx.setInTxPool(true); + tx.setIsRelayed(true); + tx.setDoNotRelay(false); + tx.setIsFailed(false); + tx.setIsMinerTx(false); + } else if (rpcType.equals("block")) { + isOutgoing = false; + tx.setIsConfirmed(true); + tx.setInTxPool(false); + tx.setIsRelayed(true); + tx.setDoNotRelay(false); + tx.setIsFailed(false); + tx.setIsMinerTx(true); + } else if (rpcType.equals("failed")) { + isOutgoing = true; + tx.setIsConfirmed(false); + tx.setInTxPool(false); + tx.setIsRelayed(true); + tx.setDoNotRelay(false); + tx.setIsFailed(true); + tx.setIsMinerTx(false); + } else { + throw new MoneroException("Unrecognized transfer type: " + rpcType); + } + return isOutgoing; + } + + /** + * Merges a transaction into a unique set of transactions. + * + * TODO monero-core: skipIfAbsent only necessary because incoming payments not returned + * when sent from/to same account #4500 + * + * @param tx is the transaction to merge into the existing txs + * @param txMap maps tx ids to txs + * @param blockMap maps block heights to blocks + * @param skipIfAbsent specifies if the tx should not be added if it doesn't already exist + */ + private static void mergeTx(MoneroTxWallet tx, Map txMap, Map blockMap, boolean skipIfAbsent) { + assertNotNull(tx.getId()); + + // if tx doesn't exist, add it (unless skipped) + MoneroTxWallet aTx = txMap.get(tx.getId()); + if (aTx == null) { + if (!skipIfAbsent) { + txMap.put(tx.getId(), tx); + } else { + LOGGER.warn("WARNING: tx does not already exist"); + } + } + + // otherwise merge with existing tx + else { + if (aTx.isFailed() != null & tx.isFailed() != null && !aTx.isFailed().equals(tx.isFailed())) { + System.out.println("ERROR: Merging these transactions will throw an error because their isFailed state is different"); + System.out.println(aTx); + System.out.println(tx); + } + aTx.merge(tx); + } + + // if confirmed, merge tx's block + if (tx.getHeight() != null) { + MoneroBlock aBlock = blockMap.get(tx.getHeight()); + if (aBlock == null) { + blockMap.put(tx.getHeight(), tx.getBlock()); + } else { + aBlock.merge(tx.getBlock()); + } + } + } + + /** + * Compares two transactions by their height. + */ + private static class TxHeightComparator implements Comparator { + @Override + public int compare(MoneroTx tx1, MoneroTx tx2) { + if (tx1.getHeight() == null && tx2.getHeight() == null) return 0; // both unconfirmed + else if (tx1.getHeight() == null) return 1; // tx1 is unconfirmed + else if (tx2.getHeight() == null) return -1; // tx2 is unconfirmed + int diff = tx1.getHeight().compareTo(tx2.getHeight()); + if (diff != 0) return diff; + return tx1.getBlock().getTxs().indexOf(tx1) - tx2.getBlock().getTxs().indexOf(tx2); // txs are in the same block so retain their original order + } + } + + /** + * Compares two transfers by ascending account and subaddress indices. + */ + public static class IncomingTransferComparator implements Comparator { + @Override + public int compare(MoneroIncomingTransfer t1, MoneroIncomingTransfer t2) { + + // compare by height + int heightComparison = TX_HEIGHT_COMPARATOR.compare(t1.getTx(), t2.getTx()); + if (heightComparison != 0) return heightComparison; + + // compare by account and subaddress index + if (t1.getAccountIndex() < t2.getAccountIndex()) return -1; + else if (t1.getAccountIndex() == t2.getAccountIndex()) return t1.getSubaddressIndex().compareTo(t2.getSubaddressIndex()); + return 1; + } + } + + /** + * Compares two vouts by ascending account and subaddress indices. + */ + public static class VoutComparator implements Comparator { + + @Override + public int compare(MoneroOutput o1, MoneroOutput o2) { + MoneroOutputWallet ow1 = (MoneroOutputWallet) o1; + MoneroOutputWallet ow2 = (MoneroOutputWallet) o2; + + // compare by height + int heightComparison = TX_HEIGHT_COMPARATOR.compare(ow1.getTx(), ow2.getTx()); + if (heightComparison != 0) return heightComparison; + + // compare by account index, subaddress index, and output + if (ow1.getAccountIndex() < ow2.getAccountIndex()) return -1; + else if (ow1.getAccountIndex() == ow2.getAccountIndex()) { + int compare = ow1.getSubaddressIndex().compareTo(ow2.getSubaddressIndex()); + if (compare != 0) return compare; + return ow1.getIndex().compareTo(ow2.getIndex()); + } + return 1; + } + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroAccount.java b/core/src/main/java/monero/wallet/model/MoneroAccount.java new file mode 100644 index 00000000000..9477677346b --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroAccount.java @@ -0,0 +1,147 @@ +package monero.wallet.model; + +import java.math.BigInteger; +import java.util.List; + +import monero.utils.MoneroUtils; + +/** + * Monero account model. + */ +public class MoneroAccount { + + private Integer index; + private String primaryAddress; + private BigInteger balance; + private BigInteger unlockedBalance; + private String tag; + private List subaddresses; + + public MoneroAccount() { + super(); + } + + public MoneroAccount(int index, String primaryAddress, BigInteger balance, BigInteger unlockedBalance, List subaddresses) { + super(); + this.index = index; + this.primaryAddress = primaryAddress; + this.balance = balance; + this.unlockedBalance = unlockedBalance; + this.subaddresses = subaddresses; + } + + public Integer getIndex() { + return index; + } + + public void setIndex(Integer index) { + this.index = index; + } + + public String getPrimaryAddress() { + return primaryAddress; + } + + public void setPrimaryAddress(String primaryAddress) { + this.primaryAddress = primaryAddress; + } + + public BigInteger getBalance() { + return balance; + } + + public void setBalance(BigInteger balance) { + this.balance = balance; + } + + public BigInteger getUnlockedBalance() { + return unlockedBalance; + } + + public void setUnlockedBalance(BigInteger unlockedBalance) { + this.unlockedBalance = unlockedBalance; + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public List getSubaddresses() { + return subaddresses; + } + + public void setSubaddresses(List subaddresses) { + this.subaddresses = subaddresses; + if (subaddresses != null) { + for (MoneroSubaddress subaddress : subaddresses) { + subaddress.setAccountIndex(index); + } + } + } + + public String toString() { + return toString(0); + } + + public String toString(int indent) { + StringBuilder sb = new StringBuilder(); + sb.append(MoneroUtils.kvLine("Index", this.getIndex(), indent)); + sb.append(MoneroUtils.kvLine("Primary address", this.getPrimaryAddress(), indent)); + sb.append(MoneroUtils.kvLine("Balance", this.getBalance(), indent)); + sb.append(MoneroUtils.kvLine("Unlocked balance", this.getUnlockedBalance(), indent)); + sb.append(MoneroUtils.kvLine("Tag", this.getTag(), indent)); + if (this.getSubaddresses() != null) { + sb.append(MoneroUtils.kvLine("Subaddresses", "", indent)); + for (int i = 0; i < this.getSubaddresses().size(); i++) { + sb.append(MoneroUtils.kvLine(i + 1, "", indent + 1)); + sb.append(this.getSubaddresses().get(i).toString(indent + 2) + "\n"); + } + } + String str = sb.toString(); + return str.substring(0, str.length() - 1); // strip last newline + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((balance == null) ? 0 : balance.hashCode()); + result = prime * result + ((index == null) ? 0 : index.hashCode()); + result = prime * result + ((primaryAddress == null) ? 0 : primaryAddress.hashCode()); + result = prime * result + ((subaddresses == null) ? 0 : subaddresses.hashCode()); + result = prime * result + ((tag == null) ? 0 : tag.hashCode()); + result = prime * result + ((unlockedBalance == null) ? 0 : unlockedBalance.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + MoneroAccount other = (MoneroAccount) obj; + if (balance == null) { + if (other.balance != null) return false; + } else if (!balance.equals(other.balance)) return false; + if (index == null) { + if (other.index != null) return false; + } else if (!index.equals(other.index)) return false; + if (primaryAddress == null) { + if (other.primaryAddress != null) return false; + } else if (!primaryAddress.equals(other.primaryAddress)) return false; + if (subaddresses == null) { + if (other.subaddresses != null) return false; + } else if (!subaddresses.equals(other.subaddresses)) return false; + if (tag == null) { + if (other.tag != null) return false; + } else if (!tag.equals(other.tag)) return false; + if (unlockedBalance == null) { + if (other.unlockedBalance != null) return false; + } else if (!unlockedBalance.equals(other.unlockedBalance)) return false; + return true; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroAccountTag.java b/core/src/main/java/monero/wallet/model/MoneroAccountTag.java new file mode 100644 index 00000000000..667a796f141 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroAccountTag.java @@ -0,0 +1,76 @@ +package monero.wallet.model; + +import java.util.List; + +/** + * Represents an account tag. + */ +public class MoneroAccountTag { + + private String tag; + private String label; + private List accountIndices; + + public MoneroAccountTag() { + super(); + } + + public MoneroAccountTag(String tag, String label, List accountIndices) { + super(); + this.tag = tag; + this.label = label; + this.accountIndices = accountIndices; + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public List getAccountIndices() { + return accountIndices; + } + + public void setAccountIndices(List accountIndices) { + this.accountIndices = accountIndices; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((accountIndices == null) ? 0 : accountIndices.hashCode()); + result = prime * result + ((label == null) ? 0 : label.hashCode()); + result = prime * result + ((tag == null) ? 0 : tag.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + MoneroAccountTag other = (MoneroAccountTag) obj; + if (accountIndices == null) { + if (other.accountIndices != null) return false; + } else if (!accountIndices.equals(other.accountIndices)) return false; + if (label == null) { + if (other.label != null) return false; + } else if (!label.equals(other.label)) return false; + if (tag == null) { + if (other.tag != null) return false; + } else if (!tag.equals(other.tag)) return false; + return true; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroAddressBookEntry.java b/core/src/main/java/monero/wallet/model/MoneroAddressBookEntry.java new file mode 100644 index 00000000000..45e5c69d423 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroAddressBookEntry.java @@ -0,0 +1,52 @@ +package monero.wallet.model; + +/** + * Monero address book entry model. + */ +public class MoneroAddressBookEntry { + + private int index; + private String address; + private String paymentId; + private String description; + + public MoneroAddressBookEntry(int index, String address, String paymentId, String description) { + super(); + this.index = index; + this.address = address; + this.paymentId = paymentId; + this.description = description; + } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getPaymentId() { + return paymentId; + } + + public void setPaymentId(String paymentId) { + this.paymentId = paymentId; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroCheck.java b/core/src/main/java/monero/wallet/model/MoneroCheck.java new file mode 100644 index 00000000000..26a179b2869 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroCheck.java @@ -0,0 +1,20 @@ +package monero.wallet.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Base class for results from checking a transaction or reserve proof. + */ +public class MoneroCheck { + + public Boolean isGood; + + @JsonProperty("isGood") + public Boolean isGood() { + return isGood; + } + + public void setIsGood(Boolean isGood) { + this.isGood = isGood; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroCheckReserve.java b/core/src/main/java/monero/wallet/model/MoneroCheckReserve.java new file mode 100644 index 00000000000..f158060b126 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroCheckReserve.java @@ -0,0 +1,28 @@ +package monero.wallet.model; + +import java.math.BigInteger; + +/** + * Results from checking a reserve proof. + */ +public class MoneroCheckReserve extends MoneroCheck { + + private BigInteger totalAmount; + private BigInteger unconfirmedSpentAmount; + + public BigInteger getTotalAmount() { + return totalAmount; + } + + public void setTotalAmount(BigInteger totalAmount) { + this.totalAmount = totalAmount; + } + + public BigInteger getUnconfirmedSpentAmount() { + return unconfirmedSpentAmount; + } + + public void setUnconfirmedSpentAmount(BigInteger unconfirmedSpentAmount) { + this.unconfirmedSpentAmount = unconfirmedSpentAmount; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroCheckTx.java b/core/src/main/java/monero/wallet/model/MoneroCheckTx.java new file mode 100644 index 00000000000..3366c651642 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroCheckTx.java @@ -0,0 +1,37 @@ +package monero.wallet.model; + +import java.math.BigInteger; + +/** + * Results from checking a transaction key. + */ +public class MoneroCheckTx extends MoneroCheck { + + public Boolean inTxPool; + public Long numConfirmations; + public BigInteger receivedAmount; + + public Boolean getInTxPool() { + return inTxPool; + } + + public void setInTxPool(Boolean inTxPool) { + this.inTxPool = inTxPool; + } + + public Long getNumConfirmations() { + return numConfirmations; + } + + public void setNumConfirmations(Long numConfirmations) { + this.numConfirmations = numConfirmations; + } + + public BigInteger getReceivedAmount() { + return receivedAmount; + } + + public void setReceivedAmount(BigInteger receivedAmount) { + this.receivedAmount = receivedAmount; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroDestination.java b/core/src/main/java/monero/wallet/model/MoneroDestination.java new file mode 100644 index 00000000000..8c590f194d9 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroDestination.java @@ -0,0 +1,90 @@ +package monero.wallet.model; + +import java.math.BigInteger; + +import monero.utils.MoneroUtils; + +/** + * Models an outgoing transfer destination. + */ +public class MoneroDestination { + + private String address; + private BigInteger amount; + + public MoneroDestination() { + // nothing to construct + } + + public MoneroDestination(String address) { + super(); + this.address = address; + } + + public MoneroDestination(String address, BigInteger amount) { + super(); + this.address = address; + this.amount = amount; + } + + public MoneroDestination(MoneroDestination destination) { + this.address = destination.address; + this.amount = destination.amount; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public BigInteger getAmount() { + return amount; + } + + public void setAmount(BigInteger amount) { + this.amount = amount; + } + + public MoneroDestination copy() { + return new MoneroDestination(this); + } + + public String toString() { + return toString(0); + } + + public String toString(int indent) { + StringBuilder sb = new StringBuilder(); + sb.append(MoneroUtils.kvLine("Address", this.getAddress(), indent)); + sb.append(MoneroUtils.kvLine("Amount", this.getAmount() != null ? this.getAmount().toString() : null, indent)); + String str = sb.toString(); + return str.substring(0, str.length() - 1); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((address == null) ? 0 : address.hashCode()); + result = prime * result + ((amount == null) ? 0 : amount.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + MoneroDestination other = (MoneroDestination) obj; + if (address == null) { + if (other.address != null) return false; + } else if (!address.equals(other.address)) return false; + if (amount == null) { + if (other.amount != null) return false; + } else if (!amount.equals(other.amount)) return false; + return true; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroIncomingTransfer.java b/core/src/main/java/monero/wallet/model/MoneroIncomingTransfer.java new file mode 100644 index 00000000000..c797ce44b46 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroIncomingTransfer.java @@ -0,0 +1,143 @@ +package monero.wallet.model; + +import static org.junit.Assert.assertTrue; + +import java.math.BigInteger; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import monero.utils.MoneroUtils; + +/** + * Models an incoming transfer of funds to the wallet. + */ +public class MoneroIncomingTransfer extends MoneroTransfer { + + private Integer subaddressIndex; + private String address; + + public MoneroIncomingTransfer() { + // nothing to initialize + } + + public MoneroIncomingTransfer(final MoneroIncomingTransfer transfer) { + super(transfer); + this.subaddressIndex = transfer.subaddressIndex; + this.address = transfer.address; + } + + @Override + public MoneroIncomingTransfer copy() { + return new MoneroIncomingTransfer(this); + } + + @JsonProperty("isIncoming") + public Boolean isIncoming() { + return true; + } + + public Integer getSubaddressIndex() { + return subaddressIndex; + } + + public MoneroIncomingTransfer setSubaddressIndex(Integer subaddressIndex) { + this.subaddressIndex = subaddressIndex; + return this; + } + + public String getAddress() { + return address; + } + + public MoneroIncomingTransfer setAddress(String address) { + this.address = address; + return this; + } + + public MoneroIncomingTransfer merge(MoneroTransfer transfer) { + assertTrue(transfer instanceof MoneroIncomingTransfer); + return merge((MoneroIncomingTransfer) transfer); + } + + /** + * Updates this transaction by merging the latest information from the given + * transaction. + * + * Merging can modify or build references to the transfer given so it + * should not be re-used or it should be copied before calling this method. + * + * @param transfer is the transfer to merge into this one + * @return this transfer for chaining + */ + public MoneroIncomingTransfer merge(MoneroIncomingTransfer transfer) { + super.merge(transfer); + assert(transfer instanceof MoneroIncomingTransfer); + if (this == transfer) return this; + this.setSubaddressIndex(MoneroUtils.reconcile(this.getSubaddressIndex(), transfer.getSubaddressIndex())); + this.setAddress(MoneroUtils.reconcile(this.getAddress(), transfer.getAddress())); + return this; + } + + public String toString() { + return toString(0); + } + + public String toString(int indent) { + StringBuilder sb = new StringBuilder(); + sb.append(super.toString(indent) + "\n"); + sb.append(MoneroUtils.kvLine("Subaddress index", this.getSubaddressIndex(), indent)); + sb.append(MoneroUtils.kvLine("Address", this.getAddress(), indent)); + String str = sb.toString(); + return str.substring(0, str.length() - 1); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((address == null) ? 0 : address.hashCode()); + result = prime * result + ((subaddressIndex == null) ? 0 : subaddressIndex.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!super.equals(obj)) return false; + if (getClass() != obj.getClass()) return false; + MoneroIncomingTransfer other = (MoneroIncomingTransfer) obj; + if (address == null) { + if (other.address != null) return false; + } else if (!address.equals(other.address)) return false; + if (subaddressIndex == null) { + if (other.subaddressIndex != null) return false; + } else if (!subaddressIndex.equals(other.subaddressIndex)) return false; + return true; + } + + //------------------- OVERRIDE CO-VARIANT RETURN TYPES --------------------- + + @Override + public MoneroIncomingTransfer setTx(MoneroTxWallet tx) { + super.setTx(tx); + return this; + } + + @Override + public MoneroIncomingTransfer setAmount(BigInteger amount) { + super.setAmount(amount); + return this; + } + + @Override + public MoneroIncomingTransfer setAccountIndex(Integer accountIndex) { + super.setAccountIndex(accountIndex); + return this; + } + + @Override + public MoneroIncomingTransfer setNumSuggestedConfirmations(Long numSuggestedConfirmations) { + super.setNumSuggestedConfirmations(numSuggestedConfirmations); + return this; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroIntegratedAddress.java b/core/src/main/java/monero/wallet/model/MoneroIntegratedAddress.java new file mode 100644 index 00000000000..b68eecf74d6 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroIntegratedAddress.java @@ -0,0 +1,78 @@ +package monero.wallet.model; + +/** + * Monero integrated address model. + */ +public class MoneroIntegratedAddress { + + private String standardAddress; + private String paymentId; + private String integratedAddress; + + public MoneroIntegratedAddress() { + // necessary for deserialization + } + + public MoneroIntegratedAddress(String standardAddress, String paymentId, String integratedAddress) { + super(); + this.standardAddress = standardAddress; + this.paymentId = paymentId; + this.integratedAddress = integratedAddress; + } + + public String getStandardAddress() { + return standardAddress; + } + + public void setStandardAddress(String standardAddress) { + this.standardAddress = standardAddress; + } + + public String getPaymentId() { + return paymentId; + } + + public void setPaymentId(String paymentId) { + this.paymentId = paymentId; + } + + public String getIntegratedAddress() { + return integratedAddress; + } + + public void setIntegratedAddress(String integratedAddress) { + this.integratedAddress = integratedAddress; + } + + public String toString() { + return integratedAddress; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((standardAddress == null) ? 0 : standardAddress.hashCode()); + result = prime * result + ((integratedAddress == null) ? 0 : integratedAddress.hashCode()); + result = prime * result + ((paymentId == null) ? 0 : paymentId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + MoneroIntegratedAddress other = (MoneroIntegratedAddress) obj; + if (standardAddress == null) { + if (other.standardAddress != null) return false; + } else if (!standardAddress.equals(other.standardAddress)) return false; + if (integratedAddress == null) { + if (other.integratedAddress != null) return false; + } else if (!integratedAddress.equals(other.integratedAddress)) return false; + if (paymentId == null) { + if (other.paymentId != null) return false; + } else if (!paymentId.equals(other.paymentId)) return false; + return true; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroKeyImageImportResult.java b/core/src/main/java/monero/wallet/model/MoneroKeyImageImportResult.java new file mode 100644 index 00000000000..8f44a6be4ec --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroKeyImageImportResult.java @@ -0,0 +1,37 @@ +package monero.wallet.model; + +import java.math.BigInteger; + +/** + * Models results from importing key images. + */ +public class MoneroKeyImageImportResult { + + private Long height; + private BigInteger spentAmount; + private BigInteger unspentAmount; + + public Long getHeight() { + return height; + } + + public void setHeight(Long height) { + this.height = height; + } + + public BigInteger getSpentAmount() { + return spentAmount; + } + + public void setSpentAmount(BigInteger spentAmount) { + this.spentAmount = spentAmount; + } + + public BigInteger getUnspentAmount() { + return unspentAmount; + } + + public void setUnspentAmount(BigInteger unspentAmount) { + this.unspentAmount = unspentAmount; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroMultisigInfo.java b/core/src/main/java/monero/wallet/model/MoneroMultisigInfo.java new file mode 100644 index 00000000000..992a1d05533 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroMultisigInfo.java @@ -0,0 +1,44 @@ +package monero.wallet.model; + +/** + * Models information about a multisig wallet. + */ +public class MoneroMultisigInfo { + + private boolean isMultisig; + private Boolean isReady; + private Integer threshold; + private Integer numParticipants; + + public boolean isMultisig() { + return isMultisig; + } + + public void setIsMultisig(boolean isMultisig) { + this.isMultisig = isMultisig; + } + + public Boolean isReady() { + return isReady; + } + + public void setIsReady(Boolean isReady) { + this.isReady = isReady; + } + + public Integer getThreshold() { + return threshold; + } + + public void setThreshold(Integer threshold) { + this.threshold = threshold; + } + + public Integer getNumParticipants() { + return numParticipants; + } + + public void setNumParticipants(Integer numParticipants) { + this.numParticipants = numParticipants; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroMultisigInitResult.java b/core/src/main/java/monero/wallet/model/MoneroMultisigInitResult.java new file mode 100644 index 00000000000..ba0154865db --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroMultisigInitResult.java @@ -0,0 +1,28 @@ +package monero.wallet.model; + +/** + * Models the result of initializing a multisig wallet which results in the + * multisig wallet's address xor another multisig hex to share with + * participants to create the wallet. + */ +public class MoneroMultisigInitResult { + + private String address; + private String multisigHex; + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getMultisigHex() { + return multisigHex; + } + + public void setMultisigHex(String multisigHex) { + this.multisigHex = multisigHex; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroMultisigSignResult.java b/core/src/main/java/monero/wallet/model/MoneroMultisigSignResult.java new file mode 100644 index 00000000000..4a6f815f0b7 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroMultisigSignResult.java @@ -0,0 +1,52 @@ +package monero.wallet.model; + +import java.util.List; + +/** + * Models the result of signing multisig tx hex. + */ +public class MoneroMultisigSignResult { + + private String signedMultisigTxHex; + private List txIds; + + public String getSignedMultisigTxHex() { + return signedMultisigTxHex; + } + + public void setSignedMultisigTxHex(String signedTxMultisigHex) { + this.signedMultisigTxHex = signedTxMultisigHex; + } + + public List getTxIds() { + return txIds; + } + + public void setTxIds(List txIds) { + this.txIds = txIds; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((signedMultisigTxHex == null) ? 0 : signedMultisigTxHex.hashCode()); + result = prime * result + ((txIds == null) ? 0 : txIds.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + MoneroMultisigSignResult other = (MoneroMultisigSignResult) obj; + if (signedMultisigTxHex == null) { + if (other.signedMultisigTxHex != null) return false; + } else if (!signedMultisigTxHex.equals(other.signedMultisigTxHex)) return false; + if (txIds == null) { + if (other.txIds != null) return false; + } else if (!txIds.equals(other.txIds)) return false; + return true; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroOutgoingTransfer.java b/core/src/main/java/monero/wallet/model/MoneroOutgoingTransfer.java new file mode 100644 index 00000000000..cdcdf5f6775 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroOutgoingTransfer.java @@ -0,0 +1,173 @@ +package monero.wallet.model; + +import static org.junit.Assert.assertTrue; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import monero.utils.MoneroUtils; + +/** + * Models an outgoing transfer of funds from the wallet. + */ +public class MoneroOutgoingTransfer extends MoneroTransfer { + + private List subaddressIndices; + private List addresses; + private List destinations; + + public MoneroOutgoingTransfer() { + // nothing to initialize + } + + public MoneroOutgoingTransfer(final MoneroOutgoingTransfer transfer) { + super(transfer); + if (transfer.subaddressIndices != null) this.subaddressIndices = new ArrayList(transfer.subaddressIndices); + if (transfer.addresses != null) this.addresses = new ArrayList(transfer.addresses); + if (transfer.destinations != null) { + this.destinations = new ArrayList(); + for (MoneroDestination destination : transfer.getDestinations()) { + this.destinations.add(destination.copy()); + } + } + } + + @Override + public MoneroOutgoingTransfer copy() { + return new MoneroOutgoingTransfer(this); + } + + @JsonProperty("isIncoming") + public Boolean isIncoming() { + return false; + } + + public List getSubaddressIndices() { + return subaddressIndices; + } + + public MoneroOutgoingTransfer setSubaddressIndices(List subaddressIndices) { + this.subaddressIndices = subaddressIndices; + return this; + } + + public List getAddresses() { + return addresses; + } + + public MoneroOutgoingTransfer setAddresses(List addresses) { + this.addresses = addresses; + return this; + } + + public List getDestinations() { + return destinations; + } + + public MoneroOutgoingTransfer setDestinations(List destinations) { + this.destinations = destinations; + return this; + } + + public MoneroOutgoingTransfer merge(MoneroTransfer transfer) { + assertTrue(transfer instanceof MoneroOutgoingTransfer); + return merge((MoneroOutgoingTransfer) transfer); + } + + /** + * Updates this transaction by merging the latest information from the given + * transaction. + * + * Merging can modify or build references to the transfer given so it + * should not be re-used or it should be copied before calling this method. + * + * @param transfer is the transfer to merge into this one + * @return this transfer for chaining + */ + public MoneroOutgoingTransfer merge(MoneroOutgoingTransfer transfer) { + super.merge(transfer); + assertTrue(transfer instanceof MoneroOutgoingTransfer); + if (this == transfer) return this; + this.setSubaddressIndices(MoneroUtils.reconcile(this.getSubaddressIndices(), transfer.getSubaddressIndices())); + this.setAddresses(MoneroUtils.reconcile(this.getAddresses(), transfer.getAddresses())); + this.setDestinations(MoneroUtils.reconcile(this.getDestinations(), transfer.getDestinations())); + return this; + } + + public String toString() { + return toString(0); + } + + public String toString(int indent) { + StringBuilder sb = new StringBuilder(); + sb.append(super.toString(indent) + "\n"); + sb.append(MoneroUtils.kvLine("Subaddress indices", this.getSubaddressIndices(), indent)); + sb.append(MoneroUtils.kvLine("Addresses", this.getAddresses(), indent)); + if (this.getDestinations() != null) { + sb.append(MoneroUtils.kvLine("Destinations", "", indent)); + for (int i = 0; i < this.getDestinations().size(); i++) { + sb.append(MoneroUtils.kvLine(i + 1, "", indent + 1)); + sb.append(getDestinations().get(i).toString(indent + 2) + "\n"); + } + } + String str = sb.toString(); + return str.substring(0, str.length() - 1); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((addresses == null) ? 0 : addresses.hashCode()); + result = prime * result + ((destinations == null) ? 0 : destinations.hashCode()); + result = prime * result + ((subaddressIndices == null) ? 0 : subaddressIndices.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!super.equals(obj)) return false; + if (getClass() != obj.getClass()) return false; + MoneroOutgoingTransfer other = (MoneroOutgoingTransfer) obj; + if (addresses == null) { + if (other.addresses != null) return false; + } else if (!addresses.equals(other.addresses)) return false; + if (destinations == null) { + if (other.destinations != null) return false; + } else if (!destinations.equals(other.destinations)) return false; + if (subaddressIndices == null) { + if (other.subaddressIndices != null) return false; + } else if (!subaddressIndices.equals(other.subaddressIndices)) return false; + return true; + } + + // ------------------- OVERRIDE CO-VARIANT RETURN TYPES --------------------- + + @Override + public MoneroOutgoingTransfer setTx(MoneroTxWallet tx) { + super.setTx(tx); + return this; + } + + @Override + public MoneroOutgoingTransfer setAmount(BigInteger amount) { + super.setAmount(amount); + return this; + } + + @Override + public MoneroOutgoingTransfer setAccountIndex(Integer accountIndex) { + super.setAccountIndex(accountIndex); + return this; + } + + @Override + public MoneroOutgoingTransfer setNumSuggestedConfirmations(Long numSuggestedConfirmations) { + super.setNumSuggestedConfirmations(numSuggestedConfirmations); + return this; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroOutputQuery.java b/core/src/main/java/monero/wallet/model/MoneroOutputQuery.java new file mode 100644 index 00000000000..c999ab157ab --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroOutputQuery.java @@ -0,0 +1,164 @@ +package monero.wallet.model; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import common.types.Filter; +import common.utils.GenUtils; +import monero.daemon.model.MoneroKeyImage; +import monero.daemon.model.MoneroTx; + +/** + * Configures a query to retrieve wallet outputs (i.e. outputs that the wallet has or had the + * ability to spend). + * + * All outputs are returned except those that do not meet the criteria defined in this query. + */ +public class MoneroOutputQuery extends MoneroOutputWallet implements Filter { + + private MoneroTxQuery txQuery; + private List subaddressIndices; + private static MoneroOutputWallet EMPTY_OUTPUT = new MoneroOutputWallet(); + + public MoneroOutputQuery() { + super(); + } + + public MoneroOutputQuery(final MoneroOutputQuery query) { + super(query); + if (query.subaddressIndices != null) this.subaddressIndices = new ArrayList(query.subaddressIndices); + this.txQuery = query.txQuery; // reference original by default, MoneroTxQuery's deep copy will set this to itself + } + + public MoneroOutputQuery copy() { + return new MoneroOutputQuery(this); + } + + @JsonIgnore + public MoneroTxQuery getTxQuery() { + return txQuery; + } + + public MoneroOutputQuery setTxQuery(MoneroTxQuery txQuery) { + this.txQuery = txQuery; + return this; + } + + public List getSubaddressIndices() { + return subaddressIndices; + } + + public MoneroOutputQuery setSubaddressIndices(List subaddressIndices) { + this.subaddressIndices = subaddressIndices; + return this; + } + + public MoneroOutputQuery setSubaddressIndices(Integer... subaddressIndices) { + this.subaddressIndices = GenUtils.arrayToList(subaddressIndices); + return this; + } + + @Override + public boolean meetsCriteria(MoneroOutputWallet output) { + if (!(output instanceof MoneroOutputWallet)) return false; + + // filter on output + if (this.getAccountIndex() != null && !this.getAccountIndex().equals(output.getAccountIndex())) return false; + if (this.getSubaddressIndex() != null && !this.getSubaddressIndex().equals(output.getSubaddressIndex())) return false; + if (this.getAmount() != null && this.getAmount().compareTo(output.getAmount()) != 0) return false; + if (this.isSpent() != null && !this.isSpent().equals(output.isSpent())) return false; + if (this.isUnlocked() != null && !this.isUnlocked().equals(output.isUnlocked())) return false; + + // filter on output key image + if (this.getKeyImage() != null) { + if (output.getKeyImage() == null) return false; + if (this.getKeyImage().getHex() != null && !this.getKeyImage().getHex().equals(output.getKeyImage().getHex())) return false; + if (this.getKeyImage().getSignature() != null && !this.getKeyImage().getSignature().equals(output.getKeyImage().getSignature())) return false; + } + + // filter on extensions + if (this.getSubaddressIndices() != null && !this.getSubaddressIndices().contains(output.getSubaddressIndex())) return false; + + // filter with tx query + if (this.getTxQuery() != null && !this.getTxQuery().meetsCriteria(output.getTx())) return false; + + // output meets query + return true; + } + + public boolean isDefault() { + return meetsCriteria(EMPTY_OUTPUT); + } + + // ------------------- OVERRIDE CO-VARIANT RETURN TYPES --------------------- + + @Override + public MoneroOutputQuery setTx(MoneroTx tx) { + super.setTx(tx); + return this; + } + + @Override + public MoneroOutputQuery setTx(MoneroTxWallet tx) { + super.setTx(tx); + return this; + } + + @Override + public MoneroOutputQuery setAccountIndex(Integer accountIndex) { + super.setAccountIndex(accountIndex); + return this; + } + + @Override + public MoneroOutputQuery setSubaddressIndex(Integer subaddressIndex) { + super.setSubaddressIndex(subaddressIndex); + return this; + } + + @Override + public MoneroOutputQuery setIsSpent(Boolean isSpent) { + super.setIsSpent(isSpent); + return this; + } + + @Override + public MoneroOutputQuery setIsUnlocked(Boolean isUnlocked) { + super.setIsUnlocked(isUnlocked); + return this; + } + + + @Override + public MoneroOutputQuery setKeyImage(MoneroKeyImage keyImage) { + super.setKeyImage(keyImage); + return this; + } + + @Override + public MoneroOutputQuery setAmount(BigInteger amount) { + super.setAmount(amount); + return this; + } + + @Override + public MoneroOutputQuery setIndex(Integer index) { + super.setIndex(index); + return this; + } + + @Override + public MoneroOutputQuery setRingOutputIndices(List ringOutputIndices) { + super.setRingOutputIndices(ringOutputIndices); + return this; + } + + @Override + public MoneroOutputQuery setStealthPublicKey(String stealthPublicKey) { + super.setStealthPublicKey(stealthPublicKey); + return this; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroOutputWallet.java b/core/src/main/java/monero/wallet/model/MoneroOutputWallet.java new file mode 100644 index 00000000000..e6f9cd53db6 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroOutputWallet.java @@ -0,0 +1,175 @@ +package monero.wallet.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import monero.daemon.model.MoneroOutput; +import monero.daemon.model.MoneroTx; +import monero.utils.MoneroException; +import monero.utils.MoneroUtils; + +/** + * Models a Monero output with wallet extensions. + */ +public class MoneroOutputWallet extends MoneroOutput { + + private Integer accountIndex; + private Integer subaddressIndex; + private Boolean isSpent; + private Boolean isUnlocked; + private Boolean isFrozen; + + public MoneroOutputWallet() { + // nothing to construct + } + + /** + * Deep copy constructor. + * + * @param output is the output to initialize from + */ + public MoneroOutputWallet(final MoneroOutputWallet output) { + super(output); + this.accountIndex = output.accountIndex; + this.subaddressIndex = output.subaddressIndex; + this.isSpent = output.isSpent; + this.isUnlocked = output.isUnlocked; + this.isFrozen = output.isFrozen; + } + + public MoneroOutputWallet copy() { + return new MoneroOutputWallet(this); + } + + public MoneroTxWallet getTx() { + return (MoneroTxWallet) super.getTx(); + } + + @JsonIgnore + public MoneroOutputWallet setTx(MoneroTx tx) { + if (tx != null && !(tx instanceof MoneroTxWallet)) throw new MoneroException("Wallet output's transaction must be of type MoneroTxWallet"); + super.setTx(tx); + return this; + } + + @JsonProperty("tx") + public MoneroOutputWallet setTx(MoneroTxWallet tx) { + super.setTx(tx); + return this; + } + + public Integer getAccountIndex() { + return accountIndex; + } + + public MoneroOutputWallet setAccountIndex(Integer accountIndex) { + this.accountIndex = accountIndex; + return this; + } + + public Integer getSubaddressIndex() { + return subaddressIndex; + } + + public MoneroOutputWallet setSubaddressIndex(Integer subaddressIndex) { + this.subaddressIndex = subaddressIndex; + return this; + } + + @JsonProperty("isSpent") + public Boolean isSpent() { + return isSpent; + } + + public MoneroOutputWallet setIsSpent(Boolean isSpent) { + this.isSpent = isSpent; + return this; + } + + @JsonProperty("isUnlocked") + public Boolean isUnlocked() { + return isUnlocked; + } + + public MoneroOutputWallet setIsUnlocked(Boolean isUnlocked) { + this.isUnlocked = isUnlocked; + return this; + } + + /** + * Indicates if this output has been deemed 'malicious' and will therefore + * not be spent by the wallet. + * + * @return Boolean is whether or not this output is frozen + */ + @JsonProperty("isFrozen") + public Boolean isFrozen() { + return isFrozen; + } + + public MoneroOutputWallet setIsFrozen(Boolean isFrozen) { + this.isFrozen = isFrozen; + return this; + } + + public MoneroOutputWallet merge(MoneroOutput output) { + return merge((MoneroOutputWallet) output); + } + + public MoneroOutputWallet merge(MoneroOutputWallet output) { + if (this == output) return this; + super.merge(output); + this.setAccountIndex(MoneroUtils.reconcile(this.getAccountIndex(), output.getAccountIndex())); + this.setSubaddressIndex(MoneroUtils.reconcile(this.getSubaddressIndex(), output.getSubaddressIndex())); + this.setIsSpent(MoneroUtils.reconcile(this.isSpent(), output.isSpent(), null, true, null)); // output can become spent + return this; + } + + public String toString(int indent) { + StringBuilder sb = new StringBuilder(); + sb.append(super.toString(indent) + "\n"); + sb.append(MoneroUtils.kvLine("Account index", this.getAccountIndex(), indent)); + sb.append(MoneroUtils.kvLine("Subaddress index", this.getSubaddressIndex(), indent)); + sb.append(MoneroUtils.kvLine("Is spent", this.isSpent(), indent)); + sb.append(MoneroUtils.kvLine("Is unlocked", this.isUnlocked(), indent)); + sb.append(MoneroUtils.kvLine("Is frozen", this.isFrozen(), indent)); + String str = sb.toString(); + return str.substring(0, str.length() - 1); // strip last newline + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((accountIndex == null) ? 0 : accountIndex.hashCode()); + result = prime * result + ((isFrozen == null) ? 0 : isFrozen.hashCode()); + result = prime * result + ((isSpent == null) ? 0 : isSpent.hashCode()); + result = prime * result + ((isUnlocked == null) ? 0 : isUnlocked.hashCode()); + result = prime * result + ((subaddressIndex == null) ? 0 : subaddressIndex.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!super.equals(obj)) return false; + if (getClass() != obj.getClass()) return false; + MoneroOutputWallet other = (MoneroOutputWallet) obj; + if (accountIndex == null) { + if (other.accountIndex != null) return false; + } else if (!accountIndex.equals(other.accountIndex)) return false; + if (isFrozen == null) { + if (other.isFrozen != null) return false; + } else if (!isFrozen.equals(other.isFrozen)) return false; + if (isSpent == null) { + if (other.isSpent != null) return false; + } else if (!isSpent.equals(other.isSpent)) return false; + if (isUnlocked == null) { + if (other.isUnlocked != null) return false; + } else if (!isUnlocked.equals(other.isUnlocked)) return false; + if (subaddressIndex == null) { + if (other.subaddressIndex != null) return false; + } else if (!subaddressIndex.equals(other.subaddressIndex)) return false; + return true; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroSendPriority.java b/core/src/main/java/monero/wallet/model/MoneroSendPriority.java new file mode 100644 index 00000000000..02d80d750a6 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroSendPriority.java @@ -0,0 +1,11 @@ +package monero.wallet.model; + +/** + * Enumerates send priorities. + */ +public enum MoneroSendPriority { + DEFAULT, + UNIMPORTANT, + NORMAL, + ELEVATED +} diff --git a/core/src/main/java/monero/wallet/model/MoneroSendRequest.java b/core/src/main/java/monero/wallet/model/MoneroSendRequest.java new file mode 100644 index 00000000000..13646e17411 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroSendRequest.java @@ -0,0 +1,330 @@ +package monero.wallet.model; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import common.utils.GenUtils; + +/** + * Configures a request to send/sweep funds or create a payment URI. + */ +public class MoneroSendRequest { + + private List destinations; + private String paymentId; + private MoneroSendPriority priority; + private Integer mixin; + private Integer ringSize; + private BigInteger fee; + private Integer accountIndex; + private List subaddressIndices; + private Long unlockTime; + private Boolean canSplit; + private Boolean doNotRelay; + private String note; + private String recipientName; + private BigInteger belowAmount; + private Boolean sweepEachSubaddress; + private String keyImage; + + public MoneroSendRequest() { + this((String) null); + } + + public MoneroSendRequest(String address) { + this(address, null); + } + + public MoneroSendRequest(String address, BigInteger amount) { + this(null, address, amount); + } + + public MoneroSendRequest(Integer accountIndex, String address) { + this(accountIndex, address, null); + } + + public MoneroSendRequest(Integer accountIndex, String address, BigInteger amount) { + this(accountIndex, address, amount, null); + } + + public MoneroSendRequest(Integer accountIndex, String address, BigInteger amount, MoneroSendPriority priority) { + this.accountIndex = accountIndex; + if (address != null || amount != null) this.destinations = Arrays.asList(new MoneroDestination(address, amount)); // map address and amount to default destination + this.priority = priority; + } + + MoneroSendRequest(final MoneroSendRequest req) { + if (req.destinations != null) { + this.destinations = new ArrayList(); + for (MoneroDestination destination : req.getDestinations()) this.destinations.add(destination.copy()); + } + this.paymentId = req.paymentId; + this.priority = req.priority; + this.mixin = req.mixin; + this.ringSize = req.ringSize; + this.fee = req.fee; + this.accountIndex = req.accountIndex; + if (req.subaddressIndices != null) this.subaddressIndices = new ArrayList(req.subaddressIndices); + this.unlockTime = req.unlockTime; + this.canSplit = req.canSplit; + this.doNotRelay = req.doNotRelay; + this.note = req.note; + this.recipientName = req.recipientName; + this.belowAmount = req.belowAmount; + this.sweepEachSubaddress = req.sweepEachSubaddress; + this.keyImage = req.keyImage; + } + + public MoneroSendRequest copy() { + return new MoneroSendRequest(this); + } + + public MoneroSendRequest addDestination(MoneroDestination destination) { + if (this.destinations == null) this.destinations = new ArrayList(); + this.destinations.add(destination); + return this; + } + + public List getDestinations() { + return destinations; + } + + @JsonProperty("destinations") + public MoneroSendRequest setDestinations(List destinations) { + this.destinations = destinations; + return this; + } + + public MoneroSendRequest setDestinations(MoneroDestination... destinations) { + this.destinations = GenUtils.arrayToList(destinations); + return this; + } + + public String getPaymentId() { + return paymentId; + } + + public MoneroSendRequest setPaymentId(String paymentId) { + this.paymentId = paymentId; + return this; + } + + public MoneroSendPriority getPriority() { + return priority; + } + + public MoneroSendRequest setPriority(MoneroSendPriority priority) { + this.priority = priority; + return this; + } + + public Integer getMixin() { + return mixin; + } + + public MoneroSendRequest setMixin(Integer mixin) { + this.mixin = mixin; + return this; + } + + public Integer getRingSize() { + return ringSize; + } + + public MoneroSendRequest setRingSize(Integer ringSize) { + this.ringSize = ringSize; + return this; + } + + public BigInteger getFee() { + return fee; + } + + public MoneroSendRequest setFee(BigInteger fee) { + this.fee = fee; + return this; + } + + public Integer getAccountIndex() { + return accountIndex; + } + + public MoneroSendRequest setAccountIndex(Integer accountIndex) { + this.accountIndex = accountIndex; + return this; + } + + public List getSubaddressIndices() { + return subaddressIndices; + } + + public MoneroSendRequest setSubaddressIndex(int subaddressIndex) { + setSubaddressIndices(subaddressIndex); + return this; + } + + @JsonProperty("subaddressIndices") + public MoneroSendRequest setSubaddressIndices(List subaddressIndices) { + this.subaddressIndices = subaddressIndices; + return this; + } + + public MoneroSendRequest setSubaddressIndices(Integer... subaddressIndices) { + this.subaddressIndices = GenUtils.arrayToList(subaddressIndices); + return this; + } + + public Long getUnlockTime() { + return unlockTime; + } + + public MoneroSendRequest setUnlockTime(Long unlockTime) { + this.unlockTime = unlockTime; + return this; + } + + public Boolean getCanSplit() { + return canSplit; + } + + public MoneroSendRequest setCanSplit(Boolean canSplit) { + this.canSplit = canSplit; + return this; + } + + public Boolean getDoNotRelay() { + return doNotRelay; + } + + public MoneroSendRequest setDoNotRelay(Boolean doNotRelay) { + this.doNotRelay = doNotRelay; + return this; + } + + public String getNote() { + return note; + } + + public MoneroSendRequest setNote(String note) { + this.note = note; + return this; + } + + public String getRecipientName() { + return recipientName; + } + + public MoneroSendRequest setRecipientName(String recipientName) { + this.recipientName = recipientName; + return this; + } + + public BigInteger getBelowAmount() { + return belowAmount; + } + + public MoneroSendRequest setBelowAmount(BigInteger belowAmount) { + this.belowAmount = belowAmount; + return this; + } + + public Boolean getSweepEachSubaddress() { + return sweepEachSubaddress; + } + + public MoneroSendRequest setSweepEachSubaddress(Boolean sweepEachSubaddress) { + this.sweepEachSubaddress = sweepEachSubaddress; + return this; + } + + public String getKeyImage() { + return keyImage; + } + + public MoneroSendRequest setKeyImage(String keyImage) { + this.keyImage = keyImage; + return this; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((accountIndex == null) ? 0 : accountIndex.hashCode()); + result = prime * result + ((belowAmount == null) ? 0 : belowAmount.hashCode()); + result = prime * result + ((canSplit == null) ? 0 : canSplit.hashCode()); + result = prime * result + ((destinations == null) ? 0 : destinations.hashCode()); + result = prime * result + ((doNotRelay == null) ? 0 : doNotRelay.hashCode()); + result = prime * result + ((fee == null) ? 0 : fee.hashCode()); + result = prime * result + ((keyImage == null) ? 0 : keyImage.hashCode()); + result = prime * result + ((mixin == null) ? 0 : mixin.hashCode()); + result = prime * result + ((note == null) ? 0 : note.hashCode()); + result = prime * result + ((paymentId == null) ? 0 : paymentId.hashCode()); + result = prime * result + ((priority == null) ? 0 : priority.hashCode()); + result = prime * result + ((recipientName == null) ? 0 : recipientName.hashCode()); + result = prime * result + ((ringSize == null) ? 0 : ringSize.hashCode()); + result = prime * result + ((subaddressIndices == null) ? 0 : subaddressIndices.hashCode()); + result = prime * result + ((sweepEachSubaddress == null) ? 0 : sweepEachSubaddress.hashCode()); + result = prime * result + ((unlockTime == null) ? 0 : unlockTime.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + MoneroSendRequest other = (MoneroSendRequest) obj; + if (accountIndex == null) { + if (other.accountIndex != null) return false; + } else if (!accountIndex.equals(other.accountIndex)) return false; + if (belowAmount == null) { + if (other.belowAmount != null) return false; + } else if (!belowAmount.equals(other.belowAmount)) return false; + if (canSplit == null) { + if (other.canSplit != null) return false; + } else if (!canSplit.equals(other.canSplit)) return false; + if (destinations == null) { + if (other.destinations != null) return false; + } else if (!destinations.equals(other.destinations)) return false; + if (doNotRelay == null) { + if (other.doNotRelay != null) return false; + } else if (!doNotRelay.equals(other.doNotRelay)) return false; + if (fee == null) { + if (other.fee != null) return false; + } else if (!fee.equals(other.fee)) return false; + if (keyImage == null) { + if (other.keyImage != null) return false; + } else if (!keyImage.equals(other.keyImage)) return false; + if (mixin == null) { + if (other.mixin != null) return false; + } else if (!mixin.equals(other.mixin)) return false; + if (note == null) { + if (other.note != null) return false; + } else if (!note.equals(other.note)) return false; + if (paymentId == null) { + if (other.paymentId != null) return false; + } else if (!paymentId.equals(other.paymentId)) return false; + if (priority != other.priority) return false; + if (recipientName == null) { + if (other.recipientName != null) return false; + } else if (!recipientName.equals(other.recipientName)) return false; + if (ringSize == null) { + if (other.ringSize != null) return false; + } else if (!ringSize.equals(other.ringSize)) return false; + if (subaddressIndices == null) { + if (other.subaddressIndices != null) return false; + } else if (!subaddressIndices.equals(other.subaddressIndices)) return false; + if (sweepEachSubaddress == null) { + if (other.sweepEachSubaddress != null) return false; + } else if (!sweepEachSubaddress.equals(other.sweepEachSubaddress)) return false; + if (unlockTime == null) { + if (other.unlockTime != null) return false; + } else if (!unlockTime.equals(other.unlockTime)) return false; + return true; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroSubaddress.java b/core/src/main/java/monero/wallet/model/MoneroSubaddress.java new file mode 100644 index 00000000000..c3dd4e95df3 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroSubaddress.java @@ -0,0 +1,184 @@ +package monero.wallet.model; + +import java.math.BigInteger; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import monero.utils.MoneroUtils; + +/** + * Monero subaddress model. + */ +public class MoneroSubaddress { + + private Integer accountIndex; + private Integer index; + private String address; + private String label; + private BigInteger balance; + private BigInteger unlockedBalance; + private Long numUnspentOutputs; + private Boolean isUsed; + private Long numBlocksToUnlock; + + public MoneroSubaddress() { + // nothing to construct + } + + public MoneroSubaddress(String address) { + this.address = address; + } + + public Integer getAccountIndex() { + return accountIndex; + } + + public MoneroSubaddress setAccountIndex(Integer accountIndex) { + this.accountIndex = accountIndex; + return this; + } + + public Integer getIndex() { + return index; + } + + public MoneroSubaddress setIndex(Integer index) { + this.index = index; + return this; + } + + public String getAddress() { + return address; + } + + public MoneroSubaddress setAddress(String address) { + this.address = address; + return this; + } + + public String getLabel() { + return label; + } + + public MoneroSubaddress setLabel(String label) { + this.label = label; + return this; + } + + public BigInteger getBalance() { + return balance; + } + + public MoneroSubaddress setBalance(BigInteger balance) { + this.balance = balance; + return this; + } + + public BigInteger getUnlockedBalance() { + return unlockedBalance; + } + + public MoneroSubaddress setUnlockedBalance(BigInteger unlockedBalance) { + this.unlockedBalance = unlockedBalance; + return this; + } + + public Long getNumUnspentOutputs() { + return numUnspentOutputs; + } + + public MoneroSubaddress setNumUnspentOutputs(Long numUnspentOutputs) { + this.numUnspentOutputs = numUnspentOutputs; + return this; + } + + @JsonProperty("isUsed") + public Boolean isUsed() { + return isUsed; + } + + public MoneroSubaddress setIsUsed(Boolean isUsed) { + this.isUsed = isUsed; + return this; + } + + public Long getNumBlocksToUnlock() { + return numBlocksToUnlock; + } + + public MoneroSubaddress setNumBlocksToUnlock(Long numBlocksToUnlock) { + this.numBlocksToUnlock = numBlocksToUnlock; + return this; + } + + public String toString() { + return toString(0); + } + + public String toString(int indent) { + StringBuilder sb = new StringBuilder(); + sb.append(MoneroUtils.kvLine("Account index", this.getAccountIndex(), indent)); + sb.append(MoneroUtils.kvLine("Subaddress index", this.getIndex(), indent)); + sb.append(MoneroUtils.kvLine("Address", this.getAddress(), indent)); + sb.append(MoneroUtils.kvLine("Label", this.getLabel(), indent)); + sb.append(MoneroUtils.kvLine("Balance", this.getBalance(), indent)); + sb.append(MoneroUtils.kvLine("Unlocked balance", this.getUnlockedBalance(), indent)); + sb.append(MoneroUtils.kvLine("Num unspent outputs", this.getNumUnspentOutputs(), indent)); + sb.append(MoneroUtils.kvLine("Is used", this.isUsed(), indent)); + sb.append(MoneroUtils.kvLine("Num blocks to unlock", this.getNumBlocksToUnlock(), indent)); + String str = sb.toString(); + return str.substring(0, str.length() - 1); // strip last newline + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((accountIndex == null) ? 0 : accountIndex.hashCode()); + result = prime * result + ((address == null) ? 0 : address.hashCode()); + result = prime * result + ((balance == null) ? 0 : balance.hashCode()); + result = prime * result + ((index == null) ? 0 : index.hashCode()); + result = prime * result + ((isUsed == null) ? 0 : isUsed.hashCode()); + result = prime * result + ((label == null) ? 0 : label.hashCode()); + result = prime * result + ((numBlocksToUnlock == null) ? 0 : numBlocksToUnlock.hashCode()); + result = prime * result + ((numUnspentOutputs == null) ? 0 : numUnspentOutputs.hashCode()); + result = prime * result + ((unlockedBalance == null) ? 0 : unlockedBalance.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + MoneroSubaddress other = (MoneroSubaddress) obj; + if (accountIndex == null) { + if (other.accountIndex != null) return false; + } else if (!accountIndex.equals(other.accountIndex)) return false; + if (address == null) { + if (other.address != null) return false; + } else if (!address.equals(other.address)) return false; + if (balance == null) { + if (other.balance != null) return false; + } else if (!balance.equals(other.balance)) return false; + if (index == null) { + if (other.index != null) return false; + } else if (!index.equals(other.index)) return false; + if (isUsed == null) { + if (other.isUsed != null) return false; + } else if (!isUsed.equals(other.isUsed)) return false; + if (label == null) { + if (other.label != null) return false; + } else if (!label.equals(other.label)) return false; + if (numBlocksToUnlock == null) { + if (other.numBlocksToUnlock != null) return false; + } else if (!numBlocksToUnlock.equals(other.numBlocksToUnlock)) return false; + if (numUnspentOutputs == null) { + if (other.numUnspentOutputs != null) return false; + } else if (!numUnspentOutputs.equals(other.numUnspentOutputs)) return false; + if (unlockedBalance == null) { + if (other.unlockedBalance != null) return false; + } else if (!unlockedBalance.equals(other.unlockedBalance)) return false; + return true; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroSyncListener.java b/core/src/main/java/monero/wallet/model/MoneroSyncListener.java new file mode 100644 index 00000000000..5d76cf2459b --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroSyncListener.java @@ -0,0 +1,18 @@ +package monero.wallet.model; + +/** + * Interface to receive progress notifications as a wallet is synchronized. + */ +public interface MoneroSyncListener { + + /** + * Invoked as the wallet is synchronized. + * + * @param height is the height of the synced block + * @param startHeight is the starting height of the sync request + * @param endHeight is the ending height of the sync request + * @param percentDone is the sync progress as a percentage + * @param message is a human-readable description of the current progress + */ + public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message); +} diff --git a/core/src/main/java/monero/wallet/model/MoneroSyncResult.java b/core/src/main/java/monero/wallet/model/MoneroSyncResult.java new file mode 100644 index 00000000000..1e76867c3b3 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroSyncResult.java @@ -0,0 +1,35 @@ +package monero.wallet.model; + +/** + * Result from syncing a Monero wallet. + */ +public class MoneroSyncResult { + + private Long numBlocksFetched; + private Boolean receivedMoney; + + public MoneroSyncResult() { + this(null, null); + } + + public MoneroSyncResult(Long numBlocksFetched, Boolean receivedMoney) { + this.numBlocksFetched = numBlocksFetched; + this.receivedMoney = receivedMoney; + } + + public Long getNumBlocksFetched() { + return numBlocksFetched; + } + + public void setNumBlocksFetched(Long numBlocksFetched) { + this.numBlocksFetched = numBlocksFetched; + } + + public Boolean getReceivedMoney() { + return receivedMoney; + } + + public void setReceivedMoney(Boolean receivedMoney) { + this.receivedMoney = receivedMoney; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroTransfer.java b/core/src/main/java/monero/wallet/model/MoneroTransfer.java new file mode 100644 index 00000000000..f0bd3ea258f --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroTransfer.java @@ -0,0 +1,162 @@ +package monero.wallet.model; + +import java.math.BigInteger; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonProperty; + +import monero.utils.MoneroUtils; + +/** + * Models a base transfer of funds to or from the wallet. + * + * Transfers are either of type MoneroIncomingTransfer or MoneroOutgoingTransfer so this class is abstract. + */ +public abstract class MoneroTransfer { + + private MoneroTxWallet tx; + private BigInteger amount; + private Integer accountIndex; + private Long numSuggestedConfirmations; + + public MoneroTransfer() { + // nothing to initialize + } + + public MoneroTransfer(final MoneroTransfer transfer) { + this.amount = transfer.amount; + this.accountIndex = transfer.accountIndex; + this.numSuggestedConfirmations = transfer.numSuggestedConfirmations; + } + + public abstract MoneroTransfer copy(); + + @JsonBackReference + public MoneroTxWallet getTx() { + return tx; + } + + public MoneroTransfer setTx(MoneroTxWallet tx) { + this.tx = tx; + return this; + } + + @JsonProperty("isOutgoing") + public Boolean isOutgoing() { + return !isIncoming(); + } + + @JsonProperty("isIncoming") + public abstract Boolean isIncoming(); + + public BigInteger getAmount() { + return amount; + } + + public MoneroTransfer setAmount(BigInteger amount) { + this.amount = amount; + return this; + } + + public Integer getAccountIndex() { + return accountIndex; + } + + public MoneroTransfer setAccountIndex(Integer accountIndex) { + this.accountIndex = accountIndex; + return this; + } + + /** + * Return how many confirmations till it's not economically worth re-writing the chain. + * That is, the number of confirmations before the transaction is highly unlikely to be + * double spent or overwritten and may be considered settled, e.g. for a merchant to trust + * as finalized. + * + * @return Integer is the number of confirmations before it's not worth rewriting the chain + */ + public Long getNumSuggestedConfirmations() { + return numSuggestedConfirmations; + } + + public MoneroTransfer setNumSuggestedConfirmations(Long numSuggestedConfirmations) { + this.numSuggestedConfirmations = numSuggestedConfirmations; + return this; + } + + /** + * Updates this transaction by merging the latest information from the given + * transaction. + * + * Merging can modify or build references to the transfer given so it + * should not be re-used or it should be copied before calling this method. + * + * @param transfer is the transfer to merge into this one + */ + public MoneroTransfer merge(MoneroTransfer transfer) { + assert(transfer instanceof MoneroTransfer); + if (this == transfer) return this; + + // merge txs if they're different which comes back to merging transfers + if (this.getTx() != transfer.getTx()) { + this.getTx().merge(transfer.getTx()); + return this; + } + + // otherwise merge transfer fields + this.setAccountIndex(MoneroUtils.reconcile(this.getAccountIndex(), transfer.getAccountIndex())); + + // TODO monero core: failed tx in pool (after testUpdateLockedDifferentAccounts()) causes non-originating saved wallets to return duplicate incoming transfers but one has amount/numSuggestedConfirmations of 0 + if (this.getAmount() != null && transfer.getAmount() != null && !this.getAmount().equals(transfer.getAmount()) && (BigInteger.valueOf(0).equals(this.getAmount()) || BigInteger.valueOf(0).equals(transfer.getAmount()))) { + this.setAmount(MoneroUtils.reconcile(this.getAmount(), transfer.getAmount(), null, null, true)); + this.setNumSuggestedConfirmations(MoneroUtils.reconcile(this.getNumSuggestedConfirmations(), transfer.getNumSuggestedConfirmations(), null, null, true)); + System.out.println("WARNING: failed tx in pool causes non-originating wallets to return duplicate incoming transfers but with one amount/numSuggestedConfirmations of 0"); + } else { + this.setAmount(MoneroUtils.reconcile(this.getAmount(), transfer.getAmount())); + this.setNumSuggestedConfirmations(MoneroUtils.reconcile(this.getNumSuggestedConfirmations(), transfer.getNumSuggestedConfirmations(), null, null, false)); // TODO monero-wallet-rpc: outgoing txs become 0 when confirmed + } + + return this; + } + + public String toString() { + return toString(0); + } + + public String toString(int indent) { + StringBuilder sb = new StringBuilder(); + sb.append(MoneroUtils.kvLine("Amount", this.getAmount() != null ? this.getAmount().toString() : null, indent)); + sb.append(MoneroUtils.kvLine("Account index", this.getAccountIndex(), indent)); + sb.append(MoneroUtils.kvLine("Num suggested confirmations", getNumSuggestedConfirmations(), indent)); + String str = sb.toString(); + return str.isEmpty() ? str : str.substring(0, str.length() - 1); // strip last newline + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((accountIndex == null) ? 0 : accountIndex.hashCode()); + result = prime * result + ((amount == null) ? 0 : amount.hashCode()); + result = prime * result + ((numSuggestedConfirmations == null) ? 0 : numSuggestedConfirmations.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + MoneroTransfer other = (MoneroTransfer) obj; + if (accountIndex == null) { + if (other.accountIndex != null) return false; + } else if (!accountIndex.equals(other.accountIndex)) return false; + if (amount == null) { + if (other.amount != null) return false; + } else if (!amount.equals(other.amount)) return false; + if (numSuggestedConfirmations == null) { + if (other.numSuggestedConfirmations != null) return false; + } else if (!numSuggestedConfirmations.equals(other.numSuggestedConfirmations)) return false; + return true; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroTransferQuery.java b/core/src/main/java/monero/wallet/model/MoneroTransferQuery.java new file mode 100644 index 00000000000..4969fae9630 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroTransferQuery.java @@ -0,0 +1,226 @@ +package monero.wallet.model; + +import static org.junit.Assert.assertNotNull; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import common.types.Filter; +import common.utils.GenUtils; + +/** + * Configures a query to retrieve transfers. + * + * All transfers are returned except those that do not meet the criteria defined in this query. + */ +public class MoneroTransferQuery extends MoneroTransfer implements Filter { + + private Boolean isIncoming; + private String address; + private List addresses; + private Integer subaddressIndex; + private List subaddressIndices; + private List destinations; + private Boolean hasDestinations; + private MoneroTxQuery txQuery; + + public MoneroTransferQuery() { + + } + + public MoneroTransferQuery(final MoneroTransferQuery query) { + super(query); + this.isIncoming = query.isIncoming; + this.address = query.address; + if (query.addresses != null) this.addresses = new ArrayList(query.addresses); + this.subaddressIndex = query.subaddressIndex; + if (query.subaddressIndices != null) this.subaddressIndices = new ArrayList(query.subaddressIndices); + if (query.destinations != null) { + this.destinations = new ArrayList(); + for (MoneroDestination destination : query.getDestinations()) this.destinations.add(destination.copy()); + } + this.hasDestinations = query.hasDestinations; + this.txQuery = query.txQuery; // reference original by default, MoneroTxQuery's deep copy will set this to itself + } + + @Override + public MoneroTransferQuery copy() { + return new MoneroTransferQuery(this); + } + + public Boolean isIncoming() { + return isIncoming; + } + + public MoneroTransferQuery setIsIncoming(Boolean isIncoming) { + this.isIncoming = isIncoming; + return this; + } + + public Boolean isOutgoing() { + return isIncoming == null ? null : !isIncoming; + } + + public MoneroTransferQuery setIsOutgoing(Boolean isOutgoing) { + isIncoming = isOutgoing == null ? null : !isOutgoing; + return this; + } + + public String getAddress() { + return address; + } + + public MoneroTransferQuery setAddress(String address) { + this.address = address; + return this; + } + + public List getAddresses() { + return addresses; + } + + public MoneroTransferQuery setAddresses(List addresses) { + this.addresses = addresses; + return this; + } + + public MoneroTransferQuery setAddresses(String... addresses) { + this.addresses = GenUtils.arrayToList(addresses); + return this; + } + + public Integer getSubaddressIndex() { + return subaddressIndex; + } + + public MoneroTransferQuery setSubaddressIndex(Integer subaddressIndex) { + this.subaddressIndex = subaddressIndex; + return this; + } + + public List getSubaddressIndices() { + return subaddressIndices; + } + + public MoneroTransferQuery setSubaddressIndices(List subaddressIndices) { + this.subaddressIndices = subaddressIndices; + return this; + } + + public MoneroTransferQuery setSubaddressIndices(Integer... subaddressIndices) { + this.subaddressIndices = GenUtils.arrayToList(subaddressIndices); + return this; + } + + public List getDestinations() { + return destinations; + } + + public MoneroTransferQuery setDestinations(List destinations) { + this.destinations = destinations; + return this; + } + + @JsonProperty("hasDestinations") + public Boolean hasDestinations() { + return hasDestinations; + } + + public MoneroTransferQuery setHasDestinations(Boolean hasDestinations) { + this.hasDestinations = hasDestinations; + return this; + } + + @JsonIgnore + public MoneroTxQuery getTxQuery() { + return txQuery; + } + + public MoneroTransferQuery setTxQuery(MoneroTxQuery txQuery) { + this.txQuery = txQuery; + return this; + } + + @Override + public boolean meetsCriteria(MoneroTransfer transfer) { + assertNotNull("transfer is null", transfer); + if (txQuery != null && txQuery.getTransferQuery() != null) throw new RuntimeException("Transfer query's tx query cannot have a circular transfer query"); // TODO: could auto detect and handle this. port to js + + // filter on common fields + if (this.isIncoming() != null && this.isIncoming() != transfer.isIncoming()) return false; + if (this.isOutgoing() != null && this.isOutgoing() != transfer.isOutgoing()) return false; + if (this.getAmount() != null && this.getAmount().compareTo(transfer.getAmount()) != 0) return false; + if (this.getAccountIndex() != null && !this.getAccountIndex().equals(transfer.getAccountIndex())) return false; + + // filter on incoming fields + if (transfer instanceof MoneroIncomingTransfer) { + if (Boolean.TRUE.equals(this.hasDestinations())) return false; + MoneroIncomingTransfer inTransfer = (MoneroIncomingTransfer) transfer; + if (this.getAddress() != null && !this.getAddress().equals(inTransfer.getAddress())) return false; + if (this.getAddresses() != null && !this.getAddresses().contains(inTransfer.getAddress())) return false; + if (this.getSubaddressIndex() != null && !this.getSubaddressIndex().equals(inTransfer.getSubaddressIndex())) return false; + if (this.getSubaddressIndices() != null && !this.getSubaddressIndices().contains(inTransfer.getSubaddressIndex())) return false; + } + + // filter on outgoing fields + else if (transfer instanceof MoneroOutgoingTransfer) { + MoneroOutgoingTransfer outTransfer = (MoneroOutgoingTransfer) transfer; + + // filter on addresses + if (this.getAddress() != null && (outTransfer.getAddresses() == null || !outTransfer.getAddresses().contains(this.getAddress()))) return false; // TODO: will filter all transfers if they don't contain addresses + if (this.getAddresses() != null) { + List intersections = new ArrayList(this.getAddresses()); + intersections.retainAll(outTransfer.getAddresses()); + if (intersections.isEmpty()) return false; // must have overlapping addresses + } + + // filter on subaddress indices + if (this.getSubaddressIndex() != null && (outTransfer.getSubaddressIndices() == null || !outTransfer.getSubaddressIndices().contains(this.getSubaddressIndex()))) return false; + if (this.getSubaddressIndices() != null) { + List intersections = new ArrayList(this.getSubaddressIndices()); + intersections.retainAll(outTransfer.getSubaddressIndices()); + if (intersections.isEmpty()) return false; // must have overlapping subaddress indices + } + + // filter on having destinations + if (this.hasDestinations() != null) { + if (this.hasDestinations() && outTransfer.getDestinations() == null) return false; + if (!this.hasDestinations() && outTransfer.getDestinations() != null) return false; + } + + // filter on destinations TODO: start with test for this +// if (this.getDestionations() != null && this.getDestionations() != transfer.getDestionations()) return false; + } + + // otherwise invalid type + else throw new RuntimeException("Transfer must be MoneroIncomingTransfer or MoneroOutgoingTransfer"); + + // filter with tx filter + if (this.getTxQuery() != null && !this.getTxQuery().meetsCriteria(transfer.getTx())) return false; + return true; + } + + // ------------------- OVERRIDE CO-VARIANT RETURN TYPES --------------------- + + @Override + public MoneroTransferQuery setTx(MoneroTxWallet tx) { + super.setTx(tx); + return this; + } + + @Override + public MoneroTransferQuery setAmount(BigInteger amount) { + super.setAmount(amount); + return this; + } + + @Override + public MoneroTransferQuery setAccountIndex(Integer accountIndex) { + super.setAccountIndex(accountIndex); + return this; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroTxQuery.java b/core/src/main/java/monero/wallet/model/MoneroTxQuery.java new file mode 100644 index 00000000000..dd57d405e5d --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroTxQuery.java @@ -0,0 +1,496 @@ +package monero.wallet.model; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import common.types.Filter; +import common.utils.GenUtils; +import monero.daemon.model.MoneroBlock; +import monero.daemon.model.MoneroOutput; + +/** + * Configures a query to retrieve transactions. + * + * All transactions are returned except those that do not meet the criteria defined in this query. + */ +public class MoneroTxQuery extends MoneroTxWallet implements Filter { + + private Boolean isOutgoing; + private Boolean isIncoming; + private List txIds; + private Boolean hasPaymentId; + private List paymentIds; + private Long height; + private Long minHeight; + private Long maxHeight; + private Boolean includeOutputs; + private MoneroTransferQuery transferQuery; + private MoneroOutputQuery outputQuery; + + public MoneroTxQuery() { + + } + + public MoneroTxQuery(final MoneroTxQuery query) { + super(query); + this.isOutgoing = query.isOutgoing; + this.isIncoming = query.isIncoming; + if (query.txIds != null) this.txIds = new ArrayList(query.txIds); + this.hasPaymentId = query.hasPaymentId; + if (query.paymentIds != null) this.paymentIds = new ArrayList(query.paymentIds); + this.height = query.height; + this.minHeight = query.minHeight; + this.maxHeight = query.maxHeight; + this.includeOutputs = query.includeOutputs; + if (query.transferQuery != null) { + this.transferQuery = new MoneroTransferQuery(query.transferQuery); + if (query.transferQuery.getTxQuery() == query) this.transferQuery.setTxQuery(this); + } + if (query.outputQuery != null) { + this.outputQuery = new MoneroOutputQuery(query.outputQuery); + if (query.outputQuery.getTxQuery() == query) this.outputQuery.setTxQuery(this) ; + } + } + + public MoneroTxQuery copy() { + return new MoneroTxQuery(this); + } + + @JsonProperty("isOutgoing") + public Boolean isOutgoing() { + return isOutgoing; + } + + public MoneroTxQuery setIsOutgoing(Boolean isOutgoing) { + this.isOutgoing = isOutgoing; + return this; + } + + @JsonProperty("isIncoming") + public Boolean isIncoming() { + return isIncoming; + } + + public MoneroTxQuery setIsIncoming(Boolean isIncoming) { + this.isIncoming = isIncoming; + return this; + } + + public List getTxIds() { + return txIds; + } + + public MoneroTxQuery setTxIds(List txIds) { + this.txIds = txIds; + return this; + } + + public MoneroTxQuery setTxIds(String... txIds) { + this.txIds = GenUtils.arrayToList(txIds); + return this; + } + + public MoneroTxQuery setTxId(String txId) { + return setTxIds(Arrays.asList(txId)); + } + + @JsonProperty("hasPaymentId") + public Boolean hasPaymentId() { + return hasPaymentId; + } + + public MoneroTxQuery setHasPaymentId(Boolean hasPaymentId) { + this.hasPaymentId = hasPaymentId; + return this; + } + + public List getPaymentIds() { + return paymentIds; + } + + public MoneroTxQuery setPaymentIds(List paymentIds) { + this.paymentIds = paymentIds; + return this; + } + + public MoneroTxQuery setPaymentId(String paymentId) { + return setPaymentIds(Arrays.asList(paymentId)); + } + + public Long getHeight() { + return height; + } + + public MoneroTxQuery setHeight(Long height) { + this.height = height; + return this; + } + + public Long getMinHeight() { + return minHeight; + } + + public MoneroTxQuery setMinHeight(Long minHeight) { + this.minHeight = minHeight; + return this; + } + + public Long getMaxHeight() { + return maxHeight; + } + + public MoneroTxQuery setMaxHeight(Long maxHeight) { + this.maxHeight = maxHeight; + return this; + } + + public Boolean getIncludeOutputs() { + return includeOutputs; + } + + public MoneroTxQuery setIncludeOutputs(Boolean includeOutputs) { + this.includeOutputs = includeOutputs; + return this; + } + + public MoneroTransferQuery getTransferQuery() { + return transferQuery; + } + + public MoneroTxQuery setTransferQuery(MoneroTransferQuery transferQuery) { + this.transferQuery = transferQuery; + return this; + } + + public MoneroOutputQuery getOutputQuery() { + return outputQuery; + } + + public MoneroTxQuery setOutputQuery(MoneroOutputQuery outputQuery) { + this.outputQuery = outputQuery; + return this; + } + + @Override + public boolean meetsCriteria(MoneroTxWallet tx) { + if (tx == null) return false; + + // filter on tx + if (this.getId() != null && !this.getId().equals(tx.getId())) return false; + if (this.getPaymentId() != null && !this.getPaymentId().equals(tx.getPaymentId())) return false; + if (this.isConfirmed() != null && this.isConfirmed() != tx.isConfirmed()) return false; + if (this.inTxPool() != null && this.inTxPool() != tx.inTxPool()) return false; + if (this.getDoNotRelay() != null && this.getDoNotRelay() != tx.getDoNotRelay()) return false; + if (this.isRelayed() != null && this.isRelayed() != tx.isRelayed()) return false; + if (this.isFailed() != null && this.isFailed() != tx.isFailed()) return false; + if (this.isMinerTx() != null && this.isMinerTx() != tx.isMinerTx()) return false; + + // at least one transfer must meet transfer query if defined + if (this.getTransferQuery() != null) { + boolean matchFound = false; + if (tx.getOutgoingTransfer() != null && this.getTransferQuery().meetsCriteria(tx.getOutgoingTransfer())) matchFound = true; + else if (tx.getIncomingTransfers() != null) { + for (MoneroTransfer incomingTransfer : tx.getIncomingTransfers()) { + if (this.getTransferQuery().meetsCriteria(incomingTransfer)) { + matchFound = true; + break; + } + } + } + if (!matchFound) return false; + } + + // at least one output must meet output query if defined + if (this.getOutputQuery() != null && !this.getOutputQuery().isDefault()) { + if (tx.getVouts() == null || tx.getVouts().isEmpty()) return false; + boolean matchFound = false; + for (MoneroOutputWallet vout : tx.getVoutsWallet()) { + if (this.getOutputQuery().meetsCriteria(vout)) { + matchFound = true; + break; + } + } + if (!matchFound) return false; + } + + // filter on having a payment id + if (this.hasPaymentId() != null) { + if (this.hasPaymentId() && tx.getPaymentId() == null) return false; + if (!this.hasPaymentId() && tx.getPaymentId() != null) return false; + } + + // filter on incoming + if (this.isIncoming() != null) { + if (this.isIncoming() && !tx.isIncoming()) return false; + if (!this.isIncoming() && tx.isIncoming()) return false; + } + + // filter on outgoing + if (this.isOutgoing() != null) { + if (this.isOutgoing() && !tx.isOutgoing()) return false; + if (!this.isOutgoing() && tx.isOutgoing()) return false; + } + + // filter on remaining fields + Long txHeight = tx.getBlock() == null ? null : tx.getBlock().getHeight(); + if (this.getTxIds() != null && !this.getTxIds().contains(tx.getId())) return false; + if (this.getPaymentIds() != null && !this.getPaymentIds().contains(tx.getPaymentId())) return false; + if (this.getHeight() != null && !this.getHeight().equals(txHeight)) return false; + if (this.getMinHeight() != null && (txHeight == null || txHeight < this.getMinHeight())) return false; + if (this.getMaxHeight() != null && (txHeight == null || txHeight > this.getMaxHeight())) return false; + + // transaction meets query criteria + return true; + } + + @Override + public String toString() { + throw new RuntimeException("Not implemented"); + } + + // ------------------- OVERRIDE CO-VARIANT RETURN TYPES --------------------- + + @Override + public MoneroTxQuery setIncomingTransfers(List incomingTransfers) { + super.setIncomingTransfers(incomingTransfers); + return this; + } + + @Override + public MoneroTxQuery setOutgoingTransfer(MoneroOutgoingTransfer outgoingTransfer) { + super.setOutgoingTransfer(outgoingTransfer); + return this; + } + + @Override + public MoneroTxQuery setVouts(List vouts) { + super.setVouts(vouts); + return this; + } + + @Override + public MoneroTxQuery setNote(String note) { + super.setNote(note); + return this; + } + + @Override + public MoneroTxQuery setBlock(MoneroBlock block) { + super.setBlock(block); + return this; + } + + @Override + public MoneroTxQuery setId(String id) { + super.setId(id); + return this; + } + + @Override + public MoneroTxQuery setVersion(Integer version) { + super.setVersion(version); + return this; + } + + @Override + public MoneroTxQuery setIsMinerTx(Boolean isMinerTx) { + super.setIsMinerTx(isMinerTx); + return this; + } + + @Override + public MoneroTxQuery setFee(BigInteger fee) { + super.setFee(fee); + return this; + } + + @Override + public MoneroTxQuery setMixin(Integer mixin) { + super.setMixin(mixin); + return this; + } + + @Override + public MoneroTxQuery setDoNotRelay(Boolean doNotRelay) { + super.setDoNotRelay(doNotRelay); + return this; + } + + @Override + public MoneroTxQuery setIsRelayed(Boolean isRelayed) { + super.setIsRelayed(isRelayed); + return this; + } + + @Override + public MoneroTxQuery setIsConfirmed(Boolean isConfirmed) { + super.setIsConfirmed(isConfirmed); + return this; + } + + @Override + public MoneroTxQuery setInTxPool(Boolean inTxPool) { + super.setInTxPool(inTxPool); + return this; + } + + @Override + public MoneroTxQuery setNumConfirmations(Long numConfirmations) { + super.setNumConfirmations(numConfirmations); + return this; + } + + @Override + public MoneroTxQuery setUnlockTime(Long unlockTime) { + super.setUnlockTime(unlockTime); + return this; + } + + @Override + public MoneroTxQuery setLastRelayedTimestamp(Long lastRelayedTimestamp) { + super.setLastRelayedTimestamp(lastRelayedTimestamp); + return this; + } + + @Override + public MoneroTxQuery setReceivedTimestamp(Long receivedTimestamp) { + super.setReceivedTimestamp(receivedTimestamp); + return this; + } + + @Override + public MoneroTxQuery setIsDoubleSpendSeen(Boolean isDoubleSpend) { + super.setIsDoubleSpendSeen(isDoubleSpend); + return this; + } + + @Override + public MoneroTxQuery setKey(String key) { + super.setKey(key); + return this; + } + + @Override + public MoneroTxQuery setFullHex(String hex) { + super.setFullHex(hex); + return this; + } + + @Override + public MoneroTxQuery setPrunedHex(String prunedHex) { + super.setPrunedHex(prunedHex); + return this; + } + + @Override + public MoneroTxQuery setPrunableHex(String prunableHex) { + super.setPrunableHex(prunableHex); + return this; + } + + @Override + public MoneroTxQuery setPrunableHash(String prunableHash) { + super.setPrunableHash(prunableHash); + return this; + } + + @Override + public MoneroTxQuery setSize(Long size) { + super.setSize(size); + return this; + } + + @Override + public MoneroTxQuery setWeight(Long weight) { + super.setWeight(weight); + return this; + } + + @Override + public MoneroTxQuery setVins(List vins) { + super.setVins(vins); + return this; + } + + @Override + public MoneroTxQuery setOutputIndices(List outputIndices) { + super.setOutputIndices(outputIndices); + return this; + } + + @Override + public MoneroTxQuery setMetadata(String metadata) { + super.setMetadata(metadata); + return this; + } + + @Override + public MoneroTxQuery setTxSet(MoneroTxSet commonTxSets) { + super.setTxSet(commonTxSets); + return this; + } + + @Override + public MoneroTxQuery setExtra(int[] extra) { + super.setExtra(extra); + return this; + } + + @Override + public MoneroTxQuery setRctSignatures(Object rctSignatures) { + super.setRctSignatures(rctSignatures); + return this; + } + + @Override + public MoneroTxQuery setRctSigPrunable(Object rctSigPrunable) { + super.setRctSigPrunable(rctSigPrunable); + return this; + } + + @Override + public MoneroTxQuery setIsKeptByBlock(Boolean isKeptByBlock) { + super.setIsKeptByBlock(isKeptByBlock); + return this; + } + + @Override + public MoneroTxQuery setIsFailed(Boolean isFailed) { + super.setIsFailed(isFailed); + return this; + } + + @Override + public MoneroTxQuery setLastFailedHeight(Long lastFailedHeight) { + super.setLastFailedHeight(lastFailedHeight); + return this; + } + + @Override + public MoneroTxQuery setLastFailedId(String lastFailedId) { + super.setLastFailedId(lastFailedId); + return this; + } + + @Override + public MoneroTxQuery setMaxUsedBlockHeight(Long maxUsedBlockHeight) { + super.setMaxUsedBlockHeight(maxUsedBlockHeight); + return this; + } + + @Override + public MoneroTxQuery setMaxUsedBlockId(String maxUsedBlockId) { + super.setMaxUsedBlockId(maxUsedBlockId); + return this; + } + + @Override + public MoneroTxQuery setSignatures(List signatures) { + super.setSignatures(signatures); + return this; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroTxSet.java b/core/src/main/java/monero/wallet/model/MoneroTxSet.java new file mode 100644 index 00000000000..2e69077394a --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroTxSet.java @@ -0,0 +1,140 @@ +package monero.wallet.model; + +import static org.junit.Assert.assertNotNull; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.fasterxml.jackson.annotation.JsonProperty; + +import common.utils.GenUtils; +import monero.daemon.model.MoneroTx; +import monero.utils.MoneroUtils; + +/** + * Groups transactions who share common hex data which is needed in order to + * sign and submit the transactions. + * + * For example, multisig transactions created from sendSplit() share a common + * hex string which is needed in order to sign and submit the multisig + * transactions. + */ +public class MoneroTxSet { + + private List txs; + private String multisigTxHex; + private String unsignedTxHex; + private String signedTxHex; + + @JsonManagedReference("tx_set") + public List getTxs() { + return txs; + } + + @JsonProperty("txs") + public MoneroTxSet setTxs(List txs) { + this.txs = txs; + return this; + } + + @JsonIgnore + public MoneroTxSet setTxs(MoneroTxWallet... txs) { + this.txs = GenUtils.arrayToList(txs); + return this; + } + + public String getMultisigTxHex() { + return multisigTxHex; + } + + public MoneroTxSet setMultisigTxHex(String multisigTxHex) { + this.multisigTxHex = multisigTxHex; + return this; + } + + public String getUnsignedTxHex() { + return unsignedTxHex; + } + + public MoneroTxSet setUnsignedTxHex(String unsignedTxHex) { + this.unsignedTxHex = unsignedTxHex; + return this; + } + + public String getSignedTxHex() { + return signedTxHex; + } + + public MoneroTxSet setSignedTxHex(String signedTxHex) { + this.signedTxHex = signedTxHex; + return this; + } + + public MoneroTxSet merge(MoneroTxSet txSet) { + assertNotNull(txSet); + if (this == txSet) return this; + + // merge sets + this.setMultisigTxHex(MoneroUtils.reconcile(this.getMultisigTxHex(), txSet.getMultisigTxHex())); + this.setUnsignedTxHex(MoneroUtils.reconcile(this.getUnsignedTxHex(), txSet.getUnsignedTxHex())); + this.setSignedTxHex(MoneroUtils.reconcile(this.getSignedTxHex(), txSet.getSignedTxHex())); + + // merge txs + if (txSet.getTxs() != null) { + for (MoneroTxWallet tx : txSet.getTxs()) { + tx.setTxSet(this); + MoneroUtils.mergeTx(txs, tx); + } + } + + return this; + } + + @Override + public String toString() { + return toString(0); + } + + public String toString(int indent) { + StringBuilder sb = new StringBuilder(); + sb.append(MoneroUtils.kvLine("Multisig tx hex: ", getMultisigTxHex(), indent)); + sb.append(MoneroUtils.kvLine("Unsigned tx hex: ", getUnsignedTxHex(), indent)); + sb.append(MoneroUtils.kvLine("Signed tx hex: ", getSignedTxHex(), indent)); + if (getTxs() != null) { + sb.append(MoneroUtils.kvLine("Txs", "", indent)); + for (MoneroTx tx : getTxs()) { + sb.append(tx.toString(indent + 1) + "\n"); + } + } + return sb.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((multisigTxHex == null) ? 0 : multisigTxHex.hashCode()); + result = prime * result + ((signedTxHex == null) ? 0 : signedTxHex.hashCode()); + result = prime * result + ((unsignedTxHex == null) ? 0 : unsignedTxHex.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + MoneroTxSet other = (MoneroTxSet) obj; + if (multisigTxHex == null) { + if (other.multisigTxHex != null) return false; + } else if (!multisigTxHex.equals(other.multisigTxHex)) return false; + if (signedTxHex == null) { + if (other.signedTxHex != null) return false; + } else if (!signedTxHex.equals(other.signedTxHex)) return false; + if (unsignedTxHex == null) { + if (other.unsignedTxHex != null) return false; + } else if (!unsignedTxHex.equals(other.unsignedTxHex)) return false; + return true; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroTxWallet.java b/core/src/main/java/monero/wallet/model/MoneroTxWallet.java new file mode 100644 index 00000000000..935ac208822 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroTxWallet.java @@ -0,0 +1,514 @@ +package monero.wallet.model; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.fasterxml.jackson.annotation.JsonProperty; + +import monero.daemon.model.MoneroBlock; +import monero.daemon.model.MoneroOutput; +import monero.daemon.model.MoneroTx; +import monero.utils.MoneroException; +import monero.utils.MoneroUtils; + +/** + * Models a Monero transaction with wallet extensions. + */ +public class MoneroTxWallet extends MoneroTx { + + private MoneroTxSet txSet; + private List incomingTransfers; + private MoneroOutgoingTransfer outgoingTransfer; + private String note; + + public MoneroTxWallet() { + // nothing to initialize + } + + public MoneroTxWallet(final MoneroTxWallet tx) { + super(tx); + this.txSet = tx.txSet; + if (tx.incomingTransfers != null) { + this.incomingTransfers = new ArrayList(); + for (MoneroIncomingTransfer transfer : tx.incomingTransfers) { + this.incomingTransfers.add(transfer.copy().setTx(this)); + } + } + if (tx.outgoingTransfer != null) this.outgoingTransfer = tx.outgoingTransfer.copy().setTx(this); + this.note = tx.note; + } + + public MoneroTxWallet copy() { + return new MoneroTxWallet(this); + } + + @JsonBackReference("tx_set") + public MoneroTxSet getTxSet() { + return txSet; + } + + public MoneroTxWallet setTxSet(MoneroTxSet txSet) { + this.txSet = txSet; + return this; + } + + @JsonProperty("isOutgoing") + public Boolean isOutgoing() { + return getOutgoingTransfer() != null; + } + + @JsonProperty("isIncoming") + public Boolean isIncoming() { + return getIncomingTransfers() != null && !getIncomingTransfers().isEmpty(); + } + + public BigInteger getIncomingAmount() { + if (getIncomingTransfers() == null) return null; + BigInteger incomingAmt = BigInteger.valueOf(0); + for (MoneroTransfer transfer : this.getIncomingTransfers()) incomingAmt = incomingAmt.add(transfer.getAmount()); + return incomingAmt; + } + + public BigInteger getOutgoingAmount() { + return getOutgoingTransfer() != null ? getOutgoingTransfer().getAmount() : null; + } + + @JsonManagedReference + public List getIncomingTransfers() { + return incomingTransfers; + } + + public MoneroTxWallet setIncomingTransfers(List incomingTransfers) { + this.incomingTransfers = incomingTransfers; + return this; + } + + @JsonManagedReference + public MoneroOutgoingTransfer getOutgoingTransfer() { + return outgoingTransfer; + } + + public MoneroTxWallet setOutgoingTransfer(MoneroOutgoingTransfer outgoingTransfer) { + this.outgoingTransfer = outgoingTransfer; + return this; + } + + /** + * Returns a copy of this model's vouts as a list of type MoneroOutputWallet. + * + * @return vouts of type MoneroOutputWallet + */ + public List getVoutsWallet() { + List vouts = getVouts(); + if (vouts == null) return null; + List voutsWallet = new ArrayList(); + for (MoneroOutput vout : getVouts()) { + voutsWallet.add((MoneroOutputWallet) vout); + } + return voutsWallet; + } + + /** + * Set the tx's vouts (MoneroOutputWallet) which contain information relative + * to a wallet. + * + * Callers must cast to extended type (MoneroOutput) because Java + * paramaterized types do not recognize inheritance. + * + * @param vouts are MoneroOutputWallets to set for the wallet tx + * @return MoneroTxWallet is a reference to this tx for chaining + */ + public MoneroTxWallet setVouts(List vouts) { + + // validate that all vouts are wallet outputs + if (vouts != null) { + for (MoneroOutput vout : vouts) { + if (!(vout instanceof MoneroOutputWallet)) throw new MoneroException("Wallet transaction vouts must be of type MoneroOutputWallet"); + } + } + super.setVouts(vouts); + return this; + } + + /** + * Set vouts with compile-time binding to MoneroOutputWallet for deserialization. + * + * @param outputs are the tx's vouts + * @return MoneroTxWallet is a reference to this tx for chaining + */ + @JsonProperty("vouts") + public MoneroTxWallet setVoutsWallet(List outputs) { + return setVouts(new ArrayList(outputs)); + } + + public String getNote() { + return note; + } + + public MoneroTxWallet setNote(String note) { + this.note = note; + return this; + } + + public MoneroTxWallet merge(MoneroTx tx) { + if (tx != null && !(tx instanceof MoneroTxWallet)) throw new MoneroException("Wallet transaction must be merged with type MoneroTxWallet"); + return merge((MoneroTxWallet) tx); + } + + /** + * Updates this transaction by merging the latest information from the given + * transaction. + * + * Merging can modify or build references to the transaction given so it + * should not be re-used or it should be copied before calling this method. + * + * @param tx is the transaction to merge into this transaction + * @return this tx for chaining + */ + public MoneroTxWallet merge(MoneroTxWallet tx) { + if (!(tx instanceof MoneroTxWallet)) throw new MoneroException("Wallet transaction must be merged with type MoneroTxWallet"); + if (this == tx) return this; + + // merge base classes + super.merge(tx); + + // merge tx set if they're different which comes back to merging txs + if (txSet != tx.getTxSet()) { + if (txSet == null) { + txSet = new MoneroTxSet(); + txSet.setTxs(this); + } + if (tx.getTxSet() == null) { + tx.setTxSet(new MoneroTxSet()); + tx.getTxSet().setTxs(tx); + } + txSet.merge(tx.getTxSet()); + return this; + } + + // merge incoming transfers + if (tx.getIncomingTransfers() != null) { + if (this.getIncomingTransfers() == null) this.setIncomingTransfers(new ArrayList()); + for (MoneroIncomingTransfer transfer : tx.getIncomingTransfers()) { + transfer.setTx(this); + mergeIncomingTransfer(this.getIncomingTransfers(), transfer); + } + } + + // merge outgoing transfer + if (tx.getOutgoingTransfer() != null) { + tx.getOutgoingTransfer().setTx(this); + if (this.getOutgoingTransfer() == null) this.setOutgoingTransfer(tx.getOutgoingTransfer()); + else this.getOutgoingTransfer().merge(tx.getOutgoingTransfer()); + } + + // merge simple extensions + this.setNote(MoneroUtils.reconcile(this.getNote(), tx.getNote())); + + return this; // for chaining + } + + public String toString() { + return toString(0, false); + } + + public String toString(int indent) { + return toString(indent, false); + } + + public String toString(int indent, boolean oneLine) { + StringBuilder sb = new StringBuilder(); + + // represent tx with one line string + // TODO: proper csv export + if (oneLine) { + sb.append(this.getId() + ", "); + sb.append((this.isConfirmed() ? this.getBlock().getTimestamp() : this.getReceivedTimestamp()) + ", "); + sb.append(this.isConfirmed() + ", "); + sb.append((this.getOutgoingAmount() != null? this.getOutgoingAmount().toString() : "") + ", "); + sb.append(this.getIncomingAmount() != null ? this.getIncomingAmount().toString() : ""); + return sb.toString(); + } + + // otherwise stringify all fields + sb.append(super.toString(indent) + "\n"); + sb.append(MoneroUtils.kvLine("Is incoming", this.isIncoming(), indent)); + sb.append(MoneroUtils.kvLine("Incoming amount", this.getIncomingAmount(), indent)); + if (this.getIncomingTransfers() != null) { + sb.append(MoneroUtils.kvLine("Incoming transfers", "", indent)); + for (int i = 0; i < this.getIncomingTransfers().size(); i++) { + sb.append(MoneroUtils.kvLine(i + 1, "", indent + 1)); + sb.append(this.getIncomingTransfers().get(i).toString(indent + 2) + "\n"); + } + } + sb.append(MoneroUtils.kvLine("Is outgoing", this.isOutgoing(), indent)); + sb.append(MoneroUtils.kvLine("Outgoing amount", this.getOutgoingAmount(), indent)); + if (this.getOutgoingTransfer() != null) { + sb.append(MoneroUtils.kvLine("Outgoing transfer", "", indent)); + sb.append(this.getOutgoingTransfer().toString(indent + 1) + "\n"); + } + sb.append(MoneroUtils.kvLine("Note: ", this.getNote(), indent)); + String str = sb.toString(); + return str.substring(0, str.length() - 1); // strip last newline + } + + // private helper to merge transfers + private static void mergeIncomingTransfer(List transfers, MoneroIncomingTransfer transfer) { + for (MoneroIncomingTransfer aTransfer : transfers) { + if (aTransfer.getAccountIndex() == transfer.getAccountIndex() && aTransfer.getSubaddressIndex() == transfer.getSubaddressIndex()) { + aTransfer.merge(transfer); + return; + } + } + transfers.add(transfer); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((incomingTransfers == null) ? 0 : incomingTransfers.hashCode()); + result = prime * result + ((note == null) ? 0 : note.hashCode()); + result = prime * result + ((outgoingTransfer == null) ? 0 : outgoingTransfer.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!super.equals(obj)) return false; + if (getClass() != obj.getClass()) return false; + MoneroTxWallet other = (MoneroTxWallet) obj; + if (incomingTransfers == null) { + if (other.incomingTransfers != null) return false; + } else if (!incomingTransfers.equals(other.incomingTransfers)) return false; + if (note == null) { + if (other.note != null) return false; + } else if (!note.equals(other.note)) return false; + if (outgoingTransfer == null) { + if (other.outgoingTransfer != null) return false; + } else if (!outgoingTransfer.equals(other.outgoingTransfer)) return false; + return true; + } + + // ------------------- OVERRIDE CO-VARIANT RETURN TYPES --------------------- + + @Override + public MoneroTxWallet setBlock(MoneroBlock block) { + super.setBlock(block); + return this; + } + + @Override + public MoneroTxWallet setId(String id) { + super.setId(id); + return this; + } + + @Override + public MoneroTxWallet setVersion(Integer version) { + super.setVersion(version); + return this; + } + + @Override + public MoneroTxWallet setIsMinerTx(Boolean isMinerTx) { + super.setIsMinerTx(isMinerTx); + return this; + } + + @Override + public MoneroTxWallet setPaymentId(String paymentId) { + super.setPaymentId(paymentId); + return this; + } + + @Override + public MoneroTxWallet setFee(BigInteger fee) { + super.setFee(fee); + return this; + } + + @Override + public MoneroTxWallet setMixin(Integer mixin) { + super.setMixin(mixin); + return this; + } + + @Override + public MoneroTxWallet setDoNotRelay(Boolean doNotRelay) { + super.setDoNotRelay(doNotRelay); + return this; + } + + @Override + public MoneroTxWallet setIsRelayed(Boolean isRelayed) { + super.setIsRelayed(isRelayed); + return this; + } + + @Override + public MoneroTxWallet setIsConfirmed(Boolean isConfirmed) { + super.setIsConfirmed(isConfirmed); + return this; + } + + @Override + public MoneroTxWallet setInTxPool(Boolean inTxPool) { + super.setInTxPool(inTxPool); + return this; + } + + @Override + public MoneroTxWallet setNumConfirmations(Long numConfirmations) { + super.setNumConfirmations(numConfirmations); + return this; + } + + @Override + public MoneroTxWallet setUnlockTime(Long unlockTime) { + super.setUnlockTime(unlockTime); + return this; + } + + @Override + public MoneroTxWallet setLastRelayedTimestamp(Long lastRelayedTimestamp) { + super.setLastRelayedTimestamp(lastRelayedTimestamp); + return this; + } + + @Override + public MoneroTxWallet setReceivedTimestamp(Long receivedTimestamp) { + super.setReceivedTimestamp(receivedTimestamp); + return this; + } + + @Override + public MoneroTxWallet setIsDoubleSpendSeen(Boolean isDoubleSpend) { + super.setIsDoubleSpendSeen(isDoubleSpend); + return this; + } + + @Override + public MoneroTxWallet setKey(String key) { + super.setKey(key); + return this; + } + + @Override + public MoneroTxWallet setFullHex(String hex) { + super.setFullHex(hex); + return this; + } + + @Override + public MoneroTxWallet setPrunedHex(String prunedHex) { + super.setPrunedHex(prunedHex); + return this; + } + + @Override + public MoneroTxWallet setPrunableHex(String prunableHex) { + super.setPrunableHex(prunableHex); + return this; + } + + @Override + public MoneroTxWallet setPrunableHash(String prunableHash) { + super.setPrunableHash(prunableHash); + return this; + } + + @Override + public MoneroTxWallet setSize(Long size) { + super.setSize(size); + return this; + } + + @Override + public MoneroTxWallet setWeight(Long weight) { + super.setWeight(weight); + return this; + } + + @Override + public MoneroTxWallet setVins(List vins) { + super.setVins(vins); + return this; + } + + @Override + public MoneroTxWallet setOutputIndices(List outputIndices) { + super.setOutputIndices(outputIndices); + return this; + } + + @Override + public MoneroTxWallet setMetadata(String metadata) { + super.setMetadata(metadata); + return this; + } + + @Override + public MoneroTxWallet setExtra(int[] extra) { + super.setExtra(extra); + return this; + } + + @Override + public MoneroTxWallet setRctSignatures(Object rctSignatures) { + super.setRctSignatures(rctSignatures); + return this; + } + + @Override + public MoneroTxWallet setRctSigPrunable(Object rctSigPrunable) { + super.setRctSigPrunable(rctSigPrunable); + return this; + } + + @Override + public MoneroTxWallet setIsKeptByBlock(Boolean isKeptByBlock) { + super.setIsKeptByBlock(isKeptByBlock); + return this; + } + + @Override + public MoneroTxWallet setIsFailed(Boolean isFailed) { + super.setIsFailed(isFailed); + return this; + } + + @Override + public MoneroTxWallet setLastFailedHeight(Long lastFailedHeight) { + super.setLastFailedHeight(lastFailedHeight); + return this; + } + + @Override + public MoneroTxWallet setLastFailedId(String lastFailedId) { + super.setLastFailedId(lastFailedId); + return this; + } + + @Override + public MoneroTxWallet setMaxUsedBlockHeight(Long maxUsedBlockHeight) { + super.setMaxUsedBlockHeight(maxUsedBlockHeight); + return this; + } + + @Override + public MoneroTxWallet setMaxUsedBlockId(String maxUsedBlockId) { + super.setMaxUsedBlockId(maxUsedBlockId); + return this; + } + + @Override + public MoneroTxWallet setSignatures(List signatures) { + super.setSignatures(signatures); + return this; + } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroWalletListener.java b/core/src/main/java/monero/wallet/model/MoneroWalletListener.java new file mode 100644 index 00000000000..3935f5fe818 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroWalletListener.java @@ -0,0 +1,19 @@ +package monero.wallet.model; + +/** + * Provides default handling for wallet notifications. + */ +public class MoneroWalletListener implements MoneroWalletListenerI { + + @Override + public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { } + + @Override + public void onNewBlock(long height) { } + + @Override + public void onOutputReceived(MoneroOutputWallet output) { } + + @Override + public void onOutputSpent(MoneroOutputWallet output) { } +} diff --git a/core/src/main/java/monero/wallet/model/MoneroWalletListenerI.java b/core/src/main/java/monero/wallet/model/MoneroWalletListenerI.java new file mode 100644 index 00000000000..bb8470f36e6 --- /dev/null +++ b/core/src/main/java/monero/wallet/model/MoneroWalletListenerI.java @@ -0,0 +1,28 @@ +package monero.wallet.model; + +/** + * Interface to receive wallet notifications. + */ +public interface MoneroWalletListenerI extends MoneroSyncListener { + + /** + * Invoked when a new block is added to the chain. + * + * @param height is the height of the block added to the chain + */ + public void onNewBlock(long height); + + /** + * Invoked when the wallet receives an output. + * + * @param output is the incoming output to the wallet + */ + public void onOutputReceived(MoneroOutputWallet output); + + /** + * Invoked when the wallet spends an output. + * + * @param output the outgoing transfer from the wallet + */ + public void onOutputSpent(MoneroOutputWallet output); +} \ No newline at end of file From ce2e327dfcfb726852201fec1a557c3e1b5f1878 Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Wed, 18 Sep 2019 00:54:47 +0100 Subject: [PATCH 14/22] Removed references to view component in core component. --- build.gradle | 5 +- .../core/xmr/wallet/XmrWalletRpcWrapper.java | 67 ++----------------- ...nceListener.java => WalletUiListener.java} | 2 +- .../content/wallet/monero/XmrBalanceUtil.java | 4 +- .../wallet/monero/receive/XmrReceiveView.java | 4 +- .../wallet/monero/send/XmrSendView.java | 4 +- .../content/wallet/monero/tx/XmrTxView.java | 4 +- 7 files changed, 19 insertions(+), 71 deletions(-) rename core/src/main/java/bisq/core/xmr/wallet/listeners/{WalletBalanceListener.java => WalletUiListener.java} (96%) diff --git a/build.gradle b/build.gradle index c79126f985b..5731e6e19c5 100644 --- a/build.gradle +++ b/build.gradle @@ -69,7 +69,6 @@ configure(subprojects) { reactfxVersion = '2.0-M3' sarxosVersion = '0.3.12' slf4jVersion = '1.7.22' - log4jVersion = '1.2.17' sparkVersion = '2.5.2' springBootVersion = '1.5.10.RELEASE' springVersion = '4.3.6.RELEASE' @@ -157,6 +156,8 @@ configure(project(':assets')) { compile "com.google.guava:guava:$guavaVersion" compile "org.slf4j:slf4j-api:$slf4jVersion" compile "org.apache.commons:commons-lang3:$langVersion" + compile "junit:junit:$junitVersion" + compile "log4j:log4j:1.2.17" } } @@ -174,6 +175,8 @@ configure(project(':common')) { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" + compile "junit:junit:$junitVersion" + compile "log4j:log4j:1.2.17" compile "org.openjfx:javafx-base:11:$os" compile "org.openjfx:javafx-graphics:11:$os" compile "com.google.protobuf:protobuf-java:$protobufVersion" diff --git a/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java b/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java index ac633564677..b6c47a0f025 100644 --- a/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java +++ b/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java @@ -27,12 +27,10 @@ import bisq.common.app.DevEnv; import bisq.core.locale.Res; import bisq.core.user.Preferences; -import bisq.core.xmr.wallet.listeners.WalletBalanceListener; +import bisq.core.xmr.wallet.listeners.WalletUiListener; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import javafx.scene.control.ComboBox; -import javafx.scene.control.TextArea; import lombok.extern.slf4j.Slf4j; import monero.rpc.MoneroRpcConnection; import monero.wallet.MoneroWalletRpc; @@ -84,7 +82,7 @@ public XmrWalletRpcWrapper(Preferences preferences) { } } - public void update(WalletBalanceListener listener, HashMap walletRpcData) { + public void update(WalletUiListener listener, HashMap walletRpcData) { Runnable command = new Runnable() { @Override @@ -149,7 +147,7 @@ public boolean test(MoneroTxWallet t) { return list.size() > 100 ? list.subList(0, 100) : list;//Reduce transactions to no more than 100. } - public void searchTx(WalletBalanceListener listener, String commaSeparatedIds) { + public void searchTx(WalletUiListener listener, String commaSeparatedIds) { Runnable command = new Runnable() { @Override @@ -177,7 +175,7 @@ public void run() { } } - public void createTx(WalletBalanceListener listener, Integer accountIndex, String address, + public void createTx(WalletUiListener listener, Integer accountIndex, String address, BigInteger amount, MoneroSendPriority priority, boolean doNotRelay, HashMap walletRpcData) { Runnable command = new Runnable() { @@ -226,7 +224,7 @@ public void run() { } } - public void relayTx(WalletBalanceListener listener, HashMap walletRpcData) { + public void relayTx(WalletUiListener listener, HashMap walletRpcData) { Runnable command = new Runnable() { @Override @@ -252,27 +250,8 @@ public void run() { listener.popupErrorWindow(Res.get("shared.account.wallet.popup.error.startupFailed")); } } - - public void fetchLanguages(WalletBalanceListener listener, ComboBox languageComboBox) { - log.debug("createWalletRpcInstance - {}, {}"); - ObservableList languageOptions = FXCollections.observableArrayList(); - Runnable command = new Runnable() { - - @Override - public void run() { - checkNotNull(walletRpc, Res.get("mainView.networkWarning.localhostLost", "Monero")); - languageOptions.addAll(walletRpc.getLanguages()); - languageComboBox.setItems(languageOptions); - } - }; - try { - Platform.runLater(command); - } catch (Exception e) { - listener.popupErrorWindow(Res.get("shared.account.wallet.popup.error.startupFailed")); - } - } - public void openWalletRpcInstance(WalletBalanceListener listener) { + public void openWalletRpcInstance(WalletUiListener listener) { log.debug("openWalletRpcInstance - {}, {}", HOST, PORT); Thread checkIfXmrLocalHostNodeIsRunningThread = new Thread(() -> { Thread.currentThread().setName("checkIfXmrLocalHostNodeIsRunningThread"); @@ -306,40 +285,6 @@ public void openWalletRpcInstance(WalletBalanceListener listener) { public boolean isXmrWalletRpcRunning() { return xmrWalletRpcRunning; } - - public void createWalletRpcInstance(WalletBalanceListener listener, String walletFile, String password, String language, TextArea seedWordsTextArea) { - log.debug("createWalletRpcInstance - {}, {}, {}, {}", HOST, PORT, walletFile, password); - Thread checkIfXmrLocalHostNodeIsRunningThread = new Thread(() -> { - Thread.currentThread().setName("checkIfXmrLocalHostNodeIsRunningThread"); - Socket socket = null; - try { - socket = new Socket(); - socket.connect(new InetSocketAddress(InetAddresses.forString(HOST), PORT), 5000); - log.debug("Localhost Monero Wallet RPC detected."); - UserThread.execute(() -> { - xmrWalletRpcRunning = true; - walletRpc = new MoneroWalletRpc(new MoneroRpcConnection("http://" + HOST + ":" + PORT)); - walletRpc.createWalletRandom(walletFile, password, language); - String mnemonic = walletRpc.getMnemonic(); - seedWordsTextArea.setText(mnemonic); - }); - } catch (Throwable e) { - log.debug("createWalletRpcInstance - {}", e.getMessage()); - e.printStackTrace(); - if(listener != null) { - listener.popupErrorWindow(Res.get("shared.account.wallet.popup.error.startupFailed", "Monero", e.getLocalizedMessage())); - } - } finally { - if (socket != null) { - try { - socket.close(); - } catch (IOException ignore) { - } - } - } - }); - checkIfXmrLocalHostNodeIsRunningThread.start(); - } public void handleTxProof(TxProofHandler handler, String txId, String message) { Runnable command = new Runnable() { diff --git a/core/src/main/java/bisq/core/xmr/wallet/listeners/WalletBalanceListener.java b/core/src/main/java/bisq/core/xmr/wallet/listeners/WalletUiListener.java similarity index 96% rename from core/src/main/java/bisq/core/xmr/wallet/listeners/WalletBalanceListener.java rename to core/src/main/java/bisq/core/xmr/wallet/listeners/WalletUiListener.java index b378f44b297..fba4ed352ff 100644 --- a/core/src/main/java/bisq/core/xmr/wallet/listeners/WalletBalanceListener.java +++ b/core/src/main/java/bisq/core/xmr/wallet/listeners/WalletUiListener.java @@ -19,7 +19,7 @@ import java.util.HashMap; -public interface WalletBalanceListener { +public interface WalletUiListener { /** * * @param walletRpcData diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrBalanceUtil.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrBalanceUtil.java index 8d854721d35..73345a268f9 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrBalanceUtil.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/XmrBalanceUtil.java @@ -14,14 +14,14 @@ import bisq.core.locale.Res; import bisq.core.xmr.XmrFormatter; import bisq.core.xmr.wallet.XmrWalletRpcWrapper; -import bisq.core.xmr.wallet.listeners.WalletBalanceListener; +import bisq.core.xmr.wallet.listeners.WalletUiListener; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.util.FormBuilder; import bisq.desktop.util.Layout; import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; -public class XmrBalanceUtil implements WalletBalanceListener { +public class XmrBalanceUtil implements WalletUiListener { // Displaying general XMR info private TextField actualBalanceTextField, unlockedBalanceTextField; private XmrWalletRpcWrapper walletWrapper; diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/receive/XmrReceiveView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/receive/XmrReceiveView.java index d9426f83fff..a9e2f8dfa18 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/receive/XmrReceiveView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/receive/XmrReceiveView.java @@ -28,7 +28,7 @@ import bisq.common.util.Tuple3; import bisq.core.locale.Res; import bisq.core.xmr.wallet.XmrWalletRpcWrapper; -import bisq.core.xmr.wallet.listeners.WalletBalanceListener; +import bisq.core.xmr.wallet.listeners.WalletUiListener; import bisq.desktop.common.view.ActivatableView; import bisq.desktop.common.view.FxmlView; import bisq.desktop.components.BusyAnimation; @@ -43,7 +43,7 @@ import javafx.scene.layout.VBox; @FxmlView -public class XmrReceiveView extends ActivatableView implements WalletBalanceListener { +public class XmrReceiveView extends ActivatableView implements WalletUiListener { private WalletAddressTextField addressTextField; private final XmrWalletRpcWrapper walletWrapper; diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java index c51d1ae49dd..6e1119e5194 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java @@ -36,7 +36,7 @@ import bisq.core.locale.Res; import bisq.core.xmr.XmrFormatter; import bisq.core.xmr.wallet.XmrWalletRpcWrapper; -import bisq.core.xmr.wallet.listeners.WalletBalanceListener; +import bisq.core.xmr.wallet.listeners.WalletUiListener; import bisq.desktop.Navigation; import bisq.desktop.common.view.ActivatableView; import bisq.desktop.common.view.FxmlView; @@ -64,7 +64,7 @@ import monero.wallet.model.MoneroTxWallet; @FxmlView -public class XmrSendView extends ActivatableView implements WalletBalanceListener { +public class XmrSendView extends ActivatableView implements WalletUiListener { private final XmrWalletRpcWrapper walletWrapper; private final XmrFormatter xmrFormatter; private final Navigation navigation; diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java index af75504d99d..58228a68815 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java @@ -28,7 +28,7 @@ import bisq.core.xmr.XmrFormatter; import bisq.core.xmr.wallet.XmrTxListItem; import bisq.core.xmr.wallet.XmrWalletRpcWrapper; -import bisq.core.xmr.wallet.listeners.WalletBalanceListener; +import bisq.core.xmr.wallet.listeners.WalletUiListener; import bisq.desktop.common.view.ActivatableView; import bisq.desktop.common.view.FxmlView; import bisq.desktop.components.AutoTooltipTableColumn; @@ -59,7 +59,7 @@ import monero.wallet.model.MoneroTxWallet; @FxmlView -public class XmrTxView extends ActivatableView implements WalletBalanceListener { +public class XmrTxView extends ActivatableView implements WalletUiListener { private TableView tableView; private final XmrWalletRpcWrapper walletWrapper; From 66dfb26199822fe65994c33d86007dae9c6d6561 Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Wed, 18 Sep 2019 02:58:29 +0100 Subject: [PATCH 15/22] RPC digest authentication fixed. --- .../main/java/bisq/core/user/Preferences.java | 2 +- .../core/xmr/wallet/XmrWalletRpcWrapper.java | 4 +++- .../java/monero/rpc/MoneroRpcConnection.java | 19 +++++++++++++++++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 010eb143018..2987925c413 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -711,7 +711,7 @@ public void setXmrHostPortDelegate(String value) { public void setXmrRpcUserDelegate(String value) { // We only persist if we have not set the program argument - if (xmrUserHostDelegate == null || xmrUserHostDelegate.isEmpty()) { + if (xmrRpcUserDelegate == null || xmrRpcUserDelegate.isEmpty()) { prefPayload.setXmrRpcUser(value); persist(); } diff --git a/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java b/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java index b6c47a0f025..7bac24a57df 100644 --- a/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java +++ b/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java @@ -48,6 +48,7 @@ public class XmrWalletRpcWrapper { private MoneroWalletRpc walletRpc; private boolean xmrWalletRpcRunning = false; private String primaryAddress; + private Preferences preferences; //TODO(niyid) onChangeListener to dynamically create and set new walletRpc instance. //TODO(niyid) onChangeListener fires only after any of host, port, user, password have changed @@ -55,6 +56,7 @@ public class XmrWalletRpcWrapper { @Inject public XmrWalletRpcWrapper(Preferences preferences) { + this.preferences = preferences; log.debug("instantiating MoneroWalletRpc..."); HOST = preferences.getXmrUserHostDelegate(); PORT = Integer.parseInt(preferences.getXmrHostPortDelegate()); @@ -262,7 +264,7 @@ public void openWalletRpcInstance(WalletUiListener listener) { log.debug("Localhost Monero Wallet RPC detected."); UserThread.execute(() -> { xmrWalletRpcRunning = true; - walletRpc = new MoneroWalletRpc(new MoneroRpcConnection("http://" + HOST + ":" + PORT)); + walletRpc = new MoneroWalletRpc(new MoneroRpcConnection("http://" + HOST + ":" + PORT, preferences.getXmrRpcUserDelegate(), preferences.getXmrRpcPwdDelegate())); }); } catch (Throwable e) { log.debug("createWalletRpcInstance - {}", e.getMessage()); diff --git a/core/src/main/java/monero/rpc/MoneroRpcConnection.java b/core/src/main/java/monero/rpc/MoneroRpcConnection.java index b73801b7f9a..75e6915c6c1 100644 --- a/core/src/main/java/monero/rpc/MoneroRpcConnection.java +++ b/core/src/main/java/monero/rpc/MoneroRpcConnection.java @@ -6,14 +6,19 @@ import java.util.Map; import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.AuthCache; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.StringEntity; +import org.apache.http.impl.auth.DigestScheme; +import org.apache.http.impl.client.BasicAuthCache; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; @@ -51,6 +56,7 @@ public class MoneroRpcConnection { private HttpClient client; private String username; private String password; + private CredentialsProvider creds; public MoneroRpcConnection(URI uri) { this(uri, null, null); @@ -70,7 +76,7 @@ public MoneroRpcConnection(URI uri, String username, String password) { this.username = username; this.password = password; if (username != null || password != null) { - CredentialsProvider creds = new BasicCredentialsProvider(); + creds = new BasicCredentialsProvider(); creds.setCredentials(new AuthScope(uri.getHost(), uri.getPort()), new UsernamePasswordCredentials(username, password)); this.client = HttpClients.custom().setDefaultCredentialsProvider(creds).build(); } else { @@ -117,12 +123,21 @@ public Map sendJsonRequest(String method, Object params) { body.put("method", method); if (params != null) body.put("params", params); LOGGER.debug("Sending json request with method '" + method + "' and body: " + JsonUtils.serialize(body)); + + URI uriObject = URI.create(uri); + HttpHost targetHost = new HttpHost(uriObject.getHost(), uriObject.getPort()); + AuthCache authCache = new BasicAuthCache(); + DigestScheme digestScheme = new DigestScheme(); + authCache.put(targetHost, digestScheme); + HttpClientContext context = HttpClientContext.create(); + context.setCredentialsProvider(creds); + context.setAuthCache(authCache); // send http request and validate response HttpPost post = new HttpPost(uri.toString() + "/json_rpc"); HttpEntity entity = new StringEntity(JsonUtils.serialize(body)); post.setEntity(entity); - HttpResponse resp = client.execute(post); + HttpResponse resp = client.execute(targetHost, post, context); validateHttpResponse(resp); // deserialize response From fa393a76d8437f21aba6869d9bac7682ca19fbbd Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Wed, 18 Sep 2019 06:53:33 +0100 Subject: [PATCH 16/22] Digest not needed. --- .../java/monero/rpc/MoneroRpcConnection.java | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/monero/rpc/MoneroRpcConnection.java b/core/src/main/java/monero/rpc/MoneroRpcConnection.java index 75e6915c6c1..b73801b7f9a 100644 --- a/core/src/main/java/monero/rpc/MoneroRpcConnection.java +++ b/core/src/main/java/monero/rpc/MoneroRpcConnection.java @@ -6,19 +6,14 @@ import java.util.Map; import org.apache.http.HttpEntity; -import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.AuthCache; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.StringEntity; -import org.apache.http.impl.auth.DigestScheme; -import org.apache.http.impl.client.BasicAuthCache; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; @@ -56,7 +51,6 @@ public class MoneroRpcConnection { private HttpClient client; private String username; private String password; - private CredentialsProvider creds; public MoneroRpcConnection(URI uri) { this(uri, null, null); @@ -76,7 +70,7 @@ public MoneroRpcConnection(URI uri, String username, String password) { this.username = username; this.password = password; if (username != null || password != null) { - creds = new BasicCredentialsProvider(); + CredentialsProvider creds = new BasicCredentialsProvider(); creds.setCredentials(new AuthScope(uri.getHost(), uri.getPort()), new UsernamePasswordCredentials(username, password)); this.client = HttpClients.custom().setDefaultCredentialsProvider(creds).build(); } else { @@ -123,21 +117,12 @@ public Map sendJsonRequest(String method, Object params) { body.put("method", method); if (params != null) body.put("params", params); LOGGER.debug("Sending json request with method '" + method + "' and body: " + JsonUtils.serialize(body)); - - URI uriObject = URI.create(uri); - HttpHost targetHost = new HttpHost(uriObject.getHost(), uriObject.getPort()); - AuthCache authCache = new BasicAuthCache(); - DigestScheme digestScheme = new DigestScheme(); - authCache.put(targetHost, digestScheme); - HttpClientContext context = HttpClientContext.create(); - context.setCredentialsProvider(creds); - context.setAuthCache(authCache); // send http request and validate response HttpPost post = new HttpPost(uri.toString() + "/json_rpc"); HttpEntity entity = new StringEntity(JsonUtils.serialize(body)); post.setEntity(entity); - HttpResponse resp = client.execute(targetHost, post, context); + HttpResponse resp = client.execute(post); validateHttpResponse(resp); // deserialize response From c117b3f0e907b1aeca8ba0dbb1f618b9edbb33af Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Fri, 20 Sep 2019 02:00:54 +0100 Subject: [PATCH 17/22] Replacing monero-java with a stripped down version of less than 10 classes. --- .../wallet/model => bisq/core/xmr/jsonrpc}/MoneroDestination.java | 0 .../model => bisq/core/xmr/jsonrpc}/MoneroOutgoingTransfer.java | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename core/src/main/java/{monero/wallet/model => bisq/core/xmr/jsonrpc}/MoneroDestination.java (100%) rename core/src/main/java/{monero/wallet/model => bisq/core/xmr/jsonrpc}/MoneroOutgoingTransfer.java (100%) diff --git a/core/src/main/java/monero/wallet/model/MoneroDestination.java b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroDestination.java similarity index 100% rename from core/src/main/java/monero/wallet/model/MoneroDestination.java rename to core/src/main/java/bisq/core/xmr/jsonrpc/MoneroDestination.java diff --git a/core/src/main/java/monero/wallet/model/MoneroOutgoingTransfer.java b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroOutgoingTransfer.java similarity index 100% rename from core/src/main/java/monero/wallet/model/MoneroOutgoingTransfer.java rename to core/src/main/java/bisq/core/xmr/jsonrpc/MoneroOutgoingTransfer.java From 3f50cceccfa445748e1901597f3fd0bd9ac89377 Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Fri, 20 Sep 2019 02:05:50 +0100 Subject: [PATCH 18/22] Replacing monero-java references in the view. --- build.gradle | 5 +- .../core/xmr/jsonrpc}/GenUtils.java | 2 +- .../core/xmr/jsonrpc}/HttpException.java | 2 +- .../core/xmr/jsonrpc}/JsonException.java | 2 +- .../core/xmr/jsonrpc}/JsonUtils.java | 4 +- .../core/xmr/jsonrpc/MoneroDestination.java | 90 - .../core/xmr/jsonrpc}/MoneroException.java | 2 +- .../xmr/jsonrpc/MoneroOutgoingTransfer.java | 173 -- .../xmr/jsonrpc}/MoneroRpcConnection.java | 54 +- .../core/xmr/jsonrpc}/MoneroRpcException.java | 5 +- .../core/xmr/jsonrpc/MoneroSendPriority.java | 8 + .../core/xmr/jsonrpc}/MoneroUtils.java | 40 +- .../core/xmr/jsonrpc/MoneroWalletRpc.java | 173 ++ .../core/xmr/jsonrpc}/StreamUtils.java | 2 +- .../bisq/core/xmr/jsonrpc/result/Address.java | 42 + .../bisq/core/xmr/jsonrpc/result/Balance.java | 82 + .../xmr/jsonrpc/result/MoneroTransfer.java | 185 ++ .../jsonrpc/result/MoneroTransferList.java | 76 + .../core/xmr/jsonrpc/result/MoneroTx.java | 134 + .../core/xmr/jsonrpc/result/SubAddress.java | 66 + .../xmr/jsonrpc/result/SubAddressBalance.java | 115 + .../xmr/jsonrpc/result/SubAddressIndex.java | 41 + .../bisq/core/xmr/wallet/XmrTxListItem.java | 30 +- .../core/xmr/wallet/XmrWalletRpcWrapper.java | 84 +- core/src/main/java/common/types/Filter.java | 43 - core/src/main/java/common/types/Pair.java | 37 - .../java/common/utils/FieldDeserializer.java | 72 - .../src/main/java/common/utils/FileUtils.java | 28 - .../src/main/java/common/utils/MathUtils.java | 25 - .../main/java/common/utils/StringUtils.java | 19 - .../main/java/monero/daemon/MoneroDaemon.java | 678 ----- .../monero/daemon/MoneroDaemonDefault.java | 123 - .../java/monero/daemon/MoneroDaemonRpc.java | 1573 ------------ .../monero/daemon/model/MoneroAltChain.java | 56 - .../java/monero/daemon/model/MoneroBan.java | 47 - .../java/monero/daemon/model/MoneroBlock.java | 276 --- .../daemon/model/MoneroBlockHeader.java | 368 --- .../daemon/model/MoneroBlockTemplate.java | 73 - .../daemon/model/MoneroDaemonConnection.java | 165 -- .../model/MoneroDaemonConnectionSpan.java | 71 - .../monero/daemon/model/MoneroDaemonInfo.java | 266 -- .../daemon/model/MoneroDaemonListener.java | 27 - .../monero/daemon/model/MoneroDaemonPeer.java | 83 - .../daemon/model/MoneroDaemonSyncInfo.java | 64 - .../model/MoneroDaemonUpdateCheckResult.java | 68 - .../MoneroDaemonUpdateDownloadResult.java | 21 - .../daemon/model/MoneroHardForkInfo.java | 83 - .../monero/daemon/model/MoneroKeyImage.java | 98 - .../model/MoneroKeyImageSpentStatus.java | 19 - .../monero/daemon/model/MoneroMinerTxSum.java | 28 - .../daemon/model/MoneroMiningStatus.java | 57 - .../daemon/model/MoneroNetworkType.java | 10 - .../monero/daemon/model/MoneroOutput.java | 167 -- .../model/MoneroOutputDistributionEntry.java | 47 - .../model/MoneroOutputHistogramEntry.java | 46 - .../daemon/model/MoneroSubmitTxResult.java | 127 - .../java/monero/daemon/model/MoneroTx.java | 843 ------- .../daemon/model/MoneroTxBacklogEntry.java | 8 - .../daemon/model/MoneroTxPoolStats.java | 125 - .../java/monero/utils/MoneroCppUtils.java | 82 - .../main/java/monero/wallet/MoneroWallet.java | 1161 --------- .../monero/wallet/MoneroWalletDefault.java | 386 --- .../java/monero/wallet/MoneroWalletJni.java | 1635 ------------ .../java/monero/wallet/MoneroWalletRpc.java | 2207 ----------------- .../monero/wallet/model/MoneroAccount.java | 147 -- .../monero/wallet/model/MoneroAccountTag.java | 76 - .../wallet/model/MoneroAddressBookEntry.java | 52 - .../java/monero/wallet/model/MoneroCheck.java | 20 - .../wallet/model/MoneroCheckReserve.java | 28 - .../monero/wallet/model/MoneroCheckTx.java | 37 - .../wallet/model/MoneroIncomingTransfer.java | 143 -- .../wallet/model/MoneroIntegratedAddress.java | 78 - .../model/MoneroKeyImageImportResult.java | 37 - .../wallet/model/MoneroMultisigInfo.java | 44 - .../model/MoneroMultisigInitResult.java | 28 - .../model/MoneroMultisigSignResult.java | 52 - .../wallet/model/MoneroOutputQuery.java | 164 -- .../wallet/model/MoneroOutputWallet.java | 175 -- .../wallet/model/MoneroSendPriority.java | 11 - .../wallet/model/MoneroSendRequest.java | 330 --- .../monero/wallet/model/MoneroSubaddress.java | 184 -- .../wallet/model/MoneroSyncListener.java | 18 - .../monero/wallet/model/MoneroSyncResult.java | 35 - .../monero/wallet/model/MoneroTransfer.java | 162 -- .../wallet/model/MoneroTransferQuery.java | 226 -- .../monero/wallet/model/MoneroTxQuery.java | 496 ---- .../java/monero/wallet/model/MoneroTxSet.java | 140 -- .../monero/wallet/model/MoneroTxWallet.java | 514 ---- .../wallet/model/MoneroWalletListener.java | 19 - .../wallet/model/MoneroWalletListenerI.java | 28 - .../xmr/jsonrpc/MoneroRpcConnectionTest.java | 63 + .../wallet/monero/send/XmrSendView.java | 5 +- .../content/wallet/monero/tx/XmrTxView.java | 41 +- 93 files changed, 1051 insertions(+), 15031 deletions(-) rename core/src/main/java/{common/utils => bisq/core/xmr/jsonrpc}/GenUtils.java (97%) rename core/src/main/java/{common/types => bisq/core/xmr/jsonrpc}/HttpException.java (96%) rename core/src/main/java/{common/types => bisq/core/xmr/jsonrpc}/JsonException.java (92%) rename core/src/main/java/{common/utils => bisq/core/xmr/jsonrpc}/JsonUtils.java (98%) delete mode 100644 core/src/main/java/bisq/core/xmr/jsonrpc/MoneroDestination.java rename core/src/main/java/{monero/utils => bisq/core/xmr/jsonrpc}/MoneroException.java (97%) delete mode 100644 core/src/main/java/bisq/core/xmr/jsonrpc/MoneroOutgoingTransfer.java rename core/src/main/java/{monero/rpc => bisq/core/xmr/jsonrpc}/MoneroRpcConnection.java (82%) rename core/src/main/java/{monero/rpc => bisq/core/xmr/jsonrpc}/MoneroRpcException.java (89%) create mode 100644 core/src/main/java/bisq/core/xmr/jsonrpc/MoneroSendPriority.java rename core/src/main/java/{monero/utils => bisq/core/xmr/jsonrpc}/MoneroUtils.java (90%) create mode 100644 core/src/main/java/bisq/core/xmr/jsonrpc/MoneroWalletRpc.java rename core/src/main/java/{common/utils => bisq/core/xmr/jsonrpc}/StreamUtils.java (97%) create mode 100644 core/src/main/java/bisq/core/xmr/jsonrpc/result/Address.java create mode 100644 core/src/main/java/bisq/core/xmr/jsonrpc/result/Balance.java create mode 100644 core/src/main/java/bisq/core/xmr/jsonrpc/result/MoneroTransfer.java create mode 100644 core/src/main/java/bisq/core/xmr/jsonrpc/result/MoneroTransferList.java create mode 100644 core/src/main/java/bisq/core/xmr/jsonrpc/result/MoneroTx.java create mode 100644 core/src/main/java/bisq/core/xmr/jsonrpc/result/SubAddress.java create mode 100644 core/src/main/java/bisq/core/xmr/jsonrpc/result/SubAddressBalance.java create mode 100644 core/src/main/java/bisq/core/xmr/jsonrpc/result/SubAddressIndex.java delete mode 100644 core/src/main/java/common/types/Filter.java delete mode 100644 core/src/main/java/common/types/Pair.java delete mode 100644 core/src/main/java/common/utils/FieldDeserializer.java delete mode 100644 core/src/main/java/common/utils/FileUtils.java delete mode 100644 core/src/main/java/common/utils/MathUtils.java delete mode 100644 core/src/main/java/common/utils/StringUtils.java delete mode 100644 core/src/main/java/monero/daemon/MoneroDaemon.java delete mode 100644 core/src/main/java/monero/daemon/MoneroDaemonDefault.java delete mode 100644 core/src/main/java/monero/daemon/MoneroDaemonRpc.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroAltChain.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroBan.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroBlock.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroBlockHeader.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroBlockTemplate.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroDaemonConnection.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroDaemonConnectionSpan.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroDaemonInfo.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroDaemonListener.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroDaemonPeer.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroDaemonSyncInfo.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroDaemonUpdateCheckResult.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroDaemonUpdateDownloadResult.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroHardForkInfo.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroKeyImage.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroKeyImageSpentStatus.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroMinerTxSum.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroMiningStatus.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroNetworkType.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroOutput.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroOutputDistributionEntry.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroOutputHistogramEntry.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroSubmitTxResult.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroTx.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroTxBacklogEntry.java delete mode 100644 core/src/main/java/monero/daemon/model/MoneroTxPoolStats.java delete mode 100644 core/src/main/java/monero/utils/MoneroCppUtils.java delete mode 100644 core/src/main/java/monero/wallet/MoneroWallet.java delete mode 100644 core/src/main/java/monero/wallet/MoneroWalletDefault.java delete mode 100644 core/src/main/java/monero/wallet/MoneroWalletJni.java delete mode 100644 core/src/main/java/monero/wallet/MoneroWalletRpc.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroAccount.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroAccountTag.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroAddressBookEntry.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroCheck.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroCheckReserve.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroCheckTx.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroIncomingTransfer.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroIntegratedAddress.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroKeyImageImportResult.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroMultisigInfo.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroMultisigInitResult.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroMultisigSignResult.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroOutputQuery.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroOutputWallet.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroSendPriority.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroSendRequest.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroSubaddress.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroSyncListener.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroSyncResult.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroTransfer.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroTransferQuery.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroTxQuery.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroTxSet.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroTxWallet.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroWalletListener.java delete mode 100644 core/src/main/java/monero/wallet/model/MoneroWalletListenerI.java create mode 100644 core/src/test/java/bisq/core/xmr/jsonrpc/MoneroRpcConnectionTest.java diff --git a/build.gradle b/build.gradle index 5731e6e19c5..c3734878b71 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,3 @@ - buildscript { repositories { jcenter() @@ -81,6 +80,10 @@ configure(subprojects) { maven { url 'https://jitpack.io' } } + dependencies { + testCompile "junit:junit:$junitVersion" + } + tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } diff --git a/core/src/main/java/common/utils/GenUtils.java b/core/src/main/java/bisq/core/xmr/jsonrpc/GenUtils.java similarity index 97% rename from core/src/main/java/common/utils/GenUtils.java rename to core/src/main/java/bisq/core/xmr/jsonrpc/GenUtils.java index 115c411af0c..641b4e9c9ff 100644 --- a/core/src/main/java/common/utils/GenUtils.java +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/GenUtils.java @@ -1,4 +1,4 @@ -package common.utils; +package bisq.core.xmr.jsonrpc; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/common/types/HttpException.java b/core/src/main/java/bisq/core/xmr/jsonrpc/HttpException.java similarity index 96% rename from core/src/main/java/common/types/HttpException.java rename to core/src/main/java/bisq/core/xmr/jsonrpc/HttpException.java index ca942338c0b..ed4c1d283a1 100644 --- a/core/src/main/java/common/types/HttpException.java +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/HttpException.java @@ -1,4 +1,4 @@ -package common.types; +package bisq.core.xmr.jsonrpc; /** * Defines an HTTP exception for when HTTP responses are not in the 200s. diff --git a/core/src/main/java/common/types/JsonException.java b/core/src/main/java/bisq/core/xmr/jsonrpc/JsonException.java similarity index 92% rename from core/src/main/java/common/types/JsonException.java rename to core/src/main/java/bisq/core/xmr/jsonrpc/JsonException.java index 8b2911219c1..bde965ad47f 100644 --- a/core/src/main/java/common/types/JsonException.java +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/JsonException.java @@ -1,4 +1,4 @@ -package common.types; +package bisq.core.xmr.jsonrpc; /** * Represents an exception handling JSON. diff --git a/core/src/main/java/common/utils/JsonUtils.java b/core/src/main/java/bisq/core/xmr/jsonrpc/JsonUtils.java similarity index 98% rename from core/src/main/java/common/utils/JsonUtils.java rename to core/src/main/java/bisq/core/xmr/jsonrpc/JsonUtils.java index 34676d2d6b5..5d73a35fefd 100644 --- a/core/src/main/java/common/utils/JsonUtils.java +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/JsonUtils.java @@ -1,4 +1,4 @@ -package common.utils; +package bisq.core.xmr.jsonrpc; import java.util.Map; @@ -6,8 +6,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import common.types.JsonException; - /** * Collection of utilities for working with JSON. * diff --git a/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroDestination.java b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroDestination.java deleted file mode 100644 index 8c590f194d9..00000000000 --- a/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroDestination.java +++ /dev/null @@ -1,90 +0,0 @@ -package monero.wallet.model; - -import java.math.BigInteger; - -import monero.utils.MoneroUtils; - -/** - * Models an outgoing transfer destination. - */ -public class MoneroDestination { - - private String address; - private BigInteger amount; - - public MoneroDestination() { - // nothing to construct - } - - public MoneroDestination(String address) { - super(); - this.address = address; - } - - public MoneroDestination(String address, BigInteger amount) { - super(); - this.address = address; - this.amount = amount; - } - - public MoneroDestination(MoneroDestination destination) { - this.address = destination.address; - this.amount = destination.amount; - } - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public BigInteger getAmount() { - return amount; - } - - public void setAmount(BigInteger amount) { - this.amount = amount; - } - - public MoneroDestination copy() { - return new MoneroDestination(this); - } - - public String toString() { - return toString(0); - } - - public String toString(int indent) { - StringBuilder sb = new StringBuilder(); - sb.append(MoneroUtils.kvLine("Address", this.getAddress(), indent)); - sb.append(MoneroUtils.kvLine("Amount", this.getAmount() != null ? this.getAmount().toString() : null, indent)); - String str = sb.toString(); - return str.substring(0, str.length() - 1); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((address == null) ? 0 : address.hashCode()); - result = prime * result + ((amount == null) ? 0 : amount.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - MoneroDestination other = (MoneroDestination) obj; - if (address == null) { - if (other.address != null) return false; - } else if (!address.equals(other.address)) return false; - if (amount == null) { - if (other.amount != null) return false; - } else if (!amount.equals(other.amount)) return false; - return true; - } -} diff --git a/core/src/main/java/monero/utils/MoneroException.java b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroException.java similarity index 97% rename from core/src/main/java/monero/utils/MoneroException.java rename to core/src/main/java/bisq/core/xmr/jsonrpc/MoneroException.java index 6af5020370b..1eb5c952a24 100644 --- a/core/src/main/java/monero/utils/MoneroException.java +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroException.java @@ -1,4 +1,4 @@ -package monero.utils; +package bisq.core.xmr.jsonrpc; import static org.junit.Assert.assertNotNull; diff --git a/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroOutgoingTransfer.java b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroOutgoingTransfer.java deleted file mode 100644 index cdcdf5f6775..00000000000 --- a/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroOutgoingTransfer.java +++ /dev/null @@ -1,173 +0,0 @@ -package monero.wallet.model; - -import static org.junit.Assert.assertTrue; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import monero.utils.MoneroUtils; - -/** - * Models an outgoing transfer of funds from the wallet. - */ -public class MoneroOutgoingTransfer extends MoneroTransfer { - - private List subaddressIndices; - private List addresses; - private List destinations; - - public MoneroOutgoingTransfer() { - // nothing to initialize - } - - public MoneroOutgoingTransfer(final MoneroOutgoingTransfer transfer) { - super(transfer); - if (transfer.subaddressIndices != null) this.subaddressIndices = new ArrayList(transfer.subaddressIndices); - if (transfer.addresses != null) this.addresses = new ArrayList(transfer.addresses); - if (transfer.destinations != null) { - this.destinations = new ArrayList(); - for (MoneroDestination destination : transfer.getDestinations()) { - this.destinations.add(destination.copy()); - } - } - } - - @Override - public MoneroOutgoingTransfer copy() { - return new MoneroOutgoingTransfer(this); - } - - @JsonProperty("isIncoming") - public Boolean isIncoming() { - return false; - } - - public List getSubaddressIndices() { - return subaddressIndices; - } - - public MoneroOutgoingTransfer setSubaddressIndices(List subaddressIndices) { - this.subaddressIndices = subaddressIndices; - return this; - } - - public List getAddresses() { - return addresses; - } - - public MoneroOutgoingTransfer setAddresses(List addresses) { - this.addresses = addresses; - return this; - } - - public List getDestinations() { - return destinations; - } - - public MoneroOutgoingTransfer setDestinations(List destinations) { - this.destinations = destinations; - return this; - } - - public MoneroOutgoingTransfer merge(MoneroTransfer transfer) { - assertTrue(transfer instanceof MoneroOutgoingTransfer); - return merge((MoneroOutgoingTransfer) transfer); - } - - /** - * Updates this transaction by merging the latest information from the given - * transaction. - * - * Merging can modify or build references to the transfer given so it - * should not be re-used or it should be copied before calling this method. - * - * @param transfer is the transfer to merge into this one - * @return this transfer for chaining - */ - public MoneroOutgoingTransfer merge(MoneroOutgoingTransfer transfer) { - super.merge(transfer); - assertTrue(transfer instanceof MoneroOutgoingTransfer); - if (this == transfer) return this; - this.setSubaddressIndices(MoneroUtils.reconcile(this.getSubaddressIndices(), transfer.getSubaddressIndices())); - this.setAddresses(MoneroUtils.reconcile(this.getAddresses(), transfer.getAddresses())); - this.setDestinations(MoneroUtils.reconcile(this.getDestinations(), transfer.getDestinations())); - return this; - } - - public String toString() { - return toString(0); - } - - public String toString(int indent) { - StringBuilder sb = new StringBuilder(); - sb.append(super.toString(indent) + "\n"); - sb.append(MoneroUtils.kvLine("Subaddress indices", this.getSubaddressIndices(), indent)); - sb.append(MoneroUtils.kvLine("Addresses", this.getAddresses(), indent)); - if (this.getDestinations() != null) { - sb.append(MoneroUtils.kvLine("Destinations", "", indent)); - for (int i = 0; i < this.getDestinations().size(); i++) { - sb.append(MoneroUtils.kvLine(i + 1, "", indent + 1)); - sb.append(getDestinations().get(i).toString(indent + 2) + "\n"); - } - } - String str = sb.toString(); - return str.substring(0, str.length() - 1); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((addresses == null) ? 0 : addresses.hashCode()); - result = prime * result + ((destinations == null) ? 0 : destinations.hashCode()); - result = prime * result + ((subaddressIndices == null) ? 0 : subaddressIndices.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!super.equals(obj)) return false; - if (getClass() != obj.getClass()) return false; - MoneroOutgoingTransfer other = (MoneroOutgoingTransfer) obj; - if (addresses == null) { - if (other.addresses != null) return false; - } else if (!addresses.equals(other.addresses)) return false; - if (destinations == null) { - if (other.destinations != null) return false; - } else if (!destinations.equals(other.destinations)) return false; - if (subaddressIndices == null) { - if (other.subaddressIndices != null) return false; - } else if (!subaddressIndices.equals(other.subaddressIndices)) return false; - return true; - } - - // ------------------- OVERRIDE CO-VARIANT RETURN TYPES --------------------- - - @Override - public MoneroOutgoingTransfer setTx(MoneroTxWallet tx) { - super.setTx(tx); - return this; - } - - @Override - public MoneroOutgoingTransfer setAmount(BigInteger amount) { - super.setAmount(amount); - return this; - } - - @Override - public MoneroOutgoingTransfer setAccountIndex(Integer accountIndex) { - super.setAccountIndex(accountIndex); - return this; - } - - @Override - public MoneroOutgoingTransfer setNumSuggestedConfirmations(Long numSuggestedConfirmations) { - super.setNumSuggestedConfirmations(numSuggestedConfirmations); - return this; - } -} diff --git a/core/src/main/java/monero/rpc/MoneroRpcConnection.java b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroRpcConnection.java similarity index 82% rename from core/src/main/java/monero/rpc/MoneroRpcConnection.java rename to core/src/main/java/bisq/core/xmr/jsonrpc/MoneroRpcConnection.java index b73801b7f9a..72165815a21 100644 --- a/core/src/main/java/monero/rpc/MoneroRpcConnection.java +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroRpcConnection.java @@ -1,4 +1,4 @@ -package monero.rpc; +package bisq.core.xmr.jsonrpc; import java.math.BigInteger; import java.net.URI; @@ -12,7 +12,6 @@ import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.HttpClients; @@ -22,13 +21,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; - -import common.types.HttpException; -import common.utils.JsonUtils; -import common.utils.StreamUtils; -import monero.utils.MoneroCppUtils; -import monero.utils.MoneroException; -import monero.utils.MoneroUtils; +import com.google.gson.Gson; /** * Maintains a connection and sends requests to a Monero RPC API. @@ -198,50 +191,7 @@ public Map sendPathRequest(String path, Map para throw new MoneroException(e3); } } - - /** - * Sends a binary RPC request. - * - * @param path is the path of the binary RPC method to invoke - * @param params are the request parameters - * @return byte[] is the binary response - */ - public byte[] sendBinaryRequest(String path, Map params) { - - // serialize params to monero's portable binary storage format - byte[] paramsBin = MoneroCppUtils.mapToBinary(params); - try { - - // build request - HttpPost post = new HttpPost(uri.toString() + "/" + path); - if (paramsBin != null) { - HttpEntity entity = new ByteArrayEntity(paramsBin); - post.setEntity(entity); - } - LOGGER.debug("Sending binary request with path '" + path + "' and params: " + JsonUtils.serialize(params)); - - // send request and validate response - HttpResponse resp = client.execute(post); - validateHttpResponse(resp); - - // deserialize response - return EntityUtils.toByteArray(resp.getEntity()); - -// // send request and store binary response as Uint8Array -// let resp = await this._throttledRequest(opts); -// if (resp.error) throw new MoneroRpcError(resp.error.code, resp.error.message, opts); -// return new Uint8Array(resp, 0, resp.length); - } catch (HttpException e1) { - throw e1; - } catch (MoneroRpcException e2) { - throw e2; - } catch (Exception e3) { - e3.printStackTrace(); - throw new MoneroException(e3); - } - } - @Override public int hashCode() { final int prime = 31; diff --git a/core/src/main/java/monero/rpc/MoneroRpcException.java b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroRpcException.java similarity index 89% rename from core/src/main/java/monero/rpc/MoneroRpcException.java rename to core/src/main/java/bisq/core/xmr/jsonrpc/MoneroRpcException.java index fa7d9b486fe..002630e4326 100644 --- a/core/src/main/java/monero/rpc/MoneroRpcException.java +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroRpcException.java @@ -1,7 +1,4 @@ -package monero.rpc; - -import common.utils.JsonUtils; -import monero.utils.MoneroException; +package bisq.core.xmr.jsonrpc; /** * Exception when interacting with the Monero daemon or wallet RPC API. diff --git a/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroSendPriority.java b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroSendPriority.java new file mode 100644 index 00000000000..01c50b06016 --- /dev/null +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroSendPriority.java @@ -0,0 +1,8 @@ +package bisq.core.xmr.jsonrpc; + +public enum MoneroSendPriority { + DEFAULT, + UNIMPORTANT, + NORMAL, + ELEVATED +} diff --git a/core/src/main/java/monero/utils/MoneroUtils.java b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroUtils.java similarity index 90% rename from core/src/main/java/monero/utils/MoneroUtils.java rename to core/src/main/java/bisq/core/xmr/jsonrpc/MoneroUtils.java index 06310497aaa..46b7dc1982d 100644 --- a/core/src/main/java/monero/utils/MoneroUtils.java +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroUtils.java @@ -1,4 +1,4 @@ -package monero.utils; +package bisq.core.xmr.jsonrpc; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -11,10 +11,6 @@ import java.util.Arrays; import java.util.List; -import common.utils.GenUtils; -import monero.daemon.model.MoneroTx; -import monero.wallet.model.MoneroTxWallet; - /** * Collection of Monero utilities. */ @@ -275,38 +271,4 @@ public static String kvLine(Object key, Object value, int indent, boolean newlin if (value == null && ignoreUndefined) return ""; return GenUtils.getIndent(indent) + key + ": " + value + (newline ? '\n' : ""); } - - /** - * Merges a transaction into a list of existing transactions. - * - * TODO: collapse into MoneroUtils.mergeTx(List txs, ...)? - * - * @param txs are existing transactions to merge into - * @param tx is the transaction to merge into the list - */ - public static void mergeTx(List txs, MoneroTx tx) { - for (MoneroTx aTx : txs) { - if (aTx.getId().equals(tx.getId())) { - aTx.merge(tx); - return; - } - } - txs.add(tx); - } - - /** - * Merges a transaction into a list of existing transactions. - * - * @param txs are existing transactions to merge into - * @param tx is the transaction to merge into the list - */ - public static void mergeTx(List txs, MoneroTxWallet tx) { - for (MoneroTx aTx : txs) { - if (aTx.getId().equals(tx.getId())) { - aTx.merge(tx); - return; - } - } - txs.add(tx); - } } diff --git a/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroWalletRpc.java b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroWalletRpc.java new file mode 100644 index 00000000000..01c1c75dbf4 --- /dev/null +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroWalletRpc.java @@ -0,0 +1,173 @@ +package bisq.core.xmr.jsonrpc; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; + +import bisq.core.xmr.jsonrpc.result.Address; +import bisq.core.xmr.jsonrpc.result.Balance; +import bisq.core.xmr.jsonrpc.result.MoneroTransfer; +import bisq.core.xmr.jsonrpc.result.MoneroTransferList; +import bisq.core.xmr.jsonrpc.result.MoneroTx; + +public class MoneroWalletRpc { + + protected Logger log = LoggerFactory.getLogger(MoneroWalletRpc.class); + + private static final Gson GSON = new Gson(); + + private MoneroRpcConnection rpcConnection; + + public MoneroWalletRpc(MoneroRpcConnection rpcConnection) { + this.rpcConnection = rpcConnection; + } + + public String getPrimaryAddress() { + Map params = new HashMap<>(); + params.put("account_index", 0); + Map response = rpcConnection.sendJsonRequest("get_address", params); + log.debug("response => {}", response); + + Address address = GSON.fromJson(GSON.toJson(response.get("result")), Address.class); + log.debug("address => {}", address); + + return address.getAddress(); + } + + public Balance getBalanceData() { + Map response = rpcConnection.sendJsonRequest("get_balance"); + log.debug("response => {}", response); + + Balance balance = GSON.fromJson(GSON.toJson(response.get("result")), Balance.class); + log.debug("balance => {}", balance); + + return balance; + } + + public BigInteger getBalance() { + return getBalanceData().getBalance(); + } + + public BigInteger getUnlockedBalance() { + return getBalanceData().getUnlockedBalance(); + } + + public MoneroTx send(Map request) { + Map response = rpcConnection.sendJsonRequest("transfer", request); + log.debug("response => {}", response); + + MoneroTx moneroTx = GSON.fromJson(GSON.toJson(response.get("result")), MoneroTx.class); + log.debug("moneroTx => {}", moneroTx); + + return moneroTx; + } + + @SuppressWarnings("unchecked") + public String relayTx(String txMetadata) { + Map params = new HashMap<>(); + params.put("hex", txMetadata); + Map response = rpcConnection.sendJsonRequest("relay_tx", params); + log.debug("response => {}", response); + + Map result = (Map) response.get("result"); + String txHash = (String) result.get("tx_hash"); + log.debug("txHash => {}", txHash); + + return txHash; + } + + public List getTxs(String txIds) { + Map params = new HashMap<>(); + Map response = null; + List transfers = new ArrayList<>(); + if(txIds == null || txIds.isEmpty()) { + params.put("in", true); + params.put("out", true); + params.put("pending", true); + params.put("failed", true); + params.put("pool", true); + params.put("in", true); + params.put("in", true); + params.put("in", true); + response = rpcConnection.sendJsonRequest("get_transfers", params); + log.debug("response => {}", response); + MoneroTransferList transferList = GSON.fromJson(GSON.toJson(response.get("result")), MoneroTransferList.class); + transfers.addAll(transferList.getIn()); + transfers.addAll(transferList.getOut()); + transfers.addAll(transferList.getPending()); + transfers.addAll(transferList.getPool()); + transfers.addAll(transferList.getFailed()); + } else { + String[] txIdTokens = txIds.replace(" ", "").split(",") ; + for(String tid : txIdTokens) { + params = new HashMap<>(); + params.put("txid", tid); + response = rpcConnection.sendJsonRequest("get_transfer_by_txid", params); + log.debug("response => {}", response); + MoneroTransfer transfer = GSON.fromJson(GSON.toJson(response.get("transfer")), MoneroTransfer.class); + transfers.add(transfer); + } + } + + log.debug("transfers => {}", transfers); + + return transfers; + } + + @SuppressWarnings("unchecked") + public String getSpendProof(String txId, String message) { + Map params = new HashMap<>(); + params.put("txid", txId); + if(message != null && !message.isEmpty() ) { + params.put("message", message); + } + Map response = rpcConnection.sendJsonRequest("get_spend_proof", params); + log.debug("response => {}", response); + + Map result = (Map) response.get("result"); + String signature = (String) result.get("signature"); + log.debug("signature => {}", signature); + + return signature; + + } + + @SuppressWarnings("unchecked") + public boolean checkSpendProof(String txId, String message, String signature) { + Map params = new HashMap<>(); + params.put("txid", txId); + if(message != null && !message.isEmpty() ) { + params.put("message", message); + } + params.put("signature", signature); + Map response = rpcConnection.sendJsonRequest("get_spend_proof", params); + log.debug("response => {}", response); + + Map result = (Map) response.get("result"); + boolean good = (boolean) result.get("good"); + log.debug("good => {}", good); + + return good; + + } + + public static final String generatePaymentId() { + String f32 = UUID.randomUUID().toString().replace("-", ""); + String l32 = UUID.randomUUID().toString().replace("-", ""); + return f32.concat(l32); + } + + public void close() { + Map response = rpcConnection.sendJsonRequest("close_wallet"); + log.debug("response => {}", response); + } + +} diff --git a/core/src/main/java/common/utils/StreamUtils.java b/core/src/main/java/bisq/core/xmr/jsonrpc/StreamUtils.java similarity index 97% rename from core/src/main/java/common/utils/StreamUtils.java rename to core/src/main/java/bisq/core/xmr/jsonrpc/StreamUtils.java index 27f7dd8cdf4..2afdcb5670a 100644 --- a/core/src/main/java/common/utils/StreamUtils.java +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/StreamUtils.java @@ -1,4 +1,4 @@ -package common.utils; +package bisq.core.xmr.jsonrpc; import java.io.ByteArrayInputStream; import java.io.IOException; diff --git a/core/src/main/java/bisq/core/xmr/jsonrpc/result/Address.java b/core/src/main/java/bisq/core/xmr/jsonrpc/result/Address.java new file mode 100644 index 00000000000..b6eddbda3d8 --- /dev/null +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/result/Address.java @@ -0,0 +1,42 @@ +package bisq.core.xmr.jsonrpc.result; + +import java.io.Serializable; +import java.util.List; + +import com.google.gson.annotations.Expose; + +public class Address implements Serializable { + + /** + * + */ + private static final long serialVersionUID = -6025042322196110971L; + + @Expose + private String address; + + @Expose + private List addresses; + + @Override + public String toString() { + return "Address [address=" + address + ", addresses=" + addresses + "]"; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public List getAddresses() { + return addresses; + } + + public void setAddresses(List addresses) { + this.addresses = addresses; + } + +} diff --git a/core/src/main/java/bisq/core/xmr/jsonrpc/result/Balance.java b/core/src/main/java/bisq/core/xmr/jsonrpc/result/Balance.java new file mode 100644 index 00000000000..5be172c81e7 --- /dev/null +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/result/Balance.java @@ -0,0 +1,82 @@ +package bisq.core.xmr.jsonrpc.result; + +import java.io.Serializable; +import java.math.BigInteger; +import java.util.List; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class Balance implements Serializable { + + /** + * + */ + private static final long serialVersionUID = 135184506420074165L; + + @Expose + private BigInteger balance; + + @Expose + @SerializedName(value = "blocks_to_unlock") + private long blocksToUnlock; + + @Expose + @SerializedName(value = "multisig_import_needed") + private boolean multisigImportNeeded; + + @Expose + @SerializedName(value = "unlocked_balance") + private BigInteger unlockedBalance; + + @Expose + @SerializedName(value = "per_subaddress") + private List perSubaddress; + + @Override + public String toString() { + return "Balance [balance=" + balance + ", blocksToUnlock=" + blocksToUnlock + ", multisigImportNeeded=" + + multisigImportNeeded + ", unlockedBalance=" + unlockedBalance + ", perSubaddress=" + perSubaddress + + "]"; + } + + public BigInteger getBalance() { + return balance; + } + + public void setBalance(BigInteger balance) { + this.balance = balance; + } + + public long getBlocksToUnlock() { + return blocksToUnlock; + } + + public void setBlocksToUnlock(long blocksToUnlock) { + this.blocksToUnlock = blocksToUnlock; + } + + public boolean isMultisigImportNeeded() { + return multisigImportNeeded; + } + + public void setMultisigImportNeeded(boolean multisigImportNeeded) { + this.multisigImportNeeded = multisigImportNeeded; + } + + public BigInteger getUnlockedBalance() { + return unlockedBalance; + } + + public void setUnlockedBalance(BigInteger unlockedBalance) { + this.unlockedBalance = unlockedBalance; + } + + public List getPerSubaddress() { + return perSubaddress; + } + + public void setPerSubaddress(List perSubaddress) { + this.perSubaddress = perSubaddress; + } +} diff --git a/core/src/main/java/bisq/core/xmr/jsonrpc/result/MoneroTransfer.java b/core/src/main/java/bisq/core/xmr/jsonrpc/result/MoneroTransfer.java new file mode 100644 index 00000000000..24a814c6b5a --- /dev/null +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/result/MoneroTransfer.java @@ -0,0 +1,185 @@ +package bisq.core.xmr.jsonrpc.result; + +import java.io.Serializable; +import java.math.BigInteger; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class MoneroTransfer implements Serializable { + + /** + * + */ + private static final long serialVersionUID = -4955120373028815989L; + + @Expose + private String address; + + @Expose + private BigInteger amount; + + @Expose + private long confirmations; + + @Expose + @SerializedName(value = "double_spend_seen") + private boolean doubleSpendSeen; + + @Expose + private BigInteger fee; + + @Expose + private long height; + + @Expose + private String note; + + @Expose + @SerializedName(value = "payment_id") + private String paymentId; + + @Expose + @SerializedName(value = "subaddr_index") + private SubAddressIndex subaddrIndex; + + @Expose + @SerializedName(value = "suggested_confirmations_threshold") + private long suggestedConfirmationsThreshold; + + @Expose + private long timestamp; + + @Expose + @SerializedName(value = "txid") + private String id; + + @Expose + private String type; + + @Expose + @SerializedName(value = "unlockTime") + private long unlockTime; + + @Override + public String toString() { + return "MoneroTransfer [address=" + address + ", amount=" + amount + ", confirmations=" + confirmations + + ", doubleSpendSeen=" + doubleSpendSeen + ", fee=" + fee + ", height=" + height + ", note=" + note + + ", paymentId=" + paymentId + ", subaddrIndex=" + subaddrIndex + ", suggestedConfirmationsThreshold=" + + suggestedConfirmationsThreshold + ", timestamp=" + timestamp + ", id=" + id + ", type=" + type + + ", unlockTime=" + unlockTime + "]"; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public BigInteger getAmount() { + return amount; + } + + public void setAmount(BigInteger amount) { + this.amount = amount; + } + + public long getConfirmations() { + return confirmations; + } + + public void setConfirmations(long confirmations) { + this.confirmations = confirmations; + } + + public boolean isDoubleSpendSeen() { + return doubleSpendSeen; + } + + public void setDoubleSpendSeen(boolean doubleSpendSeen) { + this.doubleSpendSeen = doubleSpendSeen; + } + + public BigInteger getFee() { + return fee; + } + + public void setFee(BigInteger fee) { + this.fee = fee; + } + + public long getHeight() { + return height; + } + + public void setHeight(long height) { + this.height = height; + } + + public String getNote() { + return note; + } + + public void setNote(String note) { + this.note = note; + } + + public String getPaymentId() { + return paymentId; + } + + public void setPaymentId(String paymentId) { + this.paymentId = paymentId; + } + + public SubAddressIndex getSubaddrIndex() { + return subaddrIndex; + } + + public void setSubaddrIndex(SubAddressIndex subaddrIndex) { + this.subaddrIndex = subaddrIndex; + } + + public long getSuggestedConfirmationsThreshold() { + return suggestedConfirmationsThreshold; + } + + public void setSuggestedConfirmationsThreshold(long suggestedConfirmationsThreshold) { + this.suggestedConfirmationsThreshold = suggestedConfirmationsThreshold; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public String getId() { + return id; + } + + public void setId(String txId) { + this.id = txId; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public long getUnlockTime() { + return unlockTime; + } + + public void setUnlockTime(long unlock_time) { + this.unlockTime = unlock_time; + } + +} diff --git a/core/src/main/java/bisq/core/xmr/jsonrpc/result/MoneroTransferList.java b/core/src/main/java/bisq/core/xmr/jsonrpc/result/MoneroTransferList.java new file mode 100644 index 00000000000..f0712c28ae4 --- /dev/null +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/result/MoneroTransferList.java @@ -0,0 +1,76 @@ +package bisq.core.xmr.jsonrpc.result; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; + +import com.google.gson.annotations.Expose; + +public class MoneroTransferList implements Serializable { + + /** + * + */ + private static final long serialVersionUID = 2239920012741769530L; + + @Expose + private List in = Collections.emptyList(); + + @Expose + private List out = Collections.emptyList(); + + @Expose + private List pending = Collections.emptyList(); + + @Expose + private List failed = Collections.emptyList(); + + @Expose + private List pool = Collections.emptyList(); + + @Override + public String toString() { + return "MoneroTransferList [in=" + in + ", out=" + out + ", pending=" + pending + ", failed=" + failed + + ", pool=" + pool + "]"; + } + + public List getIn() { + return in; + } + + public void setIn(List in) { + this.in = in; + } + + public List getOut() { + return out; + } + + public void setOut(List out) { + this.out = out; + } + + public List getPending() { + return pending; + } + + public void setPending(List pending) { + this.pending = pending; + } + + public List getFailed() { + return failed; + } + + public void setFailed(List failed) { + this.failed = failed; + } + + public List getPool() { + return pool; + } + + public void setPool(List pool) { + this.pool = pool; + } +} diff --git a/core/src/main/java/bisq/core/xmr/jsonrpc/result/MoneroTx.java b/core/src/main/java/bisq/core/xmr/jsonrpc/result/MoneroTx.java new file mode 100644 index 00000000000..a965114937b --- /dev/null +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/result/MoneroTx.java @@ -0,0 +1,134 @@ +package bisq.core.xmr.jsonrpc.result; + +import java.io.Serializable; +import java.math.BigInteger; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class MoneroTx implements Serializable { + + /** + * + */ + private static final long serialVersionUID = -4941720175150037660L; + + @Expose + private BigInteger amount; + + private BigInteger fee; + + @Expose + @SerializedName(value = "multisig_txset") + private String multisigTxset; + + @Expose + @SerializedName(value = "tx_blob") + private String txBlob; + + @Expose + @SerializedName(value = "tx_hash") + private String txHash; + + @Expose + @SerializedName(value = "tx_key") + private String txKey; + + @Expose + @SerializedName(value = "tx_metadata") + private String txMetadata; + + @Expose + @SerializedName(value = "get_tx_metadata") + private boolean getTxMetadata; + + @Expose + @SerializedName(value = "unsigned_txset") + private String unsignedTxset; + + @Expose + private long size; + + @Override + public String toString() { + return "MoneroTx [amount=" + amount + ", fee=" + fee + ", multisigTxset=" + multisigTxset + ", txBlob=" + txBlob + + ", txHash=" + txHash + ", txKey=" + txKey + ", txMetadata=" + txMetadata + ", getTxMetadata=" + + getTxMetadata + ", unsignedTxset=" + unsignedTxset + "]"; + } + + public BigInteger getAmount() { + return amount; + } + + public void setAmount(BigInteger amount) { + this.amount = amount; + } + + public BigInteger getFee() { + return fee; + } + + public void setFee(BigInteger fee) { + this.fee = fee; + } + + public String getMultisigTxset() { + return multisigTxset; + } + + public void setMultisigTxset(String multisigTxset) { + this.multisigTxset = multisigTxset; + } + + public String getTxBlob() { + return txBlob; + } + + public void setTxBlob(String txBlob) { + this.txBlob = txBlob; + } + + public String getTxHash() { + return txHash; + } + + public void setTxHash(String txHash) { + this.txHash = txHash; + } + + public String getTxKey() { + return txKey; + } + + public void setTxKey(String txKey) { + this.txKey = txKey; + } + + public String getTxMetadata() { + return txMetadata; + } + + public void setTxMetadata(String txMetadata) { + this.txMetadata = txMetadata; + } + + public boolean isGetTxMetadata() { + return getTxMetadata; + } + + public void setGetTxMetadata(boolean getTxMetadata) { + this.getTxMetadata = getTxMetadata; + } + + public String getUnsignedTxset() { + return unsignedTxset; + } + + public void setUnsignedTxset(String unsignedTxset) { + this.unsignedTxset = unsignedTxset; + } + + public long getSize() { + return txMetadata != null ? txMetadata.getBytes().length : 0; + } +} diff --git a/core/src/main/java/bisq/core/xmr/jsonrpc/result/SubAddress.java b/core/src/main/java/bisq/core/xmr/jsonrpc/result/SubAddress.java new file mode 100644 index 00000000000..517319c7737 --- /dev/null +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/result/SubAddress.java @@ -0,0 +1,66 @@ +package bisq.core.xmr.jsonrpc.result; + +import java.io.Serializable; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class SubAddress implements Serializable { + + /** + * + */ + private static final long serialVersionUID = -6809755448109678848L; + + @Expose + private String address; + + @Expose + @SerializedName(value = "address_index") + private int addressIndex; + + @Expose + private String label; + + @Expose + private boolean used; + + @Override + public String toString() { + return "SubAddress [address=" + address + ", addressIndex=" + addressIndex + ", label=" + label + ", used=" + + used + "]"; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public int getAddressIndex() { + return addressIndex; + } + + public void setAddressIndex(int addressIndex) { + this.addressIndex = addressIndex; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public boolean isUsed() { + return used; + } + + public void setUsed(boolean used) { + this.used = used; + } + +} diff --git a/core/src/main/java/bisq/core/xmr/jsonrpc/result/SubAddressBalance.java b/core/src/main/java/bisq/core/xmr/jsonrpc/result/SubAddressBalance.java new file mode 100644 index 00000000000..4bfb6a500b3 --- /dev/null +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/result/SubAddressBalance.java @@ -0,0 +1,115 @@ +package bisq.core.xmr.jsonrpc.result; + +import java.io.Serializable; +import java.math.BigInteger; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class SubAddressBalance implements Serializable { + + /** + * + */ + private static final long serialVersionUID = -5840586182676538682L; + + @Expose + @SerializedName(value = "account_index") + private int accountIndex; + + @Expose + private String address; + + @Expose + @SerializedName(value = "address_index") + private int addressIndex; + + @Expose + private BigInteger balance; + + @Expose + @SerializedName(value = "blocks_to_unlock") + private long blocksToUnlock; + + @Expose + private String label; + + @Expose + @SerializedName(value = "num_unspent_outputs") + private int numUnSpentOutputs; + + @Expose + @SerializedName(value = "unlocked_balance") + private BigInteger unlockedBalance; + + @Override + public String toString() { + return "SubAddressBalance [accountIndex=" + accountIndex + ", address=" + address + ", addressIndex=" + + addressIndex + ", balance=" + balance + ", blocksToUnlock=" + blocksToUnlock + ", label=" + label + + ", numUnSpentOutputs=" + numUnSpentOutputs + ", unlockedBalance=" + unlockedBalance + "]"; + } + + public int getAccountIndex() { + return accountIndex; + } + + public void setAccountIndex(int accountIndex) { + this.accountIndex = accountIndex; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public int getAddressIndex() { + return addressIndex; + } + + public void setAddressIndex(int addressIndex) { + this.addressIndex = addressIndex; + } + + public BigInteger getBalance() { + return balance; + } + + public void setBalance(BigInteger balance) { + this.balance = balance; + } + + public long getBlocksToUnlock() { + return blocksToUnlock; + } + + public void setBlocksToUnlock(long blocksToUnlock) { + this.blocksToUnlock = blocksToUnlock; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public int getNumUnSpentOutputs() { + return numUnSpentOutputs; + } + + public void setNumUnSpentOutputs(int numUnSpentOutputs) { + this.numUnSpentOutputs = numUnSpentOutputs; + } + + public BigInteger getUnlockedBalance() { + return unlockedBalance; + } + + public void setUnlockedBalance(BigInteger unlockedBalance) { + this.unlockedBalance = unlockedBalance; + } +} diff --git a/core/src/main/java/bisq/core/xmr/jsonrpc/result/SubAddressIndex.java b/core/src/main/java/bisq/core/xmr/jsonrpc/result/SubAddressIndex.java new file mode 100644 index 00000000000..78186126fbb --- /dev/null +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/result/SubAddressIndex.java @@ -0,0 +1,41 @@ +package bisq.core.xmr.jsonrpc.result; + +import java.io.Serializable; + +import com.google.gson.annotations.Expose; + +public class SubAddressIndex implements Serializable { + + /** + * + */ + private static final long serialVersionUID = -887528883772924454L; + + @Expose + private long major; + + @Expose + private long minor; + + public long getMajor() { + return major; + } + + public void setMajor(long major) { + this.major = major; + } + + public long getMinor() { + return minor; + } + + public void setMinor(long minor) { + this.minor = minor; + } + + @Override + public String toString() { + return "SubAddressIndex [major=" + major + ", minor=" + minor + "]"; + } + +} diff --git a/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java b/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java index 46f8ab1d5ca..357d2bfc196 100644 --- a/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java +++ b/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java @@ -22,11 +22,11 @@ import java.util.Date; import bisq.core.locale.Res; +import bisq.core.xmr.jsonrpc.result.MoneroTransfer; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import monero.wallet.model.MoneroTxWallet; @Slf4j @EqualsAndHashCode @@ -48,35 +48,19 @@ public class XmrTxListItem { @Getter private long confirmations = 0; @Getter - private String key; - @Getter - private Integer mixin; - @Getter private Long unlockTime; @Getter private String destinationAddress; - public XmrTxListItem(MoneroTxWallet txWallet) { + public XmrTxListItem(MoneroTransfer txWallet) { txId = txWallet.getId(); paymentId = txWallet.getPaymentId(); - Long timestamp = txWallet.getBlock().getTimestamp(); + Long timestamp = txWallet.getTimestamp(); date = timestamp != null && timestamp != 0 ? Date.from(Instant.ofEpochSecond(timestamp)) : null; - confirmed = txWallet.isConfirmed(); - confirmations = txWallet.getNumConfirmations(); - key = txWallet.getKey(); - mixin = txWallet.getMixin(); + confirmed = txWallet.getConfirmations() >= txWallet.getSuggestedConfirmationsThreshold(); + confirmations = txWallet.getConfirmations(); unlockTime = txWallet.getUnlockTime(); - BigInteger valueSentToMe = txWallet.getIncomingAmount() != null ? txWallet.getIncomingAmount() : BigInteger.ZERO; - BigInteger valueSentFromMe = txWallet.getOutgoingAmount() != null ? txWallet.getOutgoingAmount() : BigInteger.ZERO; - if(txWallet.isOutgoing()) { - destinationAddress = txWallet.getOutgoingTransfer() != null && - txWallet.getOutgoingTransfer().getDestinations() != null ? - txWallet.getOutgoingTransfer().getDestinations().get(0).getAddress() : null; - } else { - destinationAddress = txWallet.getIncomingTransfers() != null ? - txWallet.getIncomingTransfers().get(0).getAddress() : null; - } - amount = txWallet.isIncoming() ? valueSentToMe : valueSentFromMe; - direction = txWallet.isIncoming() ? Res.get("shared.account.wallet.tx.item.in") : Res.get("shared.account.wallet.tx.item.out"); + amount = txWallet.getAmount(); + direction = "in".equals(txWallet.getType()) ? Res.get("shared.account.wallet.tx.item.in") : Res.get("shared.account.wallet.tx.item.out"); } } diff --git a/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java b/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java index 7bac24a57df..0c779e24e8d 100644 --- a/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java +++ b/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java @@ -6,12 +6,13 @@ import java.math.BigInteger; import java.net.InetSocketAddress; import java.net.Socket; +import java.time.Instant; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Predicate; import javax.inject.Inject; @@ -27,17 +28,14 @@ import bisq.common.app.DevEnv; import bisq.core.locale.Res; import bisq.core.user.Preferences; +import bisq.core.xmr.jsonrpc.MoneroRpcConnection; +import bisq.core.xmr.jsonrpc.MoneroSendPriority; +import bisq.core.xmr.jsonrpc.MoneroWalletRpc; +import bisq.core.xmr.jsonrpc.result.MoneroTransfer; +import bisq.core.xmr.jsonrpc.result.MoneroTx; import bisq.core.xmr.wallet.listeners.WalletUiListener; import javafx.application.Platform; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; import lombok.extern.slf4j.Slf4j; -import monero.rpc.MoneroRpcConnection; -import monero.wallet.MoneroWalletRpc; -import monero.wallet.model.MoneroSendPriority; -import monero.wallet.model.MoneroSendRequest; -import monero.wallet.model.MoneroTxSet; -import monero.wallet.model.MoneroTxWallet; @Slf4j @Singleton @@ -113,7 +111,7 @@ public void run() { } if(walletRpcData.containsKey("getTxs")) { time0 = System.currentTimeMillis(); - List txList = walletRpc.getTxs(); + List txList = walletRpc.getTxs(null); if(txList != null && !txList.isEmpty()) { walletRpcData.put("getTxs", transformTxWallet(txList)); log.debug("listen -time: {}ms - transactions: {}", (System.currentTimeMillis() - time0), txList.size()); @@ -134,12 +132,13 @@ public void run() { } } - private List transformTxWallet(List txList) { - Predicate predicate = new Predicate<>() { + private List transformTxWallet(List txList) { + Predicate predicate = new Predicate<>() { @Override - public boolean test(MoneroTxWallet t) { - return t.isRelayed(); + public boolean test(MoneroTransfer t) { + //Check if transaction occurred less than 90 days ago + return (new Date().getTime() - Date.from(Instant.ofEpochSecond(t.getTimestamp())).getTime()) <= 90 * 24 * 3600 * 1000; } }; List list = new ArrayList<>(); @@ -162,7 +161,7 @@ public void run() { searchParam.split(","); long time0 = System.currentTimeMillis(); - List txs = walletRpc.getTxs(Arrays.asList(searchParam.split(","))); + List txs = walletRpc.getTxs(searchParam); walletRpcData.put("getTxs", transformTxWallet(txs)); log.debug("listen -time: {}ms - searchTx: {}", (System.currentTimeMillis() - time0), txs.size()); } @@ -186,35 +185,29 @@ public void run() { checkNotNull(walletRpc, Res.get("mainView.networkWarning.localhostLost", "Monero")); listener.playAnimation(); long time0 = System.currentTimeMillis(); - MoneroSendRequest request = new MoneroSendRequest(accountIndex, address, amount, priority); - request.setDoNotRelay(doNotRelay); - MoneroTxSet txSet = walletRpc.send(request); + Map destination = new HashMap<>(); + destination.put("amount", amount); + destination.put("address", address); + List> destinations = new ArrayList>(); + destinations.add(destination); + Map request = new HashMap<>(); + request.put("destinations", destinations); + request.put("priority", priority.ordinal()); + request.put("payment_id", MoneroWalletRpc.generatePaymentId()); + request.put("get_tx_key", true); + request.put("get_tx_hex", false); + request.put("do_not_relay", true); + request.put("get_tx_metadata", true); + MoneroTx tx = walletRpc.send(request); - if(txSet != null && txSet.getTxs() != null && !txSet.getTxs().isEmpty()) { - MoneroTxWallet tx = txSet.getTxs().get(0); - walletRpcData.put("getBalance", walletRpc.getBalance()); - walletRpcData.put("getUnlockedBalance", walletRpc.getUnlockedBalance()); - walletRpcData.put("getDoNotRelay", tx.getDoNotRelay()); - walletRpcData.put("getExtra", tx.getExtra()); - walletRpcData.put("getFee", tx.getFee()); - walletRpcData.put("getId", tx.getId()); - walletRpcData.put("getKey", tx.getKey()); - walletRpcData.put("getLastRelayedTimestamp", tx.getLastRelayedTimestamp() != null ? new Date(tx.getLastRelayedTimestamp()) : null); - walletRpcData.put("getMixin", tx.getMixin()); - walletRpcData.put("getNumConfirmations", tx.getNumConfirmations()); - walletRpcData.put("getOutgoingAmount", tx.getOutgoingAmount()); - walletRpcData.put("getOutgoingTransfer", tx.getOutgoingTransfer()); - walletRpcData.put("getPaymentId", tx.getPaymentId()); - walletRpcData.put("getTimestamp", tx.getBlock().getTimestamp() != null ? new Date(tx.getBlock().getTimestamp()) : null); - walletRpcData.put("getSize", tx.getSize()); - walletRpcData.put("getUnlockTime", tx.getUnlockTime()); - walletRpcData.put("getVersion", tx.getVersion()); - if(doNotRelay) { - walletRpcData.put("txToRelay", tx); - } - log.debug("MoneroTxWallet => {}", walletRpcData); - log.debug("createTx -time: {}ms - createTx: {}", (System.currentTimeMillis() - time0), tx.getSize()); + walletRpcData.put("getBalance", walletRpc.getBalance()); + walletRpcData.put("getUnlockedBalance", walletRpc.getUnlockedBalance()); + walletRpcData.put("getFee", tx.getFee()); + if(doNotRelay) { + walletRpcData.put("txToRelay", tx.getTxMetadata()); } + log.debug("MoneroTxWallet => {}", walletRpcData); + log.debug("createTx -time: {}ms - createTx: {}", (System.currentTimeMillis() - time0), tx.getSize()); listener.onUpdateBalances(walletRpcData); listener.stopAnimation(); } @@ -233,14 +226,13 @@ public void relayTx(WalletUiListener listener, HashMap walletRpc public void run() { checkNotNull(walletRpc, Res.get("mainView.networkWarning.localhostLost", "Monero")); listener.playAnimation(); - MoneroTxWallet txToRelay = (MoneroTxWallet) walletRpcData.get("txToRelay"); + String txToRelay = (String) walletRpcData.get("txToRelay"); if(txToRelay != null) { - txToRelay.setDoNotRelay(false); long time0 = System.currentTimeMillis(); String txId = walletRpc.relayTx(txToRelay); walletRpcData.put("txId", txId); - walletRpcData.put("getMetadata", txToRelay.getMetadata()); - log.debug("relayTx metadata: {}", txToRelay.getMetadata()); + walletRpcData.put("getMetadata", txToRelay); + log.debug("relayTx metadata: {}", txToRelay); log.debug("relayTx -time: {}ms - txId: {}", (System.currentTimeMillis() - time0), txId); } listener.stopAnimation(); diff --git a/core/src/main/java/common/types/Filter.java b/core/src/main/java/common/types/Filter.java deleted file mode 100644 index e2a9ceddb57..00000000000 --- a/core/src/main/java/common/types/Filter.java +++ /dev/null @@ -1,43 +0,0 @@ -package common.types; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -/** - * Base filter. - */ -public interface Filter { - - /** - * Indicates if the given item meets the criteria of this filter. - * - * @param item is the item to test - * @return true if the item meets the criteria of this filter, false otherwise - */ - public boolean meetsCriteria(T item); - - /** - * Returns a new list comprised of elements from the given list that meet the - * filter's criteria. - * - * @param items are the items to filter - * @return the items that meet this filter's criteria - */ - public static List apply(Filter filter, List items) { - List filtered = new ArrayList(); - for (T item : items) if (filter.meetsCriteria(item)) filtered.add(item); - return filtered; - } - - /** - * Returns a new set comprised of elements from the given set that meet the - * filter's criteria. - * - * @param items are the items to filter - * @return the items that meet this filter's criteria - */ - public static Set apply(Filter filter, Set items) { - throw new RuntimeException("Not implemented"); - } -} diff --git a/core/src/main/java/common/types/Pair.java b/core/src/main/java/common/types/Pair.java deleted file mode 100644 index f94c8ee092e..00000000000 --- a/core/src/main/java/common/types/Pair.java +++ /dev/null @@ -1,37 +0,0 @@ -package common.types; - -/** - * Generic parameterized pair. - * - * @author woodser - * - * @param the type of the first element - * @param the type of the second element - */ -public class Pair { - - private F first; - private S second; - - public Pair(F first, S second) { - super(); - this.first = first; - this.second = second; - } - - public F getFirst() { - return first; - } - - public void setFirst(F first) { - this.first = first; - } - - public S getSecond() { - return second; - } - - public void setSecond(S second) { - this.second = second; - } -} diff --git a/core/src/main/java/common/utils/FieldDeserializer.java b/core/src/main/java/common/utils/FieldDeserializer.java deleted file mode 100644 index b6333297cee..00000000000 --- a/core/src/main/java/common/utils/FieldDeserializer.java +++ /dev/null @@ -1,72 +0,0 @@ -package common.utils; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; - -import common.types.JsonException; - -/** - * Deserializes specific fields to specified types. - * - * TODO: this does not properly deserialize field with e.g. List value - * - * @author woodser - */ -public class FieldDeserializer extends JsonDeserializer> { - - private Map fieldTypes; - - /** - * Constructs the deserializer with the given field names. - * - * @param fieldTypes specifies the names of fields to deserialize to specific types - */ - public FieldDeserializer(Map fieldTypes) { - super(); - this.fieldTypes = fieldTypes; - } - - @Override - public Map deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { - Map result = new HashMap(); - jp.nextToken(); - while (!JsonToken.END_OBJECT.equals(jp.getCurrentToken())) { - String tokenText = jp.getText(); - jp.nextToken(); - Object type = fieldTypes.get(tokenText); - if (type != null) { - if (type instanceof Class) result.put(tokenText, jp.readValueAs((Class) type)); - else if (type instanceof TypeReference) result.put(tokenText, jp.readValueAs((TypeReference) type)); - else throw new JsonException("Invalid deserialization type " + type.getClass() + " for field '" + tokenText + "'"); - } else { - - - if (JsonToken.START_OBJECT.equals(jp.getCurrentToken())) { - result.put(tokenText, deserialize(jp, ctxt)); - } else if (JsonToken.START_ARRAY.equals(jp.getCurrentToken())) { - jp.nextToken(); - List list = new ArrayList(); - while (!JsonToken.END_ARRAY.equals(jp.getCurrentToken())) { - list.add(deserialize(jp, ctxt)); - jp.nextToken(); - } - result.put(tokenText, list); - } else { - result.put(tokenText, jp.readValueAs(Object.class)); - } - } - jp.nextToken(); - } - return result; - } -} diff --git a/core/src/main/java/common/utils/FileUtils.java b/core/src/main/java/common/utils/FileUtils.java deleted file mode 100644 index db503503e21..00000000000 --- a/core/src/main/java/common/utils/FileUtils.java +++ /dev/null @@ -1,28 +0,0 @@ -package common.utils; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.Charset; - -/** - * Collection of utilities for working with files. - * - * @author woodser - */ -public class FileUtils { - - /** - * Writes string data to the given path. - * - * @param path is the path to write the data to - * @param data is the string data to write - * @throws IOException - */ - public static void write(String path, String data) { - try { - org.apache.commons.io.FileUtils.write(new File(path), data, Charset.defaultCharset()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/core/src/main/java/common/utils/MathUtils.java b/core/src/main/java/common/utils/MathUtils.java deleted file mode 100644 index 9de609ac911..00000000000 --- a/core/src/main/java/common/utils/MathUtils.java +++ /dev/null @@ -1,25 +0,0 @@ -package common.utils; - -import static org.junit.Assert.assertTrue; - -import java.util.Random; - -/** - * Collection of math utilities. - */ -public class MathUtils { - - private static Random random = new Random(); - - /** - * Returns a random integer between the given integers, inclusive. - * - * @param start is the start of the range, inclusive - * @param end is the end of the range, inclusive - * @return int is a random integer between start and end, inclusive - */ - public static int random(int start, int end) { - assertTrue(start <= end); - return random.nextInt(end - start) + start; - } -} diff --git a/core/src/main/java/common/utils/StringUtils.java b/core/src/main/java/common/utils/StringUtils.java deleted file mode 100644 index ce1dffcc3c1..00000000000 --- a/core/src/main/java/common/utils/StringUtils.java +++ /dev/null @@ -1,19 +0,0 @@ -package common.utils; - -/** - * Collection of string utilities. - */ -public class StringUtils { - - /** - * Returns the given number of tabs as a string. - * - * @param numTabs is the number of tabs - * @return String is the number of tabs as a string - */ - public static String getTabs(int numTabs) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < numTabs; i++) sb.append("\t"); - return sb.toString(); - } -} diff --git a/core/src/main/java/monero/daemon/MoneroDaemon.java b/core/src/main/java/monero/daemon/MoneroDaemon.java deleted file mode 100644 index 0445c76d41e..00000000000 --- a/core/src/main/java/monero/daemon/MoneroDaemon.java +++ /dev/null @@ -1,678 +0,0 @@ -/** - * Copyright (c) 2017-2019 woodser - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package monero.daemon; - -import java.math.BigInteger; -import java.util.Collection; -import java.util.List; - -import monero.daemon.model.MoneroAltChain; -import monero.daemon.model.MoneroBan; -import monero.daemon.model.MoneroBlock; -import monero.daemon.model.MoneroBlockHeader; -import monero.daemon.model.MoneroBlockTemplate; -import monero.daemon.model.MoneroMinerTxSum; -import monero.daemon.model.MoneroDaemonConnection; -import monero.daemon.model.MoneroDaemonInfo; -import monero.daemon.model.MoneroDaemonListener; -import monero.daemon.model.MoneroDaemonPeer; -import monero.daemon.model.MoneroDaemonSyncInfo; -import monero.daemon.model.MoneroDaemonUpdateCheckResult; -import monero.daemon.model.MoneroDaemonUpdateDownloadResult; -import monero.daemon.model.MoneroHardForkInfo; -import monero.daemon.model.MoneroKeyImageSpentStatus; -import monero.daemon.model.MoneroMiningStatus; -import monero.daemon.model.MoneroNetworkType; -import monero.daemon.model.MoneroOutput; -import monero.daemon.model.MoneroOutputDistributionEntry; -import monero.daemon.model.MoneroOutputHistogramEntry; -import monero.daemon.model.MoneroSubmitTxResult; -import monero.daemon.model.MoneroTx; -import monero.daemon.model.MoneroTxBacklogEntry; -import monero.daemon.model.MoneroTxPoolStats; - -/** - * Monero daemon interface. - */ -public interface MoneroDaemon { - - /** - * Indicates if the daemon is trusted or untrusted. - * - * @return true if the daemon is trusted, false otherwise - */ - public boolean isTrusted(); - - /** - * Get the number of blocks in the longest chain known to the node. - * - * @return the number of blocks - */ - public long getHeight(); - - /** - * Get a block's id by its height. - * - * @param height is the height of the block id to get - * @return the block's id at the given height - */ - public String getBlockId(long height); - - /** - * Get a block template for mining a new block. - * - * @param walletAddress is the address of the wallet to receive miner transactions if block is successfully mined - * @return a block template for mining a new block - */ - public MoneroBlockTemplate getBlockTemplate(String walletAddress); - - /** - * Get a block template for mining a new block. - * - * @param walletAddress is the address of the wallet to receive miner transactions if block is successfully mined - * @param reserveSize is the reserve size (optional) - * @return a block template for mining a new block - */ - public MoneroBlockTemplate getBlockTemplate(String walletAddress, Integer reserveSize); - - /** - * Get the last block's header. - * - * @return the last block's header - */ - public MoneroBlockHeader getLastBlockHeader(); - - /** - * Get a block header by its id. - * - * @param blockId is the id of the block to get the header of - * @return the block's header - */ - public MoneroBlockHeader getBlockHeaderById(String blockId); - - /** - * Get a block header by its height. - * - * @param height is the height of the block to get the header of - * @return the block's header - */ - public MoneroBlockHeader getBlockHeaderByHeight(long height); - - /** - * Get block headers for the given range. - * - * @param startHeight is the start height lower bound inclusive (optional) - * @param endHeight is the end height upper bound inclusive (optional) - * @return block headers in the given range - */ - public List getBlockHeadersByRange(Long startHeight, Long endHeight); - - /** - * Get a block by id. - * - * @param blockId is the id of the block to get - * @return the block with the given id - */ - public MoneroBlock getBlockById(String blockId); - - /** - * Get blocks by id. - * - * @param blockIds are array of hashes; first 10 blocks id goes sequential, - * next goes in pow(2,n) offset, like 2, 4, 8, 16, 32, 64 and so on, - * and the last one is always genesis block - * @param startHeight is the start height to get blocks by id - * @param prune specifies if returned blocks should be pruned (defaults to false) // TODO: test default - * @return the retrieved blocks - */ - public List getBlocksById(List blockIds, Long startHeight, Boolean prune); - - /** - * Get a block by height. - * - * @param height is the height of the block to get - * @return the block at the given height - */ - public MoneroBlock getBlockByHeight(long height); - - /** - * Get blocks at the given heights. - * - * @param heights are the heights of the blocks to get - * @return blocks at the given heights - */ - public List getBlocksByHeight(List heights); - - /** - * Get blocks in the given height range. - * - * @param startHeight is the start height lower bound inclusive (optional) - * @param endHeight is the end height upper bound inclusive (optional) - * @return blocks in the given height range - */ - public List getBlocksByRange(Long startHeight, Long endHeight); - - /** - * Get blocks in the given height range as chunked requests so that each request is - * not too big. - * - * @param startHeight is the start height lower bound inclusive (optional) - * @param endHeight is the end height upper bound inclusive (optional) - * @return blocks in the given height range - */ - public List getBlocksByRangeChunked(Long startHeight, Long endHeight); - - /** - * Get blocks in the given height range as chunked requests so that each request is - * not too big. - * - * @param startHeight is the start height lower bound inclusive (optional) - * @param endHeight is the end height upper bound inclusive (optional) - * @param maxChunkSize is the maximum chunk size in any one request (default 3,000,000 bytes) - * @return blocks in the given height range - */ - public List getBlocksByRangeChunked(Long startHeight, Long endHeight, Long maxChunkSize); - - /** - * Get block ids as a binary request to the daemon. - * - * @param blockIds specify block ids to fetch; first 10 blocks id goes - * sequential, next goes in pow(2,n) offset, like 2, 4, 8, 16, 32, 64 - * and so on, and the last one is always genesis block - * @param startHeight is the starting height of block ids to return - * @return the requested block ids - */ - public List getBlockIds(List blockIds, Long startHeight); - - /** - * Get a transaction by id. - * - * @param txId is the id of the transaction to get - * @return the transaction with the given id - */ - public MoneroTx getTx(String txId); - - /** - * Get a transaction by id. - * - * @param txId is the id of the transaction to get - * @param prune specifies if the returned tx should be pruned (defaults to false) - * @return the transaction with the given id - */ - public MoneroTx getTx(String txId, Boolean prune); - - /** - * Get transactions by ids. - * - * @param txIds are ids of transactions to get - * @return the transactions with the given ids - */ - public List getTxs(Collection txIds); - - /** - * Get transactions by ids. - * - * @param txIds are ids of transactions to get - * @param prune specifies if the returned txs should be pruned (defaults to false) - * @return the transactions with the given ids - */ - public List getTxs(Collection txIds, Boolean prune); - - /** - * Get a transaction hex by id. - * - * @param txId is the id of the transaction to get hex from - * @return the tx hex with the given id - */ - public String getTxHex(String txId); - - /** - * Get a transaction hex by id. - * - * @param txId is the id of the transaction to get hex from - * @param prune specifies if the returned tx hex should be pruned (defaults to false) - * @return the tx hex with the given id - */ - public String getTxHex(String txId, Boolean prune); - - /** - * Get transaction hexes by ids. - * - * @param txIds are ids of transactions to get hexes from - * @return are the tx hexes - */ - public List getTxHexes(Collection txIds); - - /** - * Get transaction hexes by ids. - * - * @param txIds are ids of transactions to get hexes from - * @param prune specifies if the returned tx hexes should be pruned (defaults to false) - * @return are the tx hexes - */ - public List getTxHexes(Collection txIds, Boolean prune); - - /** - * Gets the total emissions and fees from the genesis block to the current height. - * - * @param height is the height to start computing the miner sum - * @param numBlocks are the number of blocks to include in the sum - * @return the sum emission and fees since the geneis block - */ - public MoneroMinerTxSum getMinerTxSum(long height, Long numBlocks); - - /** - * Get the fee estimate per kB. - * - * @return is the fee estimate per kB. - */ - public BigInteger getFeeEstimate(); - - /** - * Get the fee estimate per kB. - * - * @param graceBlocks TODO - * @return is the fee estimate per kB. - */ - public BigInteger getFeeEstimate(Integer graceBlocks); - - /** - * Submits a transaction to the daemon's pool. - * - * @param txHex is the raw transaction hex to submit - * @return the submission results - */ - public MoneroSubmitTxResult submitTxHex(String txHex); - - /** - * Submits a transaction to the daemon's pool. - * - * @param txHex is the raw transaction hex to submit - * @param doNotRelay specifies if the tx should be relayed (optional) - * @return the submission results - */ - public MoneroSubmitTxResult submitTxHex(String txHex, Boolean doNotRelay); - - /** - * Relays a transaction by id. - * - * @param txId identifies the transaction to relay - */ - public void relayTxById(String txId); - - /** - * Relays transactions by id. - * - * @param txIds identify the transactions to relay - */ - public void relayTxsById(Collection txIds); - - /** - * Get valid transactions seen by the node but not yet mined into a block, as well - * as spent key image information for the tx pool. - * - * @return transactions in the transaction pool - */ - public List getTxPool(); - - /** - * Get ids of transactions in the transaction pool. - * - * @return ids of transactions in the transaction pool - */ - public List getTxPoolIds(); - - /** - * Get all transaction pool backlog. - * - * @return transaction pool backlog entries - */ - public List getTxPoolBacklog(); - - /** - * Get transaction pool statistics. - * - * @return statistics about the transaction pool - */ - public MoneroTxPoolStats getTxPoolStats(); - - /** - * Flushes all transactions from the tx pool. - */ - public void flushTxPool(); - - /** - * Flush transactions from the tx pool. - * - * @param ids are ids of transactions to flush - */ - public void flushTxPool(String... ids); - - /** - * Flush transactions from the tx pool. - * - * @param ids are ids of transactions to flush - */ - public void flushTxPool(Collection ids); - - /** - * Get the spent status of the given key image. - * - * @param keyImage is key image hex to get the status of - * @return the status of the key image - */ - public MoneroKeyImageSpentStatus getKeyImageSpentStatus(String keyImage); - - /** - * Get the spent status of each given key image. - * - * @param keyImages are hex key images to get the statuses of - * @return the spent status for each key image - */ - public List getKeyImageSpentStatuses(Collection keyImages); - - /** - * Get outputs identified by a list of output amounts and indices as a binary - * request. - * - * @param outputs identify each output by amount and index - * @return the identified outputs - */ - public List getOutputs(Collection outputs); - - /** - * Get a histogram of output amounts. For all amounts (possibly filtered by - * parameters), gives the number of outputs on the chain for that amount. - * RingCT outputs counts as 0 amount. - * - * @param amounts are amounts of outputs to make the histogram with - * @param minCount TODO - * @param maxCount TODO - * @param isUnlocked makes a histogram with outputs with the specified lock state - * @param recentCutoff TODO - * @return output histogram entries meeting the parameters - */ - public List getOutputHistogram(Collection amounts, Integer minCount, Integer maxCount, Boolean isUnlocked, Integer recentCutoff); - - /** - * Creates an output distribution. - * - * @param amounts are amounts of outputs to make the distribution with - * @return output distribution entries meeting the parameters - */ - public List getOutputDistribution(Collection amounts); - - /** - * Creates an output distribution. - * - * @param amounts are amounts of outputs to make the distribution with - * @param isCumulative specifies if the results should be cumulative (defaults to TODO) - * @param startHeight is the start height lower bound inclusive (optional) - * @param endHeight is the end height upper bound inclusive (optional) - * @return output distribution entries meeting the parameters - */ - public List getOutputDistribution(Collection amounts, Boolean isCumulative, Long startHeight, Long endHeight); - - /** - * Get general information about the state of the node and the network. - * - * @return general information about the node and network - */ - public MoneroDaemonInfo getInfo(); - - /** - * Get synchronization information. - * - * @return contains sync information - */ - public MoneroDaemonSyncInfo getSyncInfo(); - - /** - * Look up information regarding hard fork voting and readiness. - * - * @return hard fork information - */ - public MoneroHardForkInfo getHardForkInfo(); - - /** - * Get alternative chains seen by the node. - * - * @return alternative chains seen by the node - */ - public List getAltChains(); - - /** - * Get known block ids which are not on the main chain. - * - * @return known block ids which are not on the main chain - */ - public List getAltBlockIds(); - -// /** -// * Get the daemon's current download and upload bandwidth limits. -// * -// * @return MoneroDaemonBandwidthLimits contains the current upload and download bandwidth limits -// */ -// public MoneroDaemonBandwidthLimits getBandwidthLimits(); -// -// /** -// * Set the daemon's current download and upload bandwidth limits. -// * -// * @param downloadLimit is the download limit to set (-1 to reset to default, 0 or null to make no change) -// * @param uploadLimit is the upload limit to set (-1 to reset to default, 0 or null to make no change) -// * @return MoneroDaemonBandwidthLimits are the daemon's bandwidth limits after setting -// */ -// public MoneroDaemonBandwidthLimits setBandwidthLimits(Integer downloadLimit, Integer uploadLimit); - - /** - * Get the download bandwidth limit. - * - * @return is the download bandwidth limit - */ - public int getDownloadLimit(); - - /** - * Set the download bandwidth limit. - * - * @param limit is the download limit to set (-1 to reset to default) - * @return int is the new download limit after setting - */ - public int setDownloadLimit(int limit); - - /** - * Reset the download bandwidth limit. - * - * @return the download bandwidth limit after resetting - */ - public int resetDownloadLimit(); - - /** - * Get the upload bandwidth limit. - * - * @return is the upload bandwidth limit - */ - public int getUploadLimit(); - - /** - * Set the upload bandwidth limit. - * - * @param limit is the upload limit to set (-1 to reset to default) - * @return int is the new upload limit after setting - */ - public int setUploadLimit(int limit); - - /** - * Reset the upload bandwidth limit. - * - * @return the upload bandwidth limit after resetting - */ - public int resetUploadLimit(); - - /** - * Get known peers including their last known online status. - * - * @return known peers - */ - public List getKnownPeers(); - - /** - * Get incoming and outgoing connections to the node. - * - * @return the daemon's peer connections - */ - public List getConnections(); - - /** - * Limit number of outgoing peers. - * - * @param limit is the maximum number of outgoing peers - */ - public void setOutgoingPeerLimit(int limit); - - /** - * Limit number of incoming peers. - * - * @param limit is the maximum number of incoming peers - */ - public void setIncomingPeerLimit(int limit); - - /** - * Get peer bans. - * - * @return entries about banned peers - */ - public List getPeerBans(); - - /** - * Ban a peer node. - * - * @param ban contains information about a node to ban - */ - public void setPeerBan(MoneroBan ban); - - /** - * Ban peers nodes. - * - * @param bans are bans to apply against peer nodes - */ - public void setPeerBans(List bans); - - /** - * Start mining. - * - * @param address is the address given miner rewards if the daemon mines a block - * @param numThreads is the number of mining threads to run - * @param isBackground specifies if the miner should run in the background or not - * @param ignoreBattery specifies if the battery state (e.g. on laptop) should be ignored or not - */ - public void startMining(String address, Long numThreads, Boolean isBackground, Boolean ignoreBattery); - - /** - * Stop mining. - */ - public void stopMining(); - - /** - * Get the daemon's mining status. - * - * @return the daemon's mining status - */ - public MoneroMiningStatus getMiningStatus(); - - /** - * Submit a mined block to the network. - * - * @param blockBlob is the mined block to submit - */ - public void submitBlock(String blockBlob); - - /** - * Submit mined blocks to the network. - * - * @param blockBlobs are the mined blocks to submit - */ - public void submitBlocks(Collection blockBlobs); - - /** - * Check for update. - * - * @return the result of the update check - */ - public MoneroDaemonUpdateCheckResult checkForUpdate(); - - /** - * Download an update. - * - * @return the result of the update download - */ - public MoneroDaemonUpdateDownloadResult downloadUpdate(); - - /** - * Download an update. - * - * @param path is the path to download the update (optional) - * @return the result of the update download - */ - public MoneroDaemonUpdateDownloadResult downloadUpdate(String path); - - /** - * Safely disconnect and shut down the daemon. - */ - public void stop(); - - /** - * Get the header of the next block added to the chain. - * - * @return the header of the next block added to the chain - */ - public MoneroBlockHeader getNextBlockHeader(); - - /** - * Register a listener to be notified when blocks are added to the chain. - * - * @param listener is invoked when blocks are added to the chain - */ - public void addListener(MoneroDaemonListener listener); - - /** - * Unregister a listener to be notified when blocks are added to the chain. - * - * @param listener is a previously registered listener to be unregistered - */ - public void removeListener(MoneroDaemonListener listener); - - // ----------------------------- STATIC UTILITIES --------------------------- - - /** - * Parses a network string to an enumerated type. - * - * @param network is the network string to parse - * @return the enumerated network type - */ - public static MoneroNetworkType parseNetworkType(String network) { - if ("mainnet".equals(network)) return MoneroNetworkType.MAINNET; - if ("testnet".equals(network)) return MoneroNetworkType.TESTNET; - if ("stagenet".equals(network)) return MoneroNetworkType.STAGENET; - throw new Error("Invalid network type to parse: " + network); - } -} \ No newline at end of file diff --git a/core/src/main/java/monero/daemon/MoneroDaemonDefault.java b/core/src/main/java/monero/daemon/MoneroDaemonDefault.java deleted file mode 100644 index cc1738b5734..00000000000 --- a/core/src/main/java/monero/daemon/MoneroDaemonDefault.java +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright (c) 2017-2019 woodser - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package monero.daemon; - -import java.math.BigInteger; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import monero.daemon.model.MoneroBan; -import monero.daemon.model.MoneroBlock; -import monero.daemon.model.MoneroBlockTemplate; -import monero.daemon.model.MoneroDaemonUpdateDownloadResult; -import monero.daemon.model.MoneroKeyImageSpentStatus; -import monero.daemon.model.MoneroOutputDistributionEntry; -import monero.daemon.model.MoneroSubmitTxResult; -import monero.daemon.model.MoneroTx; - -/** - * Default Monero daemon implementation. - */ -public abstract class MoneroDaemonDefault implements MoneroDaemon { - - @Override - public MoneroBlockTemplate getBlockTemplate(String walletAddress) { - return getBlockTemplate(walletAddress, null); - } - - @Override - public List getBlocksByRangeChunked(Long startHeight, Long endHeight) { - return getBlocksByRangeChunked(startHeight, endHeight, null); - } - - @Override - public MoneroTx getTx(String txId) { - return getTx(txId, null); - } - - @Override - public MoneroTx getTx(String txId, Boolean prune) { - return getTxs(Arrays.asList(txId), prune).get(0); - } - - @Override - public List getTxs(Collection txIds) { - return getTxs(txIds, null); - } - - @Override - public String getTxHex(String txId) { - return getTxHex(txId, false); - } - - @Override - public String getTxHex(String txId, Boolean prune) { - return getTxHexes(Arrays.asList(txId), prune).get(0); - } - - @Override - public List getTxHexes(Collection txIds) { - return getTxHexes(txIds, null); - } - - @Override - public BigInteger getFeeEstimate() { - return getFeeEstimate(null); - } - - @Override - public MoneroSubmitTxResult submitTxHex(String txHex) { - return submitTxHex(txHex, false); - } - - @Override - public void relayTxById(String txId) { - relayTxsById(Arrays.asList(txId)); - } - - @Override - public MoneroKeyImageSpentStatus getKeyImageSpentStatus(String keyImage) { - return getKeyImageSpentStatuses(Arrays.asList(keyImage)).get(0); - } - - @Override - public List getOutputDistribution(Collection amounts) { - return getOutputDistribution(amounts, null, null, null); - } - - @Override - public void setPeerBan(MoneroBan ban) { - setPeerBans(Arrays.asList(ban)); - } - - @Override - public void submitBlock(String blockBlob) { - submitBlocks(Arrays.asList(blockBlob)); - } - - @Override - public MoneroDaemonUpdateDownloadResult downloadUpdate() { - return downloadUpdate(null); - } -} diff --git a/core/src/main/java/monero/daemon/MoneroDaemonRpc.java b/core/src/main/java/monero/daemon/MoneroDaemonRpc.java deleted file mode 100644 index 6d4732a3e4d..00000000000 --- a/core/src/main/java/monero/daemon/MoneroDaemonRpc.java +++ /dev/null @@ -1,1573 +0,0 @@ -/** - * Copyright (c) 2017-2019 woodser - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package monero.daemon; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.math.BigInteger; -import java.net.URI; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import org.apache.log4j.Logger; - -import com.fasterxml.jackson.core.type.TypeReference; - -import common.utils.GenUtils; -import common.utils.JsonUtils; -import monero.daemon.model.MoneroAltChain; -import monero.daemon.model.MoneroBan; -import monero.daemon.model.MoneroBlock; -import monero.daemon.model.MoneroBlockHeader; -import monero.daemon.model.MoneroBlockTemplate; -import monero.daemon.model.MoneroDaemonConnection; -import monero.daemon.model.MoneroDaemonConnectionSpan; -import monero.daemon.model.MoneroDaemonInfo; -import monero.daemon.model.MoneroDaemonListener; -import monero.daemon.model.MoneroDaemonPeer; -import monero.daemon.model.MoneroDaemonSyncInfo; -import monero.daemon.model.MoneroDaemonUpdateCheckResult; -import monero.daemon.model.MoneroDaemonUpdateDownloadResult; -import monero.daemon.model.MoneroHardForkInfo; -import monero.daemon.model.MoneroKeyImage; -import monero.daemon.model.MoneroKeyImageSpentStatus; -import monero.daemon.model.MoneroMinerTxSum; -import monero.daemon.model.MoneroMiningStatus; -import monero.daemon.model.MoneroNetworkType; -import monero.daemon.model.MoneroOutput; -import monero.daemon.model.MoneroOutputDistributionEntry; -import monero.daemon.model.MoneroOutputHistogramEntry; -import monero.daemon.model.MoneroSubmitTxResult; -import monero.daemon.model.MoneroTx; -import monero.daemon.model.MoneroTxBacklogEntry; -import monero.daemon.model.MoneroTxPoolStats; -import monero.rpc.MoneroRpcConnection; -import monero.rpc.MoneroRpcException; -import monero.utils.MoneroCppUtils; -import monero.utils.MoneroException; -import monero.utils.MoneroUtils; - -/** - * Implements a Monero daemon using monero-daemon-rpc. - * - * TODO: every call needs to checkResponseStatus - */ -public class MoneroDaemonRpc extends MoneroDaemonDefault { - - // static variables - private static final Logger LOGGER = Logger.getLogger(MoneroDaemonRpc.class); - private static final String DEFAULT_ID = "0000000000000000000000000000000000000000000000000000000000000000"; - private static long MAX_REQ_SIZE = 3000000; // max request size when fetching blocks from daemon - private static int NUM_HEADERS_PER_REQ = 750; - - // instance variables - private MoneroRpcConnection rpc; - private MoneroDaemonPoller daemonPoller; - private Map cachedHeaders; - - public MoneroDaemonRpc(URI uri) { - this(new MoneroRpcConnection(uri)); - } - - public MoneroDaemonRpc(String uri) { - this(new MoneroRpcConnection(uri)); - } - - public MoneroDaemonRpc(String uri, String username, String password) { - this(new MoneroRpcConnection(uri, username, password)); - } - - public MoneroDaemonRpc(URI uri, String username, String password) { - this(new MoneroRpcConnection(uri, username, password)); - } - - public MoneroDaemonRpc(MoneroRpcConnection rpc) { - assertNotNull(rpc); - this.rpc = rpc; - this.daemonPoller = new MoneroDaemonPoller(this); - this.cachedHeaders = new HashMap(); - } - - /** - * Get the daemon's RPC connection. - * - * @return the daemon's rpc connection - */ - public MoneroRpcConnection getRpcConnection() { - return this.rpc; - } - - /** - * Indicates if the client is connected to the daemon via RPC. - * - * @return true if the client is connected to the daemon, false otherwise - */ - public boolean isConnected() { - try { - getHeight(); - return true; - } catch (MoneroException e) { - return false; - } - } - - @Override - public boolean isTrusted() { - Map resp = rpc.sendPathRequest("get_height"); - checkResponseStatus(resp); - return !(boolean) resp.get("untrusted"); - } - - @SuppressWarnings("unchecked") - @Override - public long getHeight() { - Map respMap = rpc.sendJsonRequest("get_block_count"); - Map resultMap = (Map) respMap.get("result"); - return ((BigInteger) resultMap.get("count")).intValue(); - } - - @Override - public String getBlockId(long height) { - Map respMap = rpc.sendJsonRequest("on_get_block_hash", Arrays.asList(height)); - return (String) respMap.get("result"); - } - - @SuppressWarnings("unchecked") - @Override - public MoneroBlockTemplate getBlockTemplate(String walletAddress, Integer reserveSize) { - Map params = new HashMap(); - params.put("wallet_address", walletAddress); - params.put("reserve_size", reserveSize); - Map respMap = rpc.sendJsonRequest("get_block_template", params); - Map resultMap = (Map) respMap.get("result"); - MoneroBlockTemplate template = convertRpcBlockTemplate(resultMap); - return template; - } - - @SuppressWarnings("unchecked") - @Override - public MoneroBlockHeader getLastBlockHeader() { - Map respMap = rpc.sendJsonRequest("get_last_block_header"); - Map resultMap = (Map) respMap.get("result"); - MoneroBlockHeader header = convertRpcBlockHeader((Map) resultMap.get("block_header")); - return header; - } - - @SuppressWarnings("unchecked") - @Override - public MoneroBlockHeader getBlockHeaderById(String blockId) { - Map params = new HashMap(); - params.put("hash", blockId); - Map respMap = rpc.sendJsonRequest("get_block_header_by_hash", params); - Map resultMap = (Map) respMap.get("result"); - MoneroBlockHeader header = convertRpcBlockHeader((Map) resultMap.get("block_header")); - return header; - } - - @SuppressWarnings("unchecked") - @Override - public MoneroBlockHeader getBlockHeaderByHeight(long height) { - Map params = new HashMap(); - params.put("height", height); - Map respMap = rpc.sendJsonRequest("get_block_header_by_height", params); - Map resultMap = (Map) respMap.get("result"); - MoneroBlockHeader header = convertRpcBlockHeader((Map) resultMap.get("block_header")); - return header; - } - - @SuppressWarnings("unchecked") - @Override - public List getBlockHeadersByRange(Long startHeight, Long endHeight) { - Map params = new HashMap(); - params.put("start_height", startHeight); - params.put("end_height", endHeight); - Map respMap = rpc.sendJsonRequest("get_block_headers_range", params); - Map resultMap = (Map) respMap.get("result"); - List> rpcHeaders = (List>) resultMap.get("headers"); - List headers = new ArrayList(); - for (Map rpcHeader : rpcHeaders) { - MoneroBlockHeader header = convertRpcBlockHeader(rpcHeader); - headers.add(header); - } - return headers; - } - - @SuppressWarnings("unchecked") - @Override - public MoneroBlock getBlockById(String blockId) { - Map params = new HashMap(); - params.put("hash", blockId); - Map respMap = rpc.sendJsonRequest("get_block", params); - Map resultMap = (Map) respMap.get("result"); - MoneroBlock block = convertRpcBlock(resultMap); - return block; - } - - @Override - public List getBlocksById(List blockIds, Long startHeight, Boolean prune) { - throw new RuntimeException("Not implemented"); - } - - @SuppressWarnings("unchecked") - @Override - public MoneroBlock getBlockByHeight(long height) { - Map params = new HashMap(); - params.put("height", height); - Map respMap = rpc.sendJsonRequest("get_block", params); - Map rpcBlock = (Map) respMap.get("result"); - MoneroBlock block = convertRpcBlock((Map) rpcBlock); - return block; - } - - @SuppressWarnings({ "unchecked" }) - @Override - public List getBlocksByHeight(List heights) { - - // fetch blocks in binary - Map params = new HashMap(); - params.put("heights", heights); - byte[] respBin = rpc.sendBinaryRequest("get_blocks_by_height.bin", params); - - // convert binary blocks to map - Map rpcResp = MoneroCppUtils.binaryBlocksToMap(respBin); - checkResponseStatus(rpcResp); - - // build blocks with transactions - List blocks = new ArrayList(); - List> rpcBlocks = (List>) rpcResp.get("blocks"); - List>> rpcTxs = (List>>) rpcResp.get("txs"); - assertEquals(rpcBlocks.size(), rpcTxs.size()); - for (int blockIdx = 0; blockIdx < rpcBlocks.size(); blockIdx++) { - - // build block - MoneroBlock block = convertRpcBlock(rpcBlocks.get(blockIdx)); - block.setHeight(heights.get(blockIdx)); - blocks.add(block); - - // build transactions - List txs = new ArrayList(); - for (int txIdx = 0; txIdx < rpcTxs.get(blockIdx).size(); txIdx++) { - MoneroTx tx = new MoneroTx(); - txs.add(tx); - List txIds = (List) rpcBlocks.get(blockIdx).get("tx_hashes"); - tx.setId(txIds.get(txIdx)); - tx.setIsConfirmed(true); - tx.setInTxPool(false); - tx.setIsMinerTx(false); - tx.setDoNotRelay(false); - tx.setIsRelayed(true); - tx.setIsFailed(false); - tx.setIsDoubleSpendSeen(false); - List> blockTxs = (List>) rpcTxs.get(blockIdx); - convertRpcTx(blockTxs.get(txIdx), tx); - } - - // merge into one block - block.setTxs(new ArrayList()); - for (MoneroTx tx : txs) { - if (tx.getBlock() != null) block.merge(tx.getBlock()); - else block.getTxs().add(tx.setBlock(block)); - } - } - - return blocks; - } - - @Override - public List getBlocksByRange(Long startHeight, Long endHeight) { - if (startHeight == null) startHeight = 0l; - if (endHeight == null) endHeight = getHeight() - 1; - List heights = new ArrayList(); - for (long height = startHeight; height <= endHeight; height++) heights.add(height); - return getBlocksByHeight(heights); - } - - @Override - public List getBlocksByRangeChunked(Long startHeight, Long endHeight, Long maxChunkSize) { - if (startHeight == null) startHeight = 0l; - if (endHeight == null) endHeight = getHeight() - 1; - long lastHeight = startHeight - 1; - List blocks = new ArrayList(); - while (lastHeight < endHeight) { - blocks.addAll(getMaxBlocks(lastHeight + 1, endHeight, maxChunkSize)); - lastHeight = blocks.get(blocks.size() - 1).getHeight(); - } - return blocks; - } - - @Override - public List getBlockIds(List blockIds, Long startHeight) { - throw new RuntimeException("Not implemented"); - } - - @SuppressWarnings("unchecked") - @Override - public List getTxs(Collection txIds, Boolean prune) { - - // validate input - if (txIds.isEmpty()) throw new MoneroException("Must provide an array of transaction ids"); - - // fetch transactions - Map params = new HashMap(); - params.put("txs_hashes", txIds); - params.put("decode_as_json", true); - params.put("prune", prune); - Map respMap = rpc.sendPathRequest("get_transactions", params); - try { - checkResponseStatus(respMap); - } catch (MoneroException e) { - if (e.getMessage().indexOf("Failed to parse hex representation of transaction hash") >= 0) throw new MoneroException("Invalid transaction id", e.getCode()); - throw e; - } - - // interpret response - List> rpcTxs = (List>) respMap.get("txs"); - - // build transaction models - List txs = new ArrayList(); - if (rpcTxs != null) { - for (int i = 0; i < rpcTxs.size(); i++) { - MoneroTx tx = new MoneroTx(); - tx.setIsMinerTx(false); - txs.add(convertRpcTx(rpcTxs.get(i), tx)); - } - } - - // fetch unconfirmed txs from pool and merge additional fields // TODO monero-daemon-rpc: merge rpc calls so this isn't necessary? - //System.out.println("Fetching from pool..."); // TODO monero core: getTxPool() can get stuck under certain conditions (observed it before coordinating tx pool as part of tests, so double spend related?) - List poolTxs = getTxPool(); - for (MoneroTx tx : txs) { - for (MoneroTx poolTx : poolTxs) { - if (tx.getId().equals(poolTx.getId())) tx.merge(poolTx); - } - } - - return txs; - } - - @Override - public List getTxHexes(Collection txIds, Boolean prune) { - List hexes = new ArrayList(); - for (MoneroTx tx : getTxs(txIds, prune)) hexes.add(Boolean.TRUE.equals(prune) ? tx.getPrunedHex() : tx.getFullHex()); - return hexes; - } - - @SuppressWarnings("unchecked") - @Override - public MoneroMinerTxSum getMinerTxSum(long height, Long numBlocks) { - assertTrue("Height must be an integer >= 0", height >= 0); - if (numBlocks == null) numBlocks = getHeight(); - else assertTrue("Count must be an integer >= 0", numBlocks >= 0); - Map params = new HashMap(); - params.put("height", height); - params.put("count", numBlocks); - Map respMap = rpc.sendJsonRequest("get_coinbase_tx_sum", params); - Map resultMap = (Map) respMap.get("result"); - checkResponseStatus(resultMap); - MoneroMinerTxSum txSum = new MoneroMinerTxSum(); - txSum.setEmissionSum((BigInteger) resultMap.get("emission_amount")); - txSum.setFeeSum((BigInteger) resultMap.get("fee_amount")); - return txSum; - } - - @SuppressWarnings("unchecked") - @Override - public BigInteger getFeeEstimate(Integer graceBlocks) { - Map resp = rpc.sendJsonRequest("get_fee_estimate"); - Map result = (Map) resp.get("result"); - checkResponseStatus(result); - return (BigInteger) result.get("fee"); - } - - @Override - public MoneroSubmitTxResult submitTxHex(String txHex, Boolean doNotRelay) { - Map params = new HashMap(); - params.put("tx_as_hex", txHex); - params.put("do_not_relay", doNotRelay); - Map resp = rpc.sendPathRequest("send_raw_transaction", params); - MoneroSubmitTxResult submitResult = convertRpcSubmitTxResult(resp); - - // set isGood based on status - try { - checkResponseStatus(resp); - submitResult.setIsGood(true); - } catch (MoneroException e) { - submitResult.setIsGood(false); - } - return submitResult; - } - - @SuppressWarnings("unchecked") - @Override - public void relayTxsById(Collection txIds) { - Map params = new HashMap(); - params.put("txids", txIds); - Map resp = rpc.sendJsonRequest("relay_tx", params); - checkResponseStatus((Map) resp.get("result")); - } - - @SuppressWarnings("unchecked") - @Override - public List getTxPool() { - - - // send rpc request - Map resp = rpc.sendPathRequest("get_transaction_pool"); - checkResponseStatus(resp); - - // build txs - List txs = new ArrayList(); - if (resp.containsKey("transactions")) { - for (Map rpcTx : (List>) resp.get("transactions")) { - MoneroTx tx = new MoneroTx(); - txs.add(tx); - tx.setIsConfirmed(false); - tx.setIsMinerTx(false); - tx.setInTxPool(true); - tx.setNumConfirmations(0l); - convertRpcTx(rpcTx, tx); - } - } - - return txs; - } - - @Override - public List getTxPoolIds() { - throw new RuntimeException("Not implemented"); - } - - @Override - public List getTxPoolBacklog() { - throw new RuntimeException("Not implemented"); - } - - @Override - public MoneroTxPoolStats getTxPoolStats() { - throw new MoneroException("Response contains field 'histo' which is binary'"); -// let resp = await this.config.rpc.sendPathRequest("get_transaction_pool_stats"); -// MoneroDaemonRpc._checkResponseStatus(resp); -// let stats = MoneroDaemonRpc._convertRpcTxPoolStats(resp.pool_stats); -// -// // uninitialize some stats if not applicable -// if (stats.getHisto98pc() === 0) stats.setHisto98pc(undefined); -// if (stats.getNumTxs() === 0) { -// stats.setBytesMin(undefined); -// stats.setBytesMed(undefined); -// stats.setBytesMax(undefined); -// stats.setHisto98pc(undefined); -// stats.setOldestTimestamp(undefined); -// } -// -// return stats; - } - - @Override - public void flushTxPool() { - flushTxPool(new String[0]); - } - - @SuppressWarnings("unchecked") - @Override - public void flushTxPool(String... ids) { - Map params = new HashMap(); - params.put("txids", ids); - Map resp = rpc.sendJsonRequest("flush_txpool", params); - checkResponseStatus((Map) resp.get("result")); - } - - @Override - public void flushTxPool(Collection ids) { - flushTxPool(ids.toArray(new String[0])); - } - - @SuppressWarnings("unchecked") - @Override - public List getKeyImageSpentStatuses(Collection keyImages) { - if (keyImages == null || keyImages.isEmpty()) throw new MoneroException("Must provide key images to check the status of"); - Map params = new HashMap(); - params.put("key_images", keyImages); - Map resp = rpc.sendPathRequest("is_key_image_spent", params); - checkResponseStatus(resp); - List statuses = new ArrayList(); - for (BigInteger bi : (List) resp.get("spent_status")) { - statuses.add(MoneroKeyImageSpentStatus.valueOf(bi.intValue())); - } - return statuses; - } - - @Override - public List getOutputs(Collection outputs) { - throw new RuntimeException("Not implemented"); - } - - @SuppressWarnings("unchecked") - @Override - public List getOutputHistogram(Collection amounts, Integer minCount, Integer maxCount, Boolean isUnlocked, Integer recentCutoff) { - - // build request params - Map params = new HashMap(); - params.put("amounts", amounts); - params.put("min_count", minCount); - params.put("max_count", maxCount); - params.put("unlocked", isUnlocked); - params.put("recent_cutoff", recentCutoff); - - // send rpc request - Map resp = rpc.sendJsonRequest("get_output_histogram", params); - Map result = (Map) resp.get("result"); - checkResponseStatus(result); - - // build histogram entries from response - List entries = new ArrayList(); - if (!result.containsKey("histogram")) return entries; - for (Map rpcEntry : (List>) result.get("histogram")) { - entries.add(convertRpcOutputHistogramEntry(rpcEntry)); - } - return entries; - } - - @Override - public List getOutputDistribution(Collection amounts, Boolean isCumulative, Long startHeight, Long endHeight) { - throw new RuntimeException("Not implemented (response 'distribution' field is binary)"); -// let amountStrs = []; -// for (let amount of amounts) amountStrs.push(amount.toJSValue()); -// console.log(amountStrs); -// console.log(cumulative); -// console.log(startHeight); -// console.log(endHeight); -// -// // send rpc request -// console.log("*********** SENDING REQUEST *************"); -// if (startHeight === undefined) startHeight = 0; -// let resp = await this.config.rpc.sendJsonRequest("get_output_distribution", { -// amounts: amountStrs, -// cumulative: cumulative, -// from_height: startHeight, -// to_height: endHeight -// }); -// -// console.log("RESPONSE"); -// console.log(resp); -// -// // build distribution entries from response -// let entries = []; -// if (!resp.result.distributions) return entries; -// for (let rpcEntry of resp.result.distributions) { -// let entry = MoneroDaemonRpc._convertRpcOutputDistributionEntry(rpcEntry); -// entries.push(entry); -// } -// return entries; - } - - @SuppressWarnings("unchecked") - @Override - public MoneroDaemonInfo getInfo() { - Map resp = rpc.sendJsonRequest("get_info"); - Map result = (Map) resp.get("result"); - checkResponseStatus(result); - return convertRpcInfo(result); - } - - @SuppressWarnings("unchecked") - @Override - public MoneroDaemonSyncInfo getSyncInfo() { - Map resp = rpc.sendJsonRequest("sync_info"); - Map result = (Map) resp.get("result"); - checkResponseStatus(result); - return convertRpcSyncInfo(result); - } - - @SuppressWarnings("unchecked") - @Override - public MoneroHardForkInfo getHardForkInfo() { - Map resp = rpc.sendJsonRequest("hard_fork_info"); - Map result = (Map) resp.get("result"); - checkResponseStatus(result); - return convertRpcHardForkInfo(result); - } - - @SuppressWarnings("unchecked") - @Override - public List getAltChains() { - Map resp = rpc.sendJsonRequest("get_alternate_chains"); - Map result = (Map) resp.get("result"); - checkResponseStatus(result); - List chains = new ArrayList(); - if (!result.containsKey("chains")) return chains; - for (Map rpcChain : (List>) result.get("chains")) chains.add(convertRpcAltChain(rpcChain)); - return chains; - } - - @SuppressWarnings("unchecked") - @Override - public List getAltBlockIds() { - Map resp = rpc.sendPathRequest("get_alt_blocks_hashes"); - checkResponseStatus(resp); - if (!resp.containsKey("blks_hashes")) return new ArrayList(); - return (List) resp.get("blks_hashes"); - } - - @Override - public int getDownloadLimit() { - return getBandwidthLimits()[0]; - } - - @Override - public int setDownloadLimit(int limit) { - if (limit == -1) return resetDownloadLimit(); - if (limit <= 0) throw new MoneroException("Download limit must be an integer greater than 0"); - return setBandwidthLimits(limit, 0)[0]; - } - - @Override - public int resetDownloadLimit() { - return setBandwidthLimits(-1, 0)[0]; - } - - @Override - public int getUploadLimit() { - return getBandwidthLimits()[1]; - } - - @Override - public int setUploadLimit(int limit) { - if (limit == -1) return resetUploadLimit(); - if (limit <= 0) throw new MoneroException("Upload limit must be an integer greater than 0"); - return setBandwidthLimits(0, limit)[1]; - } - - @Override - public int resetUploadLimit() { - return setBandwidthLimits(0, -1)[1]; - } - - @SuppressWarnings("unchecked") - @Override - public List getConnections() { - Map resp = rpc.sendJsonRequest("get_connections"); - Map result = (Map) resp.get("result"); - checkResponseStatus(result); - List connections = new ArrayList(); - if (!result.containsKey("connections")) return connections; - for (Map rpcConnection : (List>) result.get("connections")) { - connections.add(convertRpcConnection(rpcConnection)); - } - return connections; - } - - @SuppressWarnings("unchecked") - @Override - public List getKnownPeers() { - - // send request - Map respMap = rpc.sendPathRequest("get_peer_list"); - checkResponseStatus(respMap); - - // build peers - List peers = new ArrayList(); - if (respMap.containsKey("gray_list")) { - for (Map rpcPeer : (List>) respMap.get("gray_list")) { - MoneroDaemonPeer peer = convertRpcPeer(rpcPeer); - peer.setIsOnline(false); // gray list means offline last checked - peers.add(peer); - } - } - if (respMap.containsKey("white_list")) { - for (Map rpcPeer : (List>) respMap.get("white_list")) { - MoneroDaemonPeer peer = convertRpcPeer(rpcPeer); - peer.setIsOnline(true); // white list means online last checked - peers.add(peer); - } - } - return peers; - } - - @Override - public void setOutgoingPeerLimit(int limit) { - if (limit < 0) throw new MoneroException("Outgoing peer limit must be >= 0"); - Map params = new HashMap(); - params.put("out_peers", limit); - Map resp = rpc.sendPathRequest("out_peers", params); - checkResponseStatus(resp); - } - - @Override - public void setIncomingPeerLimit(int limit) { - if (limit < 0) throw new MoneroException("Incoming peer limit must be >= 0"); - Map params = new HashMap(); - params.put("in_peers", limit); - Map resp = rpc.sendPathRequest("in_peers", params); - checkResponseStatus(resp); - } - - @SuppressWarnings("unchecked") - @Override - public List getPeerBans() { - Map resp = (Map) rpc.sendJsonRequest("get_bans"); - Map result = (Map) resp.get("result"); - checkResponseStatus(result); - List bans = new ArrayList(); - for (Map rpcBan : (List>) result.get("bans")) { - MoneroBan ban = new MoneroBan(); - ban.setHost((String) rpcBan.get("host")); - ban.setIp(((BigInteger) rpcBan.get("ip")).intValue()); - ban.setSeconds(((BigInteger) rpcBan.get("seconds")).longValue()); - bans.add(ban); - } - return bans; - } - - @SuppressWarnings("unchecked") - @Override - public void setPeerBans(List bans) { - List> rpcBans = new ArrayList>(); - for (MoneroBan ban : bans) rpcBans.add(convertToRpcBan(ban)); - Map params = new HashMap(); - params.put("bans", rpcBans); - Map resp = rpc.sendJsonRequest("set_bans", params); - checkResponseStatus((Map) resp.get("result")); - } - -// async setOutgoingPeerLimit(limit) { -// assert(GenUtils.isInt(limit) && limit >= 0, "Outgoing peer limit must be >= 0"); -// let resp = this.config.rpc.sendPathRequest("out_peers", {out_peers: limit}); -// MoneroDaemonRpc._checkResponseStatus(resp); -// } -// -// async setIncomingPeerLimit(limit) { -// assert(GenUtils.isInt(limit) && limit >= 0, "Incoming peer limit must be >= 0"); -// let resp = this.config.rpc.sendPathRequest("in_peers", {in_peers: limit}); -// MoneroDaemonRpc._checkResponseStatus(resp); -// } -// -// async getPeerBans() { -// Map resp = rpc.sendJsonRequest("get_bans"); -// MoneroDaemonRpc._checkResponseStatus(resp.result); -// let bans = []; -// for (let rpcBan of resp.result.bans) { -// let ban = new MoneroBan(); -// ban.setHost(rpcBan.host); -// ban.setIp(rpcBan.ip); -// ban.setSeconds(rpcBan.seconds); -// bans.push(ban); -// } -// return bans; -// } -// -// async setPeerBan(ban) { -// return this.setPeerBans([ban]); -// } -// -// async setPeerBans(bans) { -// let rpcBans = []; -// for (let ban of bans) rpcBans.push(MoneroDaemonRpc._convertRpcBan(ban)); -// List resp = rpc.sendJsonRequest("set_bans", {bans: rpcBans}); -// MoneroDaemonRpc._checkResponseStatus(resp.result); -// } - - @Override - public void startMining(String address, Long numThreads, Boolean isBackground, Boolean ignoreBattery) { - if (address == null || address.isEmpty()) throw new MoneroException("Must provide address to mine to"); - if (numThreads == null || numThreads <= 0) throw new MoneroException("Number of threads must be an integer greater than 0"); - Map params = new HashMap(); - params.put("miner_address", address); - params.put("threads_count", numThreads); - params.put("do_background_mining", isBackground); - params.put("ignore_battery", ignoreBattery); - Map resp = rpc.sendPathRequest("start_mining", params); - checkResponseStatus(resp); - } - - @Override - public void stopMining() { - Map resp = rpc.sendPathRequest("stop_mining"); - checkResponseStatus(resp); - } - - @Override - public MoneroMiningStatus getMiningStatus() { - Map resp = rpc.sendPathRequest("mining_status"); - checkResponseStatus(resp); - return convertRpcMiningStatus(resp); - } - - @SuppressWarnings("unchecked") - @Override - public void submitBlocks(Collection blockBlobs) { - if (blockBlobs.isEmpty()) throw new MoneroException("Must provide an array of mined block blobs to submit"); - Map resp = rpc.sendJsonRequest("submit_block", blockBlobs); - checkResponseStatus((Map) resp.get("result")); - } - - @Override - public MoneroDaemonUpdateCheckResult checkForUpdate() { - Map params = new HashMap(); - params.put("command", "check"); - Map respMap = rpc.sendPathRequest("update", params); - checkResponseStatus(respMap); - return convertRpcUpdateCheckResult(respMap); - } - - @Override - public MoneroDaemonUpdateDownloadResult downloadUpdate(String path) { - Map params = new HashMap(); - params.put("command", "download"); - params.put("path", path); - Map resp = rpc.sendPathRequest("update", params); - checkResponseStatus(resp); - return convertRpcUpdateDownloadResult(resp); - } - - @Override - public void stop() { - Map resp = rpc.sendPathRequest("stop_daemon"); - checkResponseStatus(resp); - } - - @Override - public MoneroBlockHeader getNextBlockHeader() { - Object syncObject = new Object(); - synchronized(syncObject) { - try { - MoneroDaemonListener customListener = new MoneroDaemonListener() { - @Override - public void onBlockHeader(MoneroBlockHeader header) { - super.onBlockHeader(header); - synchronized(syncObject) { - syncObject.notifyAll(); - } - } - }; - addListener(customListener); - syncObject.wait(); - removeListener(customListener); - return customListener.getLastBlockHeader(); - } catch (InterruptedException e) { - throw new MoneroException(e); - } - } - } - - @Override - public void addListener(MoneroDaemonListener listener) { - daemonPoller.addListener(listener); - } - - @Override - public void removeListener(MoneroDaemonListener listener) { - daemonPoller.removeListener(listener); - } - - // ------------------------------- PRIVATE INSTANCE ---------------------------- - - private int[] getBandwidthLimits() { - Map resp = rpc.sendPathRequest("get_limit"); - checkResponseStatus(resp); - return new int[] { ((BigInteger) resp.get("limit_down")).intValue(), ((BigInteger) resp.get("limit_up")).intValue() }; - } - - private int[] setBandwidthLimits(Integer downLimit, Integer upLimit) { - if (downLimit == null) downLimit = 0; - if (upLimit == null) upLimit = 0; - Map params = new HashMap(); - params.put("limit_down", downLimit); - params.put("limit_up", upLimit); - Map resp = rpc.sendPathRequest("set_limit", params); - checkResponseStatus(resp); - return new int[] { ((BigInteger) resp.get("limit_down")).intValue(), ((BigInteger) resp.get("limit_up")).intValue() }; - } - - /** - * Get a contiguous chunk of blocks starting from a given height up to a maximum - * height or maximum amount of block data fetched from the blockchain, whichever comes first. - * - * @param startHeight is the start height to retrieve blocks (default 0) - * @param maxHeight is the maximum end height to retrieve blocks (default blockchain height) - * @param chunkSize is the maximum chunk size in any one request (default 3,000,000 bytes) - * @return List are the resulting chunk of blocks - */ - private List getMaxBlocks(Long startHeight, Long maxHeight, Long chunkSize) { - if (startHeight == null) startHeight = 0l; - if (maxHeight == null) maxHeight = getHeight() - 1; - if (chunkSize == null) chunkSize = MAX_REQ_SIZE; - - // determine end height to fetch - int reqSize = 0; - long endHeight = startHeight - 1; - while (reqSize < chunkSize && endHeight < maxHeight) { - - // get header of next block - MoneroBlockHeader header = getBlockHeaderByHeightCached(endHeight + 1, maxHeight); - - // block cannot be bigger than max request size - assertTrue("Block exceeds maximum request size: " + header.getSize(), header.getSize() <= chunkSize); - - // done iterating if fetching block would exceed max request size - if (reqSize + header.getSize() > chunkSize) break; - - // otherwise block is included - reqSize += header.getSize(); - endHeight++; - } - return endHeight >= startHeight ? getBlocksByRange(startHeight, endHeight) : new ArrayList(); - } - - /** - * Retrieves a header by height from the cache or fetches and caches a header - * range if not already in the cache. - * - * @param height is the height of the header to retrieve from the cache - * @param maxHeight is the maximum height of headers to cache - */ - private MoneroBlockHeader getBlockHeaderByHeightCached(long height, long maxHeight) { - - // get header from cache - MoneroBlockHeader cachedHeader = cachedHeaders.get(height); - if (cachedHeader != null) return cachedHeader; - - // fetch and cache headers if not in cache - long endHeight = Math.min(maxHeight, height + NUM_HEADERS_PER_REQ - 1); // TODO: could specify end height to cache to optimize small requests (would like to have time profiling in place though) - List headers = getBlockHeadersByRange(height, endHeight); - for (MoneroBlockHeader header : headers) { - cachedHeaders.put(header.getHeight(), header); - } - - // return the cached header - return cachedHeaders.get(height); - } - - //---------------------------------- PRIVATE STATIC ------------------------------- - - private static void checkResponseStatus(Map resp) { - String status = (String) resp.get("status"); - if (!"OK".equals(status)) throw new MoneroRpcException(status, null, null, null); - } - - private static MoneroBlockTemplate convertRpcBlockTemplate(Map rpcTemplate) { - MoneroBlockTemplate template = new MoneroBlockTemplate(); - for (String key : rpcTemplate.keySet()) { - Object val = rpcTemplate.get(key); - if (key.equals("blockhashing_blob")) template.setBlockTemplateBlob((String) val); - else if (key.equals("blocktemplate_blob")) template.setBlockHashingBlob((String) val); - else if (key.equals("difficulty")) template.setDifficulty((BigInteger) val); - else if (key.equals("expected_reward")) template.setExpectedReward((BigInteger) val); - else if (key.equals("difficulty")) { } // handled by wide_difficulty - else if (key.equals("difficulty_top64")) { } // handled by wide_difficulty - else if (key.equals("wide_difficulty")) template.setDifficulty(MoneroUtils.reconcile(template.getDifficulty(), prefixedHexToBI((String) val))); - else if (key.equals("height")) template.setHeight(((BigInteger) val).longValue()); - else if (key.equals("prev_hash")) template.setPrevId((String) val); - else if (key.equals("reserved_offset")) template.setReservedOffset(((BigInteger) val).longValue()); - else if (key.equals("status")) {} // handled elsewhere - else if (key.equals("untrusted")) {} // handled elsewhere - else LOGGER.warn("WARNING: ignoring unexpected field in block template: " + key + ": " + val); - } - return template; - } - - private static MoneroBlockHeader convertRpcBlockHeader(Map rpcHeader) { - return convertRpcBlockHeader(rpcHeader, null); - } - - private static MoneroBlockHeader convertRpcBlockHeader(Map rpcHeader, MoneroBlockHeader header) { - if (header == null) header = new MoneroBlockHeader(); - for (String key : rpcHeader.keySet()) { - Object val = rpcHeader.get(key); - if (key.equals("block_size")) header.setSize(MoneroUtils.reconcile(header.getSize(), ((BigInteger) val).longValue())); - else if (key.equals("depth")) header.setDepth(MoneroUtils.reconcile(header.getDepth(), ((BigInteger) val).longValue())); - else if (key.equals("difficulty")) { } // handled by wide_difficulty - else if (key.equals("cumulative_difficulty")) { } // handled by wide_cumulative_difficulty - else if (key.equals("difficulty_top64")) { } // handled by wide_difficulty - else if (key.equals("cumulative_difficulty_top64")) { } // handled by wide_cumulative_difficulty - else if (key.equals("wide_difficulty")) header.setDifficulty(MoneroUtils.reconcile(header.getDifficulty(), prefixedHexToBI((String) val))); - else if (key.equals("wide_cumulative_difficulty")) header.setCumulativeDifficulty(MoneroUtils.reconcile(header.getCumulativeDifficulty(), prefixedHexToBI((String) val))); - else if (key.equals("hash")) header.setId(MoneroUtils.reconcile(header.getId(), (String) val)); - else if (key.equals("height")) header.setHeight(MoneroUtils.reconcile(header.getHeight(), ((BigInteger) val).longValue())); - else if (key.equals("major_version")) header.setMajorVersion(MoneroUtils.reconcile(header.getMajorVersion(), ((BigInteger) val).intValue())); - else if (key.equals("minor_version")) header.setMinorVersion(MoneroUtils.reconcile(header.getMinorVersion(), ((BigInteger) val).intValue())); - else if (key.equals("nonce")) header.setNonce(MoneroUtils.reconcile(header.getNonce(), ((BigInteger) val).intValue())); - else if (key.equals("num_txes")) header.setNumTxs(MoneroUtils.reconcile(header.getNumTxs(), ((BigInteger) val).intValue())); - else if (key.equals("orphan_status")) header.setOrphanStatus(MoneroUtils.reconcile(header.getOrphanStatus(), (Boolean) val)); - else if (key.equals("prev_hash") || key.equals("prev_id")) header.setPrevId(MoneroUtils.reconcile(header.getPrevId(), (String) val)); - else if (key.equals("reward")) header.setReward(MoneroUtils.reconcile(header.getReward(), (BigInteger) val)); - else if (key.equals("timestamp")) header.setTimestamp(MoneroUtils.reconcile(header.getTimestamp(), ((BigInteger) val).longValue())); - else if (key.equals("block_weight")) header.setWeight(MoneroUtils.reconcile(header.getWeight(), ((BigInteger) val).longValue())); - else if (key.equals("long_term_weight")) header.setLongTermWeight(MoneroUtils.reconcile(header.getLongTermWeight(), ((BigInteger) val).longValue())); - else if (key.equals("pow_hash")) header.setPowHash(MoneroUtils.reconcile(header.getPowHash(), "".equals(val) ? null : (String) val)); - else if (key.equals("tx_hashes")) {} // used in block model, not header model - else if (key.equals("miner_tx")) {} // used in block model, not header model - else if (key.equals("miner_tx_hash")) header.setMinerTxId((String) val); - else LOGGER.warn("WARNING: ignoring unexpected block header field: '" + key + "': " + val); - } - return header; - } - - @SuppressWarnings("unchecked") - private static MoneroBlock convertRpcBlock(Map rpcBlock) { - - // build block - MoneroBlock block = new MoneroBlock(); - convertRpcBlockHeader(rpcBlock.containsKey("block_header") ? (Map) rpcBlock.get("block_header") : rpcBlock, block); - block.setHex((String) rpcBlock.get("blob")); - block.setTxIds(rpcBlock.containsKey("tx_hashes") ? (List) rpcBlock.get("tx_hashes") : new ArrayList()); - - // build miner tx - Map rpcMinerTx = (Map) (rpcBlock.containsKey("json") ? JsonUtils.deserialize(MoneroRpcConnection.MAPPER, (String) rpcBlock.get("json"), new TypeReference>(){}).get("miner_tx") : rpcBlock.get("miner_tx")); // may need to be parsed from json - MoneroTx minerTx = new MoneroTx().setIsConfirmed(true).setIsMinerTx(true); - MoneroDaemonRpc.convertRpcTx(rpcMinerTx, minerTx); - block.setMinerTx(minerTx); - - return block; - } - - /** - * Transfers RPC tx fields to a given MoneroTx without overwriting previous values. - * - * TODO: switch from safe set - * - * @param rpcTx is the RPC map containing transaction fields - * @param tx is the MoneroTx to populate with values (optional) - * @returns tx is the same tx that was passed in or a new one if none given - */ - @SuppressWarnings("unchecked") - private static MoneroTx convertRpcTx(Map rpcTx, MoneroTx tx) { - if (rpcTx == null) return null; - if (tx == null) tx = new MoneroTx(); - -// System.out.println("******** BUILDING TX ***********"); -// System.out.println(rpcTx); -// System.out.println(tx.toString()); - - // initialize from rpc map - MoneroBlock block = null; - for (String key : rpcTx.keySet()) { - Object val = rpcTx.get(key); - if (key.equals("tx_hash") || key.equals("id_hash")) tx.setId(MoneroUtils.reconcile(tx.getId(), (String) val)); - else if (key.equals("block_timestamp")) { - if (block == null) block = new MoneroBlock(); - block.setTimestamp(MoneroUtils.reconcile(block.getTimestamp(), ((BigInteger) val).longValue())); - } - else if (key.equals("block_height")) { - if (block == null) block = new MoneroBlock(); - block.setHeight(MoneroUtils.reconcile(block.getHeight(), ((BigInteger) val).longValue())); - } - else if (key.equals("last_relayed_time")) tx.setLastRelayedTimestamp(MoneroUtils.reconcile(tx.getLastRelayedTimestamp(), ((BigInteger) val).longValue())); - else if (key.equals("receive_time")) tx.setReceivedTimestamp(MoneroUtils.reconcile(tx.getReceivedTimestamp(), ((BigInteger) val).longValue())); - else if (key.equals("in_pool")) { - tx.setIsConfirmed(MoneroUtils.reconcile(tx.isConfirmed(), !(Boolean) val)); - tx.setInTxPool(MoneroUtils.reconcile(tx.inTxPool(), (Boolean) val)); - } - else if (key.equals("double_spend_seen")) tx.setIsDoubleSpendSeen(MoneroUtils.reconcile(tx.isDoubleSpendSeen(), (Boolean) val)); - else if (key.equals("version")) tx.setVersion(MoneroUtils.reconcile(tx.getVersion(), ((BigInteger) val).intValue())); - else if (key.equals("extra")) { - List ints = new ArrayList(); - for (BigInteger bi : (List) val) ints.add(bi.intValue()); - tx.setExtra(MoneroUtils.reconcile(tx.getExtra(), GenUtils.listToIntArray(ints))); - } - else if (key.equals("vin")) { - List> rpcVins = (List>) val; - if (rpcVins.size() != 1 || !rpcVins.get(0).containsKey("gen")) { // ignore miner vin TODO: why? probably needs re-enabled - List vins = new ArrayList(); - for (Map rpcVin : rpcVins) vins.add(convertRpcOutput(rpcVin, tx)); - tx.setVins(vins); - } - } - else if (key.equals("vout")) { - List> rpcVouts = (List>) val; - List vouts = new ArrayList(); - for (Map rpcVout : rpcVouts) vouts.add(convertRpcOutput(rpcVout, tx)); - tx.setVouts(vouts); - } - else if (key.equals("rct_signatures")) tx.setRctSignatures(MoneroUtils.reconcile(tx.getRctSignatures(), (Map) val)); - else if (key.equals("rctsig_prunable")) tx.setRctSigPrunable(MoneroUtils.reconcile(tx.getRctSigPrunable(), val)); - else if (key.equals("unlock_time")) tx.setUnlockTime(MoneroUtils.reconcile(tx.getUnlockTime(), ((BigInteger) val).longValue())); - else if (key.equals("as_json") || key.equals("tx_json")) { } // handled last so tx is as initialized as possible - else if (key.equals("as_hex") || key.equals("tx_blob")) tx.setFullHex(MoneroUtils.reconcile(tx.getFullHex(), "".equals((String) val) ? null : (String) val)); - else if (key.equals("blob_size")) tx.setSize(MoneroUtils.reconcile(tx.getSize(), ((BigInteger) val).longValue())); - else if (key.equals("weight")) tx.setWeight(MoneroUtils.reconcile(tx.getWeight(), ((BigInteger) val).longValue())); - else if (key.equals("fee")) tx.setFee(MoneroUtils.reconcile(tx.getFee(), (BigInteger) val)); - else if (key.equals("relayed")) tx.setIsRelayed(MoneroUtils.reconcile(tx.isRelayed(), (Boolean) val)); - else if (key.equals("output_indices")) { - List indices = new ArrayList(); - for (BigInteger bi : (List) val) indices.add(bi.intValue()); - tx.setOutputIndices(MoneroUtils.reconcile(tx.getOutputIndices(), indices)); - } - else if (key.equals("do_not_relay")) tx.setDoNotRelay(MoneroUtils.reconcile(tx.getDoNotRelay(), (Boolean) val)); - else if (key.equals("kept_by_block")) tx.setIsKeptByBlock(MoneroUtils.reconcile(tx.isKeptByBlock(), (Boolean) val)); - else if (key.equals("signatures")) tx.setSignatures(MoneroUtils.reconcile(tx.getSignatures(), (List) val)); - else if (key.equals("last_failed_height")) { - long lastFailedHeight = ((BigInteger) val).longValue(); - if (lastFailedHeight == 0) tx.setIsFailed(MoneroUtils.reconcile(tx.isFailed(), false)); - else { - tx.setIsFailed(MoneroUtils.reconcile(tx.isFailed(), true)); - tx.setLastFailedHeight(MoneroUtils.reconcile(tx.getLastFailedHeight(), lastFailedHeight)); - } - } - else if (key.equals("last_failed_id_hash")) { - if (DEFAULT_ID.equals((String) val)) tx.setIsFailed(MoneroUtils.reconcile(tx.isFailed(), false)); - else { - tx.setIsFailed(MoneroUtils.reconcile(tx.isFailed(), true)); - tx.setLastFailedId(MoneroUtils.reconcile(tx.getLastFailedId(), (String) val)); - } - } - else if (key.equals("max_used_block_height")) tx.setMaxUsedBlockHeight(MoneroUtils.reconcile(tx.getMaxUsedBlockHeight(), ((BigInteger) val).longValue())); - else if (key.equals("max_used_block_id_hash")) tx.setMaxUsedBlockId(MoneroUtils.reconcile(tx.getMaxUsedBlockId(), (String) val)); - else if (key.equals("prunable_hash")) tx.setPrunableHash(MoneroUtils.reconcile(tx.getPrunableHash(), "".equals((String) val) ? null : (String) val)); - else if (key.equals("prunable_as_hex")) tx.setPrunableHex(MoneroUtils.reconcile(tx.getPrunableHex(), "".equals((String) val) ? null : (String) val)); - else if (key.equals("pruned_as_hex")) tx.setPrunedHex(MoneroUtils.reconcile(tx.getPrunedHex(), "".equals((String) val) ? null : (String) val)); - else LOGGER.warn("WARNING: ignoring unexpected field in rpc tx: " + key + ": " + val); - } - - // link block and tx - if (block != null) tx.setBlock(block.setTxs(Arrays.asList(tx))); - - // TODO monero-daemon-rpc: unconfirmed txs misreport block height and timestamp - if (tx.getBlock() != null && tx.getBlock().getHeight() != null && (long) tx.getBlock().getHeight() == tx.getBlock().getTimestamp()) { - tx.setBlock(null); - tx.setIsConfirmed(false); - } - - // initialize remaining known fields - if (tx.isConfirmed()) { - tx.setIsRelayed(MoneroUtils.reconcile(tx.isRelayed(), true)); - tx.setDoNotRelay(MoneroUtils.reconcile(tx.getDoNotRelay(), false)); - tx.setIsFailed(MoneroUtils.reconcile(tx.isFailed(), false)); - } else { - tx.setNumConfirmations(0l); - } - if (tx.isFailed() == null) tx.setIsFailed(false); - if (tx.getOutputIndices() != null && tx.getVouts() != null) { - assertEquals(tx.getOutputIndices().size(), (int) tx.getVouts().size()); - for (int i = 0; i < tx.getVouts().size(); i++) { - tx.getVouts().get(i).setIndex(tx.getOutputIndices().get(i)); // transfer output indices to vouts - } - } - if (rpcTx.containsKey("as_json") && !"".equals(rpcTx.get("as_json"))) convertRpcTx(JsonUtils.deserialize(MoneroRpcConnection.MAPPER, (String) rpcTx.get("as_json"), new TypeReference>(){}), tx); - if (rpcTx.containsKey("tx_json") && !"".equals(rpcTx.get("tx_json"))) convertRpcTx(JsonUtils.deserialize(MoneroRpcConnection.MAPPER, (String) rpcTx.get("tx_json"), new TypeReference>(){}), tx); - if (!Boolean.TRUE.equals(tx.isRelayed())) tx.setLastRelayedTimestamp(null); // TODO monero-daemon-rpc: returns last_relayed_timestamp despite relayed: false, self inconsistent - - // return built transaction - return tx; - } - - @SuppressWarnings("unchecked") - private static MoneroOutput convertRpcOutput(Map rpcOutput, MoneroTx tx) { - MoneroOutput output = new MoneroOutput(); - output.setTx(tx); - for (String key : rpcOutput.keySet()) { - Object val = rpcOutput.get(key); - if (key.equals("gen")) throw new Error("Output with 'gen' from daemon rpc is miner tx which we ignore (i.e. each miner vin is null)"); - else if (key.equals("key")) { - Map rpcKey = (Map) val; - output.setAmount(MoneroUtils.reconcile(output.getAmount(), (BigInteger) rpcKey.get("amount"))); - output.setKeyImage(MoneroUtils.reconcile(output.getKeyImage(), new MoneroKeyImage((String) rpcKey.get("k_image")))); - List ringOutputIndices = new ArrayList(); - for (BigInteger bi : (List) rpcKey.get("key_offsets")) ringOutputIndices.add(bi.intValue()); - output.setRingOutputIndices(MoneroUtils.reconcile(output.getRingOutputIndices(), ringOutputIndices)); - } - else if (key.equals("amount")) output.setAmount(MoneroUtils.reconcile(output.getAmount(), (BigInteger) val)); - else if (key.equals("target")) output.setStealthPublicKey(MoneroUtils.reconcile(output.getStealthPublicKey(), (String) ((Map) val).get("key"))); - else LOGGER.warn("WARNING: ignoring unexpected field output: " + key + ": " + val); - } - return output; - } - - private static MoneroDaemonUpdateCheckResult convertRpcUpdateCheckResult(Map rpcResult) { - MoneroDaemonUpdateCheckResult result = new MoneroDaemonUpdateCheckResult(); - for (String key : rpcResult.keySet()) { - Object val = rpcResult.get(key); - if (key.equals("auto_uri")) result.setAutoUri((String) val); - else if (key.equals("hash")) result.setHash((String) val); - else if (key.equals("path")) {} // handled elsewhere - else if (key.equals("status")) {} // handled elsewhere - else if (key.equals("update")) result.setIsUpdateAvailable((Boolean) val); - else if (key.equals("user_uri")) result.setUserUri((String) val); - else if (key.equals("version")) result.setVersion((String) val); - else LOGGER.warn("WARNING: ignoring unexpected field in rpc check update result: " + key + ": " + val); - } - if ("".equals(result.getAutoUri())) result.setAutoUri(null); - if ("".equals(result.getUserUri())) result.setUserUri(null); - if ("".equals(result.getVersion())) result.setVersion(null); - if ("".equals(result.getHash())) result.setHash(null); - return result; - } - - private static MoneroDaemonUpdateDownloadResult convertRpcUpdateDownloadResult(Map rpcResult) { - MoneroDaemonUpdateDownloadResult result = new MoneroDaemonUpdateDownloadResult(convertRpcUpdateCheckResult(rpcResult)); - result.setDownloadPath((String) rpcResult.get("path")); - if ("".equals(result.getDownloadPath())) result.setDownloadPath(null); - return result; - } - - private static MoneroDaemonPeer convertRpcPeer(Map rpcPeer) { - assertNotNull(rpcPeer); - MoneroDaemonPeer peer = new MoneroDaemonPeer(); - for (String key : rpcPeer.keySet()) { - Object val = rpcPeer.get(key); - if (key.equals("host")) peer.setHost((String) val); - else if (key.equals("id")) peer.setId("" + val); // TODO monero-wallet-rpc: peer id is big integer but string in `get_connections` - else if (key.equals("ip")) {} // host used instead which is consistently a string - else if (key.equals("last_seen")) peer.setLastSeenTimestamp(((BigInteger) val).longValue()); - else if (key.equals("port")) peer.setPort(((BigInteger) val).intValue()); - else if (key.equals("rpc_port")) peer.setRpcPort(((BigInteger) val).intValue()); - else if (key.equals("pruning_seed")) peer.setPruningSeed(((BigInteger) val).intValue()); - else LOGGER.warn("WARNING: ignoring unexpected field in rpc peer: " + key + ": " + val); - } - return peer; - } - - private static MoneroSubmitTxResult convertRpcSubmitTxResult(Map rpcResult) { - assertNotNull(rpcResult); - MoneroSubmitTxResult result = new MoneroSubmitTxResult(); - for (String key : rpcResult.keySet()) { - Object val = rpcResult.get(key); - if (key.equals("double_spend")) result.setIsDoubleSpend((Boolean) val); - else if (key.equals("fee_too_low")) result.setIsFeeTooLow((Boolean) val); - else if (key.equals("invalid_input")) result.setHasInvalidInput((Boolean) val); - else if (key.equals("invalid_output")) result.setHasInvalidOutput((Boolean) val); - else if (key.equals("low_mixin")) result.setIsMixinTooLow((Boolean) val); - else if (key.equals("not_rct")) result.setIsRct(!Boolean.TRUE.equals(val)); - else if (key.equals("not_relayed")) result.setIsRelayed(!Boolean.TRUE.equals(val)); - else if (key.equals("overspend")) result.setIsOverspend((Boolean) val); - else if (key.equals("reason")) result.setReason("".equals((String) val) ? null : (String) val); - else if (key.equals("too_big")) result.setIsTooBig((Boolean) val); - else if (key.equals("sanity_check_failed")) result.setSanityCheckFailed((Boolean) val); - else if (key.equals("status") || key.equals("untrusted")) {} // handled elsewhere - else LOGGER.warn("WARNING: ignoring unexpected field in submit tx hex result: " + key + ": " + val); - } - return result; - } - - private static MoneroDaemonConnection convertRpcConnection(Map rpcConnection) { - MoneroDaemonConnection connection = new MoneroDaemonConnection(); - MoneroDaemonPeer peer = new MoneroDaemonPeer(); - connection.setPeer(peer); - peer.setIsOnline(true); - for (String key : rpcConnection.keySet()) { - Object val = rpcConnection.get(key); - if (key.equals("address")) peer.setAddress((String) val); - else if (key.equals("avg_download")) connection.setAvgDownload(((BigInteger) val).longValue()); - else if (key.equals("avg_upload")) connection.setAvgUpload(((BigInteger) val).longValue()); - else if (key.equals("connection_id")) connection.setId((String) val); - else if (key.equals("current_download")) connection.setCurrentDownload(((BigInteger) val).longValue()); - else if (key.equals("current_upload")) connection.setCurrentUpload(((BigInteger) val).longValue()); - else if (key.equals("height")) connection.setHeight(((BigInteger) val).longValue()); - else if (key.equals("host")) peer.setHost((String) val); - else if (key.equals("ip")) {} // host used instead which is consistently a string - else if (key.equals("incoming")) connection.setIsIncoming((Boolean) val); - else if (key.equals("live_time")) connection.setLiveTime(((BigInteger) val).longValue()); - else if (key.equals("local_ip")) connection.setIsLocalIp((Boolean) val); - else if (key.equals("localhost")) connection.setIsLocalHost((Boolean) val); - else if (key.equals("peer_id")) peer.setId((String) val); - else if (key.equals("port")) peer.setPort(Integer.parseInt((String) val)); - else if (key.equals("rpc_port")) peer.setRpcPort(((BigInteger) val).intValue()); - else if (key.equals("recv_count")) connection.setNumReceives(((BigInteger) val).intValue()); - else if (key.equals("recv_idle_time")) connection.setReceiveIdleTime(((BigInteger) val).longValue()); - else if (key.equals("send_count")) connection.setNumSends(((BigInteger) val).intValue()); - else if (key.equals("send_idle_time")) connection.setSendIdleTime(((BigInteger) val).longValue()); - else if (key.equals("state")) connection.setState((String) val); - else if (key.equals("support_flags")) connection.setNumSupportFlags(((BigInteger) val).intValue()); - else if (key.equals("pruning_seed")) peer.setPruningSeed(((BigInteger) val).intValue()); - else LOGGER.warn("WARNING: ignoring unexpected field in connection: " + key + ": " + val); - } - return connection; - } - - private static MoneroOutputHistogramEntry convertRpcOutputHistogramEntry(Map rpcEntry) { - MoneroOutputHistogramEntry entry = new MoneroOutputHistogramEntry(); - for (String key : rpcEntry.keySet()) { - Object val = rpcEntry.get(key); - if (key.equals("amount")) entry.setAmount((BigInteger) val); - else if (key.equals("total_instances")) entry.setNumInstances(((BigInteger) val).longValue()); - else if (key.equals("unlocked_instances")) entry.setNumUnlockedInstances(((BigInteger) val).longValue()); - else if (key.equals("recent_instances")) entry.setNumRecentInstances(((BigInteger) val).longValue()); - else LOGGER.warn("WARNING: ignoring unexpected field in output histogram: " + key + ": " + val); - } - return entry; - } - - private static MoneroDaemonInfo convertRpcInfo(Map rpcInfo) { - if (rpcInfo == null) return null; - MoneroDaemonInfo info = new MoneroDaemonInfo(); - for (String key : rpcInfo.keySet()) { - Object val = rpcInfo.get(key); - if (key.equals("version")) info.setVersion((String) val); - else if (key.equals("alt_blocks_count")) info.setNumAltBlocks(((BigInteger) val).longValue()); - else if (key.equals("block_size_limit")) info.setBlockSizeLimit(((BigInteger) val).longValue()); - else if (key.equals("block_size_median")) info.setBlockSizeMedian(((BigInteger) val).longValue()); - else if (key.equals("block_weight_limit")) info.setBlockWeightLimit(((BigInteger) val).longValue()); - else if (key.equals("block_weight_median")) info.setBlockWeightMedian(((BigInteger) val).longValue()); - else if (key.equals("bootstrap_daemon_address")) { if (!((String) val).isEmpty()) info.setBootstrapDaemonAddress((String) val); } - else if (key.equals("difficulty")) { } // handled by wide_difficulty - else if (key.equals("cumulative_difficulty")) { } // handled by wide_cumulative_difficulty - else if (key.equals("difficulty_top64")) { } // handled by wide_difficulty - else if (key.equals("cumulative_difficulty_top64")) { } // handled by wide_cumulative_difficulty - else if (key.equals("wide_difficulty")) info.setDifficulty(MoneroUtils.reconcile(info.getDifficulty(), prefixedHexToBI((String) val))); - else if (key.equals("wide_cumulative_difficulty")) info.setCumulativeDifficulty(MoneroUtils.reconcile(info.getCumulativeDifficulty(), prefixedHexToBI((String) val))); - else if (key.equals("free_space")) info.setFreeSpace((BigInteger) val); - else if (key.equals("database_size")) info.setDatabaseSize(((BigInteger) val).longValue()); - else if (key.equals("grey_peerlist_size")) info.setNumOfflinePeers(((BigInteger) val).intValue()); - else if (key.equals("height")) info.setHeight(((BigInteger) val).longValue()); - else if (key.equals("height_without_bootstrap")) info.setHeightWithoutBootstrap(((BigInteger) val).longValue()); - else if (key.equals("incoming_connections_count")) info.setNumIncomingConnections(((BigInteger) val).intValue()); - else if (key.equals("offline")) info.setIsOffline((Boolean) val); - else if (key.equals("outgoing_connections_count")) info.setNumOutgoingConnections(((BigInteger) val).intValue()); - else if (key.equals("rpc_connections_count")) info.setNumRpcConnections(((BigInteger) val).intValue()); - else if (key.equals("start_time")) info.setStartTimestamp(((BigInteger) val).longValue()); - else if (key.equals("status")) {} // handled elsewhere - else if (key.equals("target")) info.setTarget(((BigInteger) val).longValue()); - else if (key.equals("target_height")) info.setTargetHeight(((BigInteger) val).longValue()); - else if (key.equals("top_block_hash")) info.setTopBlockId((String) val); - else if (key.equals("tx_count")) info.setNumTxs(((BigInteger) val).intValue()); - else if (key.equals("tx_pool_size")) info.setNumTxsPool(((BigInteger) val).intValue()); - else if (key.equals("untrusted")) {} // handled elsewhere - else if (key.equals("was_bootstrap_ever_used")) info.setWasBootstrapEverUsed((Boolean) val); - else if (key.equals("white_peerlist_size")) info.setNumOnlinePeers(((BigInteger) val).intValue()); - else if (key.equals("update_available")) info.setUpdateAvailable((Boolean) val); - else if (key.equals("nettype")) info.setNetworkType(MoneroUtils.reconcile(info.getNetworkType(), MoneroDaemon.parseNetworkType((String) val))); - else if (key.equals("mainnet")) { if ((Boolean) val) info.setNetworkType(MoneroUtils.reconcile(info.getNetworkType(), MoneroNetworkType.MAINNET)); } - else if (key.equals("testnet")) { if ((Boolean) val) info.setNetworkType(MoneroUtils.reconcile(info.getNetworkType(), MoneroNetworkType.TESTNET)); } - else if (key.equals("stagenet")) { if ((Boolean) val) info.setNetworkType(MoneroUtils.reconcile(info.getNetworkType(), MoneroNetworkType.STAGENET)); } - else LOGGER.warn("WARNING: Ignoring unexpected info field: " + key + ": " + val); - } - return info; - } - - /** - * Initializes sync info from RPC sync info. - * - * @param rpcSyncInfo is the rpc map to initialize the sync info from - * @return {MoneroDaemonSyncInfo} is sync info initialized from the map - */ - @SuppressWarnings("unchecked") - private static MoneroDaemonSyncInfo convertRpcSyncInfo(Map rpcSyncInfo) { - MoneroDaemonSyncInfo syncInfo = new MoneroDaemonSyncInfo(); - for (String key : rpcSyncInfo.keySet()) { - Object val = rpcSyncInfo.get(key); - if (key.equals("height")) syncInfo.setHeight(((BigInteger) val).longValue()); - else if (key.equals("peers")) { - syncInfo.setConnections(new ArrayList()); - List> rpcConnections = (List>) val; - for (Map rpcConnection : rpcConnections) { - syncInfo.getConnections().add(convertRpcConnection((Map) rpcConnection.get("info"))); - } - } else if (key.equals("spans")) { - syncInfo.setSpans(new ArrayList()); - List> rpcSpans = (List>) val; - for (Map rpcSpan : rpcSpans) { - syncInfo.getSpans().add(convertRpcConnectionSpan(rpcSpan)); - } - } - else if (key.equals("status")) {} // handled elsewhere - else if (key.equals("target_height")) syncInfo.setTargetHeight(((BigInteger) val).longValue()); - else if (key.equals("next_needed_pruning_seed")) syncInfo.setNextNeededPruningSeed(((BigInteger) val).intValue()); - else if (key.equals("overview")) { // this returns [] without pruning - try { - List overview = JsonUtils.deserialize((String) val, new TypeReference>(){}); - if (!overview.isEmpty()) LOGGER.warn("WARNING: ignoring non-empty 'overview' field (not implemented): " + overview); // TODO - } catch (Exception e) { - //e.printStackTrace(); - LOGGER.warn("WARNING: failed to parse 'overview' field: " + val); - } - } - else LOGGER.warn("WARNING: ignoring unexpected field in sync info: " + key + ": " + val); - } - return syncInfo; - } - - private static MoneroHardForkInfo convertRpcHardForkInfo(Map rpcHardForkInfo) { - MoneroHardForkInfo info = new MoneroHardForkInfo(); - for (String key : rpcHardForkInfo.keySet()) { - Object val = rpcHardForkInfo.get(key); - if (key.equals("earliest_height")) info.setEarliestHeight(((BigInteger) val).longValue()); - else if (key.equals("enabled")) info.setIsEnabled((Boolean) val); - else if (key.equals("state")) info.setState(((BigInteger) val).intValue()); - else if (key.equals("status")) {} // handled elsewhere - else if (key.equals("untrusted")) {} // handled elsewhere - else if (key.equals("threshold")) info.setThreshold(((BigInteger) val).intValue()); - else if (key.equals("version")) info.setVersion(((BigInteger) val).intValue()); - else if (key.equals("votes")) info.setNumVotes(((BigInteger) val).intValue()); - else if (key.equals("voting")) info.setVoting(((BigInteger) val).intValue()); - else if (key.equals("window")) info.setWindow(((BigInteger) val).intValue()); - else LOGGER.warn("WARNING: ignoring unexpected field in hard fork info: " + key + ": " + val); - } - return info; - } - - private static MoneroDaemonConnectionSpan convertRpcConnectionSpan(Map rpcConnectionSpan) { - MoneroDaemonConnectionSpan span = new MoneroDaemonConnectionSpan(); - for (String key : rpcConnectionSpan.keySet()) { - Object val = rpcConnectionSpan.get(key); - if (key.equals("connection_id")) span.setConnectionId((String) val); - else if (key.equals("nblocks")) span.setNumBlocks(((BigInteger) val).longValue()); - else if (key.equals("rate")) span.setRate(((BigInteger) val).longValue()); - else if (key.equals("remote_address")) { if (!"".equals(val)) span.setRemoteAddress((String) val); } - else if (key.equals("size")) span.setSize(((BigInteger) val).longValue()); - else if (key.equals("speed")) span.setSpeed(((BigInteger) val).longValue()); - else if (key.equals("start_block_height")) span.setStartHeight(((BigInteger) val).longValue()); - else LOGGER.warn("WARNING: ignoring unexpected field in daemon connection span: " + key + ": " + val); - } - return span; - } - - private static Map convertToRpcBan(MoneroBan ban) { - Map rpcBan = new HashMap(); - rpcBan.put("host", ban.getHost()); - rpcBan.put("ip", ban.getIp()); - rpcBan.put("ban", ban.isBanned()); - rpcBan.put("seconds", ban.getSeconds()); - return rpcBan; - } - - private static MoneroMiningStatus convertRpcMiningStatus(Map rpcStatus) { - MoneroMiningStatus status = new MoneroMiningStatus(); - status.setIsActive((Boolean) rpcStatus.get("active")); - status.setSpeed(((BigInteger) rpcStatus.get("speed")).longValue()); - status.setNumThreads(((BigInteger) rpcStatus.get("threads_count")).intValue()); - if (status.isActive()) { - status.setAddress((String) rpcStatus.get("address")); - status.setIsBackground((Boolean) rpcStatus.get("is_background_mining_enabled")); - } - return status; - } - - @SuppressWarnings("unchecked") - private static MoneroAltChain convertRpcAltChain(Map rpcChain) { - MoneroAltChain chain = new MoneroAltChain(); - for (String key : rpcChain.keySet()) { - Object val = rpcChain.get(key); - if (key.equals("block_hash")) {} // using block_hashes instead - else if (key.equals("difficulty")) { } // handled by wide_difficulty - else if (key.equals("difficulty_top64")) { } // handled by wide_difficulty - else if (key.equals("wide_difficulty")) chain.setDifficulty(MoneroUtils.reconcile(chain.getDifficulty(), prefixedHexToBI((String) val))); - else if (key.equals("height")) chain.setHeight(((BigInteger) val).longValue()); - else if (key.equals("length")) chain.setLength(((BigInteger) val).longValue()); - else if (key.equals("block_hashes")) chain.setBlockIds((List) val); - else if (key.equals("main_chain_parent_block")) chain.setMainChainParentBlockId((String) val); - else LOGGER.warn("WARNING: ignoring unexpected field in alternative chain: " + key + ": " + val); - } - return chain; - } - - /** - * Converts a '0x' prefixed hexidecimal string to a BigInteger. - * - * @param hex is the '0x' prefixed hexidecimal string to convert - * @return BigInteger is the hexicedimal converted to decimal - */ - private static BigInteger prefixedHexToBI(String hex) { - assertTrue("Given hex does not start with \"0x\": " + hex, hex.startsWith("0x")); - return new BigInteger(hex.substring(2), 16); - } - - /** - * Polls a Monero daemon for updates and notifies listeners as they occur. - */ - private class MoneroDaemonPoller { - - private MoneroDaemon daemon; - private MoneroDaemonPollerRunnable runnable; - private List listeners; - private static final long POLL_INTERVAL_MS = 10000; // poll every X ms TODO: poll interval should come from configuration - - public MoneroDaemonPoller(MoneroDaemon daemon) { - this.daemon = daemon; - this.listeners = new ArrayList(); - } - - public void addListener(MoneroDaemonListener listener) { - - // register listener - listeners.add(listener); - - // start polling thread - if (runnable == null) { - runnable = new MoneroDaemonPollerRunnable(daemon, POLL_INTERVAL_MS); - Thread thread = new Thread(runnable); - thread.setDaemon(true); // daemon thread does not prevent JVM from halting - thread.start(); - } - } - - public void removeListener(MoneroDaemonListener listener) { - boolean found = listeners.remove(listener); - if (!found) throw new MoneroException("Listener is not registered"); - if (listeners.isEmpty()) { - runnable.terminate(); - runnable = null; - } - } - - private class MoneroDaemonPollerRunnable implements Runnable { - - private MoneroDaemon daemon; - private long interval; - private boolean isTerminated; - - public MoneroDaemonPollerRunnable(MoneroDaemon daemon, long interval) { - this.daemon = daemon; - this.interval = interval; - this.isTerminated = false; - } - - @Override - public void run() { - - // get header to detect changes while polling - MoneroBlockHeader lastHeader = daemon.getLastBlockHeader(); - - // poll until stopped - while (!isTerminated) { - - // pause for interval ms - try { - TimeUnit.MILLISECONDS.sleep(interval); - } catch (InterruptedException e) { - e.printStackTrace(); - terminate(); - } - - // fetch and compare latest block header - MoneroBlockHeader header = daemon.getLastBlockHeader(); - if (!header.getId().equals(lastHeader.getId())) { - lastHeader = header; - for (MoneroDaemonListener listener : listeners) { - listener.onBlockHeader(header); // notify listener - } - } - } - } - - public void terminate() { - isTerminated = true; - } - } - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroAltChain.java b/core/src/main/java/monero/daemon/model/MoneroAltChain.java deleted file mode 100644 index 115904badad..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroAltChain.java +++ /dev/null @@ -1,56 +0,0 @@ -package monero.daemon.model; - -import java.math.BigInteger; -import java.util.List; - -/** - * Models an alternative chain seen by the node. - */ -public class MoneroAltChain { - - private List blockIds; - private BigInteger difficulty; - private Long height; - private Long length; - private String mainChainParentBlockId; - - public List getBlockIds() { - return blockIds; - } - - public void setBlockIds(List blockIds) { - this.blockIds = blockIds; - } - - public BigInteger getDifficulty() { - return difficulty; - } - - public void setDifficulty(BigInteger difficulty) { - this.difficulty = difficulty; - } - - public Long getHeight() { - return height; - } - - public void setHeight(Long height) { - this.height = height; - } - - public Long getLength() { - return length; - } - - public void setLength(Long length) { - this.length = length; - } - - public String getMainChainParentBlockId() { - return mainChainParentBlockId; - } - - public void setMainChainParentBlockId(String mainChainParentBlockId) { - this.mainChainParentBlockId = mainChainParentBlockId; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroBan.java b/core/src/main/java/monero/daemon/model/MoneroBan.java deleted file mode 100644 index b2bf2ab4704..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroBan.java +++ /dev/null @@ -1,47 +0,0 @@ -package monero.daemon.model; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Monero banhammer. - */ -public class MoneroBan { - - private String host; // e.g. 192.168.1.100 - private Integer ip; // integer formatted IP - private Boolean isBanned; - private Long seconds; - - public String getHost() { - return host; - } - - public void setHost(String host) { - this.host = host; - } - - public Integer getIp() { - return ip; - } - - public void setIp(Integer ip) { - this.ip = ip; - } - - @JsonProperty("isBanned") - public Boolean isBanned() { - return isBanned; - } - - public void setIsBanned(Boolean isBanned) { - this.isBanned = isBanned; - } - - public Long getSeconds() { - return seconds; - } - - public void setSeconds(Long seconds) { - this.seconds = seconds; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroBlock.java b/core/src/main/java/monero/daemon/model/MoneroBlock.java deleted file mode 100644 index cd351f27733..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroBlock.java +++ /dev/null @@ -1,276 +0,0 @@ -package monero.daemon.model; - -import static org.junit.Assert.assertNotNull; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonManagedReference; -import com.fasterxml.jackson.annotation.JsonProperty; - -import common.utils.GenUtils; -import monero.utils.MoneroUtils; - -/** - * Models a Monero block in the blockchain. - */ -public class MoneroBlock extends MoneroBlockHeader { - - private String hex; - private MoneroTx minerTx; - private List txs; - private List txIds; - - public MoneroBlock() { - super(); - } - - public MoneroBlock(MoneroBlockHeader header) { - super(header); - } - - public MoneroBlock(MoneroBlock block) { - super(block); - this.hex = block.getHex(); - if (block.minerTx != null) this.minerTx = block.minerTx.copy().setBlock(this); - if (block.txs != null) { - this.txs = new ArrayList(); - for (MoneroTx tx : block.txs) txs.add(tx.copy().setBlock(this)); - } - if (block.getTxIds() != null) this.txIds = new ArrayList(block.getTxIds()); - } - - public String getHex() { - return hex; - } - - public MoneroBlock setHex(String hex) { - this.hex = hex; - return this; - } - - public MoneroTx getMinerTx() { - return minerTx; - } - - public MoneroBlock setMinerTx(MoneroTx minerTx) { - this.minerTx = minerTx; - return this; - } - - @JsonManagedReference("block_txs") - public List getTxs() { - return txs; - } - - @JsonProperty("txs") - public MoneroBlock setTxs(List txs) { - this.txs = txs; - return this; - } - - @JsonIgnore - public MoneroBlock setTxs(MoneroTx... txs) { - this.txs = GenUtils.arrayToList(txs); - return this; - } - - public List getTxIds() { - return txIds; - } - - public MoneroBlock setTxIds(List txIds) { - this.txIds = txIds; - return this; - } - - public MoneroBlock copy() { - return new MoneroBlock(this); - } - - public MoneroBlock merge(MoneroBlock block) { - assertNotNull(block); - if (this == block) return this; - - // merge header fields - super.merge(block); - - // merge reconcilable block extensions - this.setHex(MoneroUtils.reconcile(this.getHex(), block.getHex())); - this.setTxIds(MoneroUtils.reconcile(this.getTxIds(), block.getTxIds())); - - // merge miner tx - if (this.getMinerTx() == null) this.setMinerTx(block.getMinerTx()); - if (block.getMinerTx() != null) { - block.getMinerTx().setBlock(this); - minerTx.merge(block.getMinerTx()); - } - - // merge non-miner txs - if (block.getTxs() != null) { - for (MoneroTx tx : block.getTxs()) { - tx.setBlock(this); - MoneroUtils.mergeTx(txs, tx); - } - } - - return this; - } - - public String toString(int indent) { - StringBuilder sb = new StringBuilder(); - sb.append(super.toString(indent)); - sb.append("\n"); - sb.append(MoneroUtils.kvLine("Hex", getHex(), indent)); - sb.append(MoneroUtils.kvLine("Txs ids", getTxIds(), indent)); - if (getMinerTx() != null) { - sb.append(MoneroUtils.kvLine("Miner tx", "", indent)); - sb.append(getMinerTx().toString(indent + 1) + "\n"); - } - if (getTxs() != null) { - sb.append(MoneroUtils.kvLine("Txs", "", indent)); - for (MoneroTx tx : getTxs()) { - sb.append(tx.toString(indent + 1) + "\n"); - } - } - String str = sb.toString(); - return str.charAt(str.length() - 1) == '\n' ? str.substring(0, str.length() - 1) : str; // strip newline - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((minerTx == null) ? 0 : minerTx.hashCode()); - result = prime * result + ((hex == null) ? 0 : hex.hashCode()); - result = prime * result + ((txIds == null) ? 0 : txIds.hashCode()); - result = prime * result + ((txs == null) ? 0 : txs.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!super.equals(obj)) return false; - if (getClass() != obj.getClass()) return false; - MoneroBlock other = (MoneroBlock) obj; - if (minerTx == null) { - if (other.minerTx != null) return false; - } else if (!minerTx.equals(other.minerTx)) return false; - if (hex == null) { - if (other.hex != null) return false; - } else if (!hex.equals(other.hex)) return false; - if (txIds == null) { - if (other.txIds != null) return false; - } else if (!txIds.equals(other.txIds)) return false; - if (txs == null) { - if (other.txs != null) return false; - } else if (!txs.equals(other.txs)) return false; - return true; - } - - // ------------------- OVERRIDE CO-VARIANT RETURN TYPES --------------------- - - public MoneroBlock setId(String id) { - super.setId(id); - return this; - } - - @Override - public MoneroBlock setHeight(Long height) { - super.setHeight(height); - return this; - } - - @Override - public MoneroBlock setTimestamp(Long timestamp) { - super.setTimestamp(timestamp); - return this; - } - - @Override - public MoneroBlock setSize(Long size) { - super.setSize(size); - return this; - } - - @Override - public MoneroBlock setWeight(Long weight) { - super.setWeight(weight); - return this; - } - - @Override - public MoneroBlock setLongTermWeight(Long longTermWeight) { - super.setLongTermWeight(longTermWeight); - return this; - } - - @Override - public MoneroBlock setDepth(Long depth) { - super.setDepth(depth); - return this; - } - - @Override - public MoneroBlock setDifficulty(BigInteger difficulty) { - super.setDifficulty(difficulty); - return this; - } - - @Override - public MoneroBlock setCumulativeDifficulty(BigInteger cumulativeDifficulty) { - super.setCumulativeDifficulty(cumulativeDifficulty); - return this; - } - - @Override - public MoneroBlock setMajorVersion(Integer majorVersion) { - super.setMajorVersion(majorVersion); - return this; - } - - @Override - public MoneroBlock setMinorVersion(Integer minorVersion) { - super.setMinorVersion(minorVersion); - return this; - } - - @Override - public MoneroBlock setNonce(Integer nonce) { - super.setNonce(nonce); - return this; - } - - @Override - public MoneroBlock setNumTxs(Integer numTxs) { - super.setNumTxs(numTxs); - return this; - } - - @Override - public MoneroBlock setOrphanStatus(Boolean orphanStatus) { - super.setOrphanStatus(orphanStatus); - return this; - } - - @Override - public MoneroBlock setPrevId(String prevId) { - super.setPrevId(prevId); - return this; - } - - @Override - public MoneroBlock setReward(BigInteger reward) { - super.setReward(reward); - return this; - } - - @Override - public MoneroBlock setPowHash(String powHash) { - super.setPowHash(powHash); - return this; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroBlockHeader.java b/core/src/main/java/monero/daemon/model/MoneroBlockHeader.java deleted file mode 100644 index afa27d642b0..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroBlockHeader.java +++ /dev/null @@ -1,368 +0,0 @@ -package monero.daemon.model; - -import static org.junit.Assert.assertNotNull; - -import java.math.BigInteger; - -import monero.utils.MoneroUtils; - -/** - * Models a Monero block header which contains information about the block. - */ -public class MoneroBlockHeader { - - private String id; - private Long height; - private Long timestamp; - private Long size; - private Long weight; - private Long longTermWeight; - private Long depth; - private BigInteger difficulty; - private BigInteger cumulativeDifficulty; - private Integer majorVersion; - private Integer minorVersion; - private Integer nonce; - private String minerTxId; - private Integer numTxs; - private Boolean orphanStatus; - private String prevId; - private BigInteger reward; - private String powHash; - - public MoneroBlockHeader() { - super(); - } - - public MoneroBlockHeader(MoneroBlockHeader header) { - this.id = header.id; - this.height = header.height; - this.timestamp = header.timestamp; - this.size = header.size; - this.weight = header.weight; - this.longTermWeight = header.longTermWeight; - this.depth = header.depth; - this.difficulty = header.difficulty; - this.cumulativeDifficulty = header.cumulativeDifficulty; - this.majorVersion = header.majorVersion; - this.minorVersion = header.minorVersion; - this.nonce = header.nonce; - this.numTxs = header.numTxs; - this.orphanStatus = header.orphanStatus; - this.prevId = header.prevId; - this.reward = header.reward; - this.powHash = header.powHash; - } - - public String getId() { - return id; - } - - public MoneroBlockHeader setId(String id) { - this.id = id; - return this; - } - - /** - * Return the block's height which is the total number of blocks that have occurred before. - * - * @return the block's height - */ - public Long getHeight() { - return height; - } - - /** - * Set the block's height which is the total number of blocks that have occurred before. - * - * @param height is the block's height to set - * @return a reference to this header for chaining - */ - public MoneroBlockHeader setHeight(Long height) { - this.height = height; - return this; - } - - public Long getTimestamp() { - return timestamp; - } - - public MoneroBlockHeader setTimestamp(Long timestamp) { - this.timestamp = timestamp; - return this; - } - - public Long getSize() { - return size; - } - - public MoneroBlockHeader setSize(Long size) { - this.size = size; - return this; - } - - public Long getWeight() { - return weight; - } - - public MoneroBlockHeader setWeight(Long weight) { - this.weight = weight; - return this; - } - - public Long getLongTermWeight() { - return longTermWeight; - } - - public MoneroBlockHeader setLongTermWeight(Long longTermWeight) { - this.longTermWeight = longTermWeight; - return this; - } - - public Long getDepth() { - return depth; - } - - public MoneroBlockHeader setDepth(Long depth) { - this.depth = depth; - return this; - } - - public BigInteger getDifficulty() { - return difficulty; - } - - public MoneroBlockHeader setDifficulty(BigInteger difficulty) { - this.difficulty = difficulty; - return this; - } - - public BigInteger getCumulativeDifficulty() { - return cumulativeDifficulty; - } - - public MoneroBlockHeader setCumulativeDifficulty(BigInteger cumulativeDifficulty) { - this.cumulativeDifficulty = cumulativeDifficulty; - return this; - } - - public Integer getMajorVersion() { - return majorVersion; - } - - public MoneroBlockHeader setMajorVersion(Integer majorVersion) { - this.majorVersion = majorVersion; - return this; - } - - public Integer getMinorVersion() { - return minorVersion; - } - - public MoneroBlockHeader setMinorVersion(Integer minorVersion) { - this.minorVersion = minorVersion; - return this; - } - - public Integer getNonce() { - return nonce; - } - - public MoneroBlockHeader setNonce(Integer nonce) { - this.nonce = nonce; - return this; - } - - public String getMinerTxId() { - return minerTxId; - } - - public MoneroBlockHeader setMinerTxId(String minerTxId) { - this.minerTxId = minerTxId; - return this; - } - - public Integer getNumTxs() { - return numTxs; - } - - public MoneroBlockHeader setNumTxs(Integer numTxs) { - this.numTxs = numTxs; - return this; - } - - public Boolean getOrphanStatus() { - return orphanStatus; - } - - public MoneroBlockHeader setOrphanStatus(Boolean orphanStatus) { - this.orphanStatus = orphanStatus; - return this; - } - - public String getPrevId() { - return prevId; - } - - public MoneroBlockHeader setPrevId(String prevId) { - this.prevId = prevId; - return this; - } - - public BigInteger getReward() { - return reward; - } - - public MoneroBlockHeader setReward(BigInteger reward) { - this.reward = reward; - return this; - } - - public String getPowHash() { - return powHash; - } - - public MoneroBlockHeader setPowHash(String powHash) { - this.powHash = powHash; - return this; - } - - public MoneroBlockHeader merge(MoneroBlockHeader header) { - assertNotNull(header); - if (this == header) return this; - this.setId(MoneroUtils.reconcile(this.getId(), header.getId())); - this.setHeight(MoneroUtils.reconcile(this.getHeight(), header.getHeight(), null, null, true)); // height can increase - this.setTimestamp(MoneroUtils.reconcile(this.getTimestamp(), header.getTimestamp(), null, null, true)); // block timestamp can increase - this.setSize(MoneroUtils.reconcile(this.getSize(), header.getSize())); - this.setWeight(MoneroUtils.reconcile(this.getWeight(), header.getWeight())); - this.setDepth(MoneroUtils.reconcile(this.getDepth(), header.getDepth())); - this.setDifficulty(MoneroUtils.reconcile(this.getDifficulty(), header.getDifficulty())); - this.setCumulativeDifficulty(MoneroUtils.reconcile(this.getCumulativeDifficulty(), header.getCumulativeDifficulty())); - this.setMajorVersion(MoneroUtils.reconcile(this.getMajorVersion(), header.getMajorVersion())); - this.setMinorVersion(MoneroUtils.reconcile(this.getMinorVersion(), header.getMinorVersion())); - this.setNonce(MoneroUtils.reconcile(this.getNonce(), header.getNonce())); - this.setMinerTxId(MoneroUtils.reconcile(this.getMinerTxId(), header.getMinerTxId())); - this.setNumTxs(MoneroUtils.reconcile(this.getNumTxs(), header.getNumTxs())); - this.setOrphanStatus(MoneroUtils.reconcile(this.getOrphanStatus(), header.getOrphanStatus())); - this.setPrevId(MoneroUtils.reconcile(this.getPrevId(), header.getPrevId())); - this.setReward(MoneroUtils.reconcile(this.getReward(), header.getReward())); - this.setPowHash(MoneroUtils.reconcile(this.getPowHash(), header.getPowHash())); - return this; - } - - public String toString() { - return toString(0); - } - - public String toString(int indent) { - StringBuilder sb = new StringBuilder(); - sb.append(MoneroUtils.kvLine("Id", getId(), indent)); - sb.append(MoneroUtils.kvLine("Height", getHeight(), indent)); - sb.append(MoneroUtils.kvLine("Timestamp", getTimestamp(), indent)); - sb.append(MoneroUtils.kvLine("Size", getSize(), indent)); - sb.append(MoneroUtils.kvLine("Weight", getWeight(), indent)); - sb.append(MoneroUtils.kvLine("Depth", getDepth(), indent)); - sb.append(MoneroUtils.kvLine("Difficulty", getDifficulty(), indent)); - sb.append(MoneroUtils.kvLine("Cumulative difficulty", getCumulativeDifficulty(), indent)); - sb.append(MoneroUtils.kvLine("Major version", getMajorVersion(), indent)); - sb.append(MoneroUtils.kvLine("Minor version", getMinorVersion(), indent)); - sb.append(MoneroUtils.kvLine("Nonce", getNonce(), indent)); - sb.append(MoneroUtils.kvLine("Miner tx id", getMinerTxId(), indent)); - sb.append(MoneroUtils.kvLine("Num txs", getNumTxs(), indent)); - sb.append(MoneroUtils.kvLine("Orphan status", getOrphanStatus(), indent)); - sb.append(MoneroUtils.kvLine("Prev id", getPrevId(), indent)); - sb.append(MoneroUtils.kvLine("Reward", getReward(), indent)); - sb.append(MoneroUtils.kvLine("Pow hash", getPowHash(), indent)); - String str = sb.toString(); - if (str.isEmpty()) return ""; - return str.charAt(str.length() - 1) == '\n' ? str.substring(0, str.length() - 1) : str; // strip newline - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((minerTxId == null) ? 0 : minerTxId.hashCode()); - result = prime * result + ((cumulativeDifficulty == null) ? 0 : cumulativeDifficulty.hashCode()); - result = prime * result + ((depth == null) ? 0 : depth.hashCode()); - result = prime * result + ((difficulty == null) ? 0 : difficulty.hashCode()); - result = prime * result + ((height == null) ? 0 : height.hashCode()); - result = prime * result + ((id == null) ? 0 : id.hashCode()); - result = prime * result + ((longTermWeight == null) ? 0 : longTermWeight.hashCode()); - result = prime * result + ((majorVersion == null) ? 0 : majorVersion.hashCode()); - result = prime * result + ((minorVersion == null) ? 0 : minorVersion.hashCode()); - result = prime * result + ((nonce == null) ? 0 : nonce.hashCode()); - result = prime * result + ((numTxs == null) ? 0 : numTxs.hashCode()); - result = prime * result + ((orphanStatus == null) ? 0 : orphanStatus.hashCode()); - result = prime * result + ((powHash == null) ? 0 : powHash.hashCode()); - result = prime * result + ((prevId == null) ? 0 : prevId.hashCode()); - result = prime * result + ((reward == null) ? 0 : reward.hashCode()); - result = prime * result + ((size == null) ? 0 : size.hashCode()); - result = prime * result + ((timestamp == null) ? 0 : timestamp.hashCode()); - result = prime * result + ((weight == null) ? 0 : weight.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - MoneroBlockHeader other = (MoneroBlockHeader) obj; - if (minerTxId == null) { - if (other.minerTxId != null) return false; - } else if (!minerTxId.equals(other.minerTxId)) return false; - if (cumulativeDifficulty == null) { - if (other.cumulativeDifficulty != null) return false; - } else if (!cumulativeDifficulty.equals(other.cumulativeDifficulty)) return false; - if (depth == null) { - if (other.depth != null) return false; - } else if (!depth.equals(other.depth)) return false; - if (difficulty == null) { - if (other.difficulty != null) return false; - } else if (!difficulty.equals(other.difficulty)) return false; - if (height == null) { - if (other.height != null) return false; - } else if (!height.equals(other.height)) return false; - if (id == null) { - if (other.id != null) return false; - } else if (!id.equals(other.id)) return false; - if (longTermWeight == null) { - if (other.longTermWeight != null) return false; - } else if (!longTermWeight.equals(other.longTermWeight)) return false; - if (majorVersion == null) { - if (other.majorVersion != null) return false; - } else if (!majorVersion.equals(other.majorVersion)) return false; - if (minorVersion == null) { - if (other.minorVersion != null) return false; - } else if (!minorVersion.equals(other.minorVersion)) return false; - if (nonce == null) { - if (other.nonce != null) return false; - } else if (!nonce.equals(other.nonce)) return false; - if (numTxs == null) { - if (other.numTxs != null) return false; - } else if (!numTxs.equals(other.numTxs)) return false; - if (orphanStatus == null) { - if (other.orphanStatus != null) return false; - } else if (!orphanStatus.equals(other.orphanStatus)) return false; - if (powHash == null) { - if (other.powHash != null) return false; - } else if (!powHash.equals(other.powHash)) return false; - if (prevId == null) { - if (other.prevId != null) return false; - } else if (!prevId.equals(other.prevId)) return false; - if (reward == null) { - if (other.reward != null) return false; - } else if (!reward.equals(other.reward)) return false; - if (size == null) { - if (other.size != null) return false; - } else if (!size.equals(other.size)) return false; - if (timestamp == null) { - if (other.timestamp != null) return false; - } else if (!timestamp.equals(other.timestamp)) return false; - if (weight == null) { - if (other.weight != null) return false; - } else if (!weight.equals(other.weight)) return false; - return true; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroBlockTemplate.java b/core/src/main/java/monero/daemon/model/MoneroBlockTemplate.java deleted file mode 100644 index ce479e7fca1..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroBlockTemplate.java +++ /dev/null @@ -1,73 +0,0 @@ -package monero.daemon.model; - -import java.math.BigInteger; - -/** - * Monero block template to mine. - */ -public class MoneroBlockTemplate { - - private String blockTemplateBlob; - private String blockHashingBlob; - private BigInteger difficulty; - private BigInteger expectedReward; - private Long height; - private String prevId; - private Long reservedOffset; - - public String getBlockTemplateBlob() { - return blockTemplateBlob; - } - - public void setBlockTemplateBlob(String blockTemplateBlob) { - this.blockTemplateBlob = blockTemplateBlob; - } - - public String getBlockHashingBlob() { - return blockHashingBlob; - } - - public void setBlockHashingBlob(String blockHashingBlob) { - this.blockHashingBlob = blockHashingBlob; - } - - public BigInteger getDifficulty() { - return difficulty; - } - - public void setDifficulty(BigInteger difficulty) { - this.difficulty = difficulty; - } - - public BigInteger getExpectedReward() { - return expectedReward; - } - - public void setExpectedReward(BigInteger expectedReward) { - this.expectedReward = expectedReward; - } - - public Long getHeight() { - return height; - } - - public void setHeight(Long height) { - this.height = height; - } - - public String getPrevId() { - return prevId; - } - - public void setPrevId(String prevId) { - this.prevId = prevId; - } - - public Long getReservedOffset() { - return reservedOffset; - } - - public void setReservedOffset(Long reservedOffset) { - this.reservedOffset = reservedOffset; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroDaemonConnection.java b/core/src/main/java/monero/daemon/model/MoneroDaemonConnection.java deleted file mode 100644 index 83648167488..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroDaemonConnection.java +++ /dev/null @@ -1,165 +0,0 @@ -package monero.daemon.model; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Monero daemon connection. - */ -public class MoneroDaemonConnection { - - private MoneroDaemonPeer peer; - private String id; - private Long avgDownload; - private Long avgUpload; - private Long currentDownload; - private Long currentUpload; - private Long height; - private Boolean isIncoming; - private Long liveTime; - private Boolean isLocalIp; - private Boolean isLocalHost; - private Integer numReceives; - private Integer numSends; - private Long receiveIdleTime; - private Long sendIdleTime; - private String state; - private Integer numSupportFlags; - - public MoneroDaemonPeer getPeer() { - return peer; - } - - public void setPeer(MoneroDaemonPeer peer) { - this.peer = peer; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public Long getAvgDownload() { - return avgDownload; - } - - public void setAvgDownload(Long avgDownload) { - this.avgDownload = avgDownload; - } - - public Long getAvgUpload() { - return avgUpload; - } - - public void setAvgUpload(Long avgUpload) { - this.avgUpload = avgUpload; - } - - public Long getCurrentDownload() { - return currentDownload; - } - - public void setCurrentDownload(Long currentDownload) { - this.currentDownload = currentDownload; - } - - public Long getCurrentUpload() { - return currentUpload; - } - - public void setCurrentUpload(Long currentUpload) { - this.currentUpload = currentUpload; - } - - public Long getHeight() { - return height; - } - - public void setHeight(Long height) { - this.height = height; - } - - @JsonProperty("isIncoming") - public Boolean isIncoming() { - return isIncoming; - } - - public void setIsIncoming(Boolean isIncoming) { - this.isIncoming = isIncoming; - } - - public Long getLiveTime() { - return liveTime; - } - - public void setLiveTime(Long liveTime) { - this.liveTime = liveTime; - } - - @JsonProperty("isLocalIp") - public Boolean isLocalIp() { - return isLocalIp; - } - - public void setIsLocalIp(Boolean isLocalIp) { - this.isLocalIp = isLocalIp; - } - - public Boolean isLocalHost() { - return isLocalHost; - } - - public void setIsLocalHost(Boolean isLocalHost) { - this.isLocalHost = isLocalHost; - } - - public Integer getNumReceives() { - return numReceives; - } - - public void setNumReceives(Integer numReceives) { - this.numReceives = numReceives; - } - - public Integer getNumSends() { - return numSends; - } - - public void setNumSends(Integer numSends) { - this.numSends = numSends; - } - - public Long getReceiveIdleTime() { - return receiveIdleTime; - } - - public void setReceiveIdleTime(Long receiveIdleTime) { - this.receiveIdleTime = receiveIdleTime; - } - - public Long getSendIdleTime() { - return sendIdleTime; - } - - public void setSendIdleTime(Long sendIdleTime) { - this.sendIdleTime = sendIdleTime; - } - - public String getState() { - return state; - } - - public void setState(String state) { - this.state = state; - } - - public Integer getNumSupportFlags() { - return numSupportFlags; - } - - public void setNumSupportFlags(Integer numSupportFlags) { - this.numSupportFlags = numSupportFlags; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroDaemonConnectionSpan.java b/core/src/main/java/monero/daemon/model/MoneroDaemonConnectionSpan.java deleted file mode 100644 index 4c1b18ef75a..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroDaemonConnectionSpan.java +++ /dev/null @@ -1,71 +0,0 @@ -package monero.daemon.model; - -/** - * Monero daemon connection span. - */ -public class MoneroDaemonConnectionSpan { - - private String connectionId; - private Long numBlocks; - private String remoteAddress; - private Long rate; - private Long speed; - private Long size; - private Long startBlockHeight; - - public String getConnectionId() { - return connectionId; - } - - public void setConnectionId(String connectionId) { - this.connectionId = connectionId; - } - - public Long getNumBlocks() { - return numBlocks; - } - - public void setNumBlocks(Long numBlocks) { - this.numBlocks = numBlocks; - } - - public String getRemoteAddress() { - return remoteAddress; - } - - public void setRemoteAddress(String remoteAddress) { - this.remoteAddress = remoteAddress; - } - - public Long getRate() { - return rate; - } - - public void setRate(Long rate) { - this.rate = rate; - } - - public Long getSpeed() { - return speed; - } - - public void setSpeed(Long speed) { - this.speed = speed; - } - - public Long getSize() { - return size; - } - - public void setSize(Long size) { - this.size = size; - } - - public Long getStartHeight() { - return startBlockHeight; - } - - public void setStartHeight(Long startHeight) { - this.startBlockHeight = startHeight; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroDaemonInfo.java b/core/src/main/java/monero/daemon/model/MoneroDaemonInfo.java deleted file mode 100644 index 029e4067717..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroDaemonInfo.java +++ /dev/null @@ -1,266 +0,0 @@ -package monero.daemon.model; - -import java.math.BigInteger; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Monero daemon info. - */ -public class MoneroDaemonInfo { - - private String version; - private Long numAltBlocks; - private Long blockSizeLimit; - private Long blockSizeMedian; - private Long blockWeightLimit; - private Long blockWeightMedian; - private String bootstrapDaemonAddress; - private BigInteger difficulty; - private BigInteger cumulativeDifficulty; - private BigInteger freeSpace; - private Integer numOfflinePeers; - private Integer numOnlinePeers; - private Long height; - private Long heightWithoutBootstrap; - private MoneroNetworkType networkType; - private Boolean isOffline; - private Integer numIncomingConnections; - private Integer numOutgoingConnections; - private Integer numRpcConnections; - private Long startTimestamp; - private Long target; - private Long targetHeight; - private String topBlockId; - private Integer numTxs; - private Integer numTxsPool; - private Boolean wasBootstrapEverUsed; - private Long databaseSize; - private Boolean updateAvailable; - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - public Long getNumAltBlocks() { - return numAltBlocks; - } - - public void setNumAltBlocks(Long numAltBlocks) { - this.numAltBlocks = numAltBlocks; - } - - public Long getBlockSizeLimit() { - return blockSizeLimit; - } - - public void setBlockSizeLimit(Long blockSizeLimit) { - this.blockSizeLimit = blockSizeLimit; - } - - public Long getBlockSizeMedian() { - return blockSizeMedian; - } - - public void setBlockSizeMedian(Long blockSizeMedian) { - this.blockSizeMedian = blockSizeMedian; - } - - public Long getBlockWeightLimit() { - return blockWeightLimit; - } - - public MoneroDaemonInfo setBlockWeightLimit(Long blockWeightLimit) { - this.blockWeightLimit = blockWeightLimit; - return this; - } - - public Long getBlockWeightMedian() { - return blockWeightMedian; - } - - public void setBlockWeightMedian(Long blockWeightMedian) { - this.blockWeightMedian = blockWeightMedian; - } - - public String getBootstrapDaemonAddress() { - return bootstrapDaemonAddress; - } - - public void setBootstrapDaemonAddress(String bootstrapDaemonAddress) { - this.bootstrapDaemonAddress = bootstrapDaemonAddress; - } - - public BigInteger getDifficulty() { - return difficulty; - } - - public void setDifficulty(BigInteger difficulty) { - this.difficulty = difficulty; - } - - public BigInteger getCumulativeDifficulty() { - return cumulativeDifficulty; - } - - public void setCumulativeDifficulty(BigInteger cumulativeDifficulty) { - this.cumulativeDifficulty = cumulativeDifficulty; - } - - public BigInteger getFreeSpace() { - return freeSpace; - } - - public void setFreeSpace(BigInteger freeSpace) { - this.freeSpace = freeSpace; - } - - public Integer getNumOfflinePeers() { - return numOfflinePeers; - } - - public void setNumOfflinePeers(Integer numOfflinePeers) { - this.numOfflinePeers = numOfflinePeers; - } - - public Integer getNumOnlinePeers() { - return numOnlinePeers; - } - - public void setNumOnlinePeers(Integer numOnlinePeers) { - this.numOnlinePeers = numOnlinePeers; - } - - public Long getHeight() { - return height; - } - - public void setHeight(Long height) { - this.height = height; - } - - public Long getHeightWithoutBootstrap() { - return heightWithoutBootstrap; - } - - public void setHeightWithoutBootstrap(Long heightWithoutBootstrap) { - this.heightWithoutBootstrap = heightWithoutBootstrap; - } - - public MoneroNetworkType getNetworkType() { - return networkType; - } - - public void setNetworkType(MoneroNetworkType networkType) { - this.networkType = networkType; - } - - @JsonProperty("isOffline") - public Boolean isOffline() { - return isOffline; - } - - public void setIsOffline(Boolean isOffline) { - this.isOffline = isOffline; - } - - public Integer getNumIncomingConnections() { - return numIncomingConnections; - } - - public void setNumIncomingConnections(Integer numIncomingConnections) { - this.numIncomingConnections = numIncomingConnections; - } - - public Integer getNumOutgoingConnections() { - return numOutgoingConnections; - } - - public void setNumOutgoingConnections(Integer numOutgoingConnections) { - this.numOutgoingConnections = numOutgoingConnections; - } - - public Integer getNumRpcConnections() { - return numRpcConnections; - } - - public void setNumRpcConnections(Integer numRpcConnections) { - this.numRpcConnections = numRpcConnections; - } - - public Long getStartTimestamp() { - return startTimestamp; - } - - public void setStartTimestamp(Long startTimestamp) { - this.startTimestamp = startTimestamp; - } - - public Long getTarget() { - return target; - } - - public void setTarget(Long target) { - this.target = target; - } - - public Long getTargetHeight() { - return targetHeight; - } - - public void setTargetHeight(Long targetHeight) { - this.targetHeight = targetHeight; - } - - public String getTopBlockId() { - return topBlockId; - } - - public void setTopBlockId(String topBlockId) { - this.topBlockId = topBlockId; - } - - public Integer getNumTxs() { - return numTxs; - } - - public void setNumTxs(Integer numTxs) { - this.numTxs = numTxs; - } - - public Integer getNumTxsPool() { - return numTxsPool; - } - - public void setNumTxsPool(Integer numTxsPool) { - this.numTxsPool = numTxsPool; - } - - public Boolean getWasBootstrapEverUsed() { - return wasBootstrapEverUsed; - } - - public void setWasBootstrapEverUsed(Boolean wasBootstrapEverUsed) { - this.wasBootstrapEverUsed = wasBootstrapEverUsed; - } - - public Long getDatabaseSize() { - return databaseSize; - } - - public void setDatabaseSize(Long databaseSize) { - this.databaseSize = databaseSize; - } - - public Boolean getUpdateAvailable() { - return updateAvailable; - } - - public void setUpdateAvailable(Boolean updateAvailable) { - this.updateAvailable = updateAvailable; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroDaemonListener.java b/core/src/main/java/monero/daemon/model/MoneroDaemonListener.java deleted file mode 100644 index 241c275b880..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroDaemonListener.java +++ /dev/null @@ -1,27 +0,0 @@ -package monero.daemon.model; - -/** - * Receives notifications as a daemon is updated. - */ -public class MoneroDaemonListener { - - private MoneroBlockHeader lastHeader; - - /** - * Called when a new block is added to the chain. - * - * @param header is the header of the block added to the chain - */ - public void onBlockHeader(MoneroBlockHeader header) { - lastHeader = header; - } - - /** - * Get the last notified block header. - * - * @return the last notified block header - */ - public MoneroBlockHeader getLastBlockHeader() { - return lastHeader; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroDaemonPeer.java b/core/src/main/java/monero/daemon/model/MoneroDaemonPeer.java deleted file mode 100644 index 70b8df5c6f6..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroDaemonPeer.java +++ /dev/null @@ -1,83 +0,0 @@ -package monero.daemon.model; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Models a peer to the daemon. - */ -public class MoneroDaemonPeer { - - private String id; - private String address; - private String host; - private Integer port; - private Integer rpcPort; - private Boolean isOnline; - private Long lastSeenTimestamp; - private Integer pruningSeed; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public String getHost() { - return host; - } - - public void setHost(String host) { - this.host = host; - } - - public Integer getPort() { - return port; - } - - public void setPort(Integer port) { - this.port = port; - } - - public Integer getRpcPort() { - return rpcPort; - } - - public void setRpcPort(Integer rpcPort) { - this.rpcPort = rpcPort; - } - - @JsonProperty("isOnline") - public Boolean isOnline() { - return isOnline; - } - - public void setIsOnline(Boolean isOnline) { - this.isOnline = isOnline; - } - - public Long getLastSeenTimestamp() { - return lastSeenTimestamp; - } - - public void setLastSeenTimestamp(Long lastSeenTimestamp) { - this.lastSeenTimestamp = lastSeenTimestamp; - } - - public Integer getPruningSeed() { - return pruningSeed; - } - - public void setPruningSeed(Integer pruningSeed) { - this.pruningSeed = pruningSeed; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroDaemonSyncInfo.java b/core/src/main/java/monero/daemon/model/MoneroDaemonSyncInfo.java deleted file mode 100644 index 37c363ab8a2..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroDaemonSyncInfo.java +++ /dev/null @@ -1,64 +0,0 @@ -package monero.daemon.model; - -import java.util.List; - -/** - * Models daemon synchronization information. - */ -public class MoneroDaemonSyncInfo { - - private Long height; - private List connections; - private List spans; - private Long targetHeight; - private Integer nextNeededPruningSeed; - private String overview; - - public Long getHeight() { - return height; - } - - public void setHeight(Long height) { - this.height = height; - } - - public List getConnections() { - return connections; - } - - public void setConnections(List connections) { - this.connections = connections; - } - - public List getSpans() { - return spans; - } - - public void setSpans(List spans) { - this.spans = spans; - } - - public Long getTargetHeight() { - return targetHeight; - } - - public void setTargetHeight(Long targetHeight) { - this.targetHeight = targetHeight; - } - - public Integer getNextNeededPruningSeed() { - return nextNeededPruningSeed; - } - - public void setNextNeededPruningSeed(Integer nextNeededPruningSeed) { - this.nextNeededPruningSeed = nextNeededPruningSeed; - } - - public String getOverview() { - return overview; - } - - public void setOverview(String overview) { - this.overview = overview; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroDaemonUpdateCheckResult.java b/core/src/main/java/monero/daemon/model/MoneroDaemonUpdateCheckResult.java deleted file mode 100644 index 6bdaae0df65..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroDaemonUpdateCheckResult.java +++ /dev/null @@ -1,68 +0,0 @@ -package monero.daemon.model; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Models the result of checking for a daemon update. - */ -public class MoneroDaemonUpdateCheckResult { - - private Boolean isUpdateAvailable; - private String version; - private String hash; - private String autoUri; - private String userUri; - - public MoneroDaemonUpdateCheckResult() { - // nothing to construct - } - - MoneroDaemonUpdateCheckResult(MoneroDaemonUpdateCheckResult checkResult) { - this.isUpdateAvailable = checkResult.isUpdateAvailable; - this.version = checkResult.version; - this.hash = checkResult.hash; - this.autoUri = checkResult.autoUri; - this.userUri = checkResult.userUri; - } - - @JsonProperty("isUpdateAvailable") - public Boolean isUpdateAvailable() { - return isUpdateAvailable; - } - - public void setIsUpdateAvailable(Boolean isUpdateAvailable) { - this.isUpdateAvailable = isUpdateAvailable; - } - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - public String getHash() { - return hash; - } - - public void setHash(String hash) { - this.hash = hash; - } - - public String getAutoUri() { - return autoUri; - } - - public void setAutoUri(String autoUri) { - this.autoUri = autoUri; - } - - public String getUserUri() { - return userUri; - } - - public void setUserUri(String userUri) { - this.userUri = userUri; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroDaemonUpdateDownloadResult.java b/core/src/main/java/monero/daemon/model/MoneroDaemonUpdateDownloadResult.java deleted file mode 100644 index 77b33a03765..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroDaemonUpdateDownloadResult.java +++ /dev/null @@ -1,21 +0,0 @@ -package monero.daemon.model; - -/** - * Models the result of downloading an update. - */ -public class MoneroDaemonUpdateDownloadResult extends MoneroDaemonUpdateCheckResult { - - private String downloadPath; - - public MoneroDaemonUpdateDownloadResult(MoneroDaemonUpdateCheckResult checkResult) { - super(checkResult); - } - - public String getDownloadPath() { - return downloadPath; - } - - public void setDownloadPath(String downloadPath) { - this.downloadPath = downloadPath; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroHardForkInfo.java b/core/src/main/java/monero/daemon/model/MoneroHardForkInfo.java deleted file mode 100644 index c9c440af35b..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroHardForkInfo.java +++ /dev/null @@ -1,83 +0,0 @@ -package monero.daemon.model; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Monero hard fork info. - */ -public class MoneroHardForkInfo { - - private Long earliestHeight; - private Boolean isEnabled; - private Integer state; - private Integer threshold; - private Integer version; - private Integer numVotes; - private Integer window; - private Integer voting; - - public Long getEarliestHeight() { - return earliestHeight; - } - - public void setEarliestHeight(Long earliestHeight) { - this.earliestHeight = earliestHeight; - } - - @JsonProperty("isEnabled") - public Boolean isEnabled() { - return isEnabled; - } - - public void setIsEnabled(Boolean isEnabled) { - this.isEnabled = isEnabled; - } - - public Integer getState() { - return state; - } - - public void setState(Integer state) { - this.state = state; - } - - public Integer getThreshold() { - return threshold; - } - - public void setThreshold(Integer threshold) { - this.threshold = threshold; - } - - public Integer getVersion() { - return version; - } - - public void setVersion(Integer version) { - this.version = version; - } - - public Integer getNumVotes() { - return numVotes; - } - - public void setNumVotes(Integer numVotes) { - this.numVotes = numVotes; - } - - public Integer getWindow() { - return window; - } - - public void setWindow(Integer window) { - this.window = window; - } - - public Integer getVoting() { - return voting; - } - - public void setVoting(Integer voting) { - this.voting = voting; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroKeyImage.java b/core/src/main/java/monero/daemon/model/MoneroKeyImage.java deleted file mode 100644 index fb15d619813..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroKeyImage.java +++ /dev/null @@ -1,98 +0,0 @@ -package monero.daemon.model; - -import static org.junit.Assert.assertTrue; - -import monero.utils.MoneroUtils; - -/** - * Models a Monero key image. - */ -public class MoneroKeyImage { - - private String hex; - private String signature; - - public MoneroKeyImage() { - // nothing to construct - } - - public MoneroKeyImage(String hex) { - this(hex, null); - } - - public MoneroKeyImage(String hex, String signature) { - this.hex = hex; - this.signature = signature; - } - - public MoneroKeyImage(MoneroKeyImage keyImage) { - this.hex = keyImage.hex; - this.signature = keyImage.signature; - } - - public String getHex() { - return hex; - } - - public MoneroKeyImage setHex(String hex) { - this.hex = hex; - return this; - } - - public String getSignature() { - return signature; - } - - public MoneroKeyImage setSignature(String signature) { - this.signature = signature; - return this; - } - - public MoneroKeyImage copy() { - return new MoneroKeyImage(this); - } - - public MoneroKeyImage merge(MoneroKeyImage keyImage) { - assertTrue(keyImage instanceof MoneroKeyImage); - if (keyImage == this) return this; - this.setHex(MoneroUtils.reconcile(this.getHex(), keyImage.getHex())); - this.setSignature(MoneroUtils.reconcile(this.getSignature(), keyImage.getSignature())); - return this; - } - - public String toString() { - return toString(0); - } - - public String toString(int indent) { - StringBuilder sb = new StringBuilder(); - sb.append(MoneroUtils.kvLine("Hex", getHex(), indent)); - sb.append(MoneroUtils.kvLine("Signature", getSignature(), indent)); - String str = sb.toString(); - return str.substring(0, str.length() - 1); // strip newline - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((hex == null) ? 0 : hex.hashCode()); - result = prime * result + ((signature == null) ? 0 : signature.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - MoneroKeyImage other = (MoneroKeyImage) obj; - if (hex == null) { - if (other.hex != null) return false; - } else if (!hex.equals(other.hex)) return false; - if (signature == null) { - if (other.signature != null) return false; - } else if (!signature.equals(other.signature)) return false; - return true; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroKeyImageSpentStatus.java b/core/src/main/java/monero/daemon/model/MoneroKeyImageSpentStatus.java deleted file mode 100644 index a90490980bb..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroKeyImageSpentStatus.java +++ /dev/null @@ -1,19 +0,0 @@ -package monero.daemon.model; - -import monero.utils.MoneroException; - -/** - * Enumerate key image spent statuses. - */ -public enum MoneroKeyImageSpentStatus { - NOT_SPENT, - CONFIRMED, - TX_POOL; - - public static MoneroKeyImageSpentStatus valueOf(int status) { - if (status == 0) return NOT_SPENT; - else if (status == 1) return CONFIRMED; - else if (status == 2) return TX_POOL; - throw new MoneroException("Invalid integer value for spent status: " + status); - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroMinerTxSum.java b/core/src/main/java/monero/daemon/model/MoneroMinerTxSum.java deleted file mode 100644 index b9b0bc600dc..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroMinerTxSum.java +++ /dev/null @@ -1,28 +0,0 @@ -package monero.daemon.model; - -import java.math.BigInteger; - -/** - * Model for the summation of miner emissions and fees. - */ -public class MoneroMinerTxSum { - - private BigInteger emissionSum; - private BigInteger feeSum; - - public BigInteger getEmissionSum() { - return emissionSum; - } - - public void setEmissionSum(BigInteger emissionSum) { - this.emissionSum = emissionSum; - } - - public BigInteger getFeeSum() { - return feeSum; - } - - public void setFeeSum(BigInteger feeSum) { - this.feeSum = feeSum; - } -} \ No newline at end of file diff --git a/core/src/main/java/monero/daemon/model/MoneroMiningStatus.java b/core/src/main/java/monero/daemon/model/MoneroMiningStatus.java deleted file mode 100644 index 826785c12fa..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroMiningStatus.java +++ /dev/null @@ -1,57 +0,0 @@ -package monero.daemon.model; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Monero daemon mining status. - */ -public class MoneroMiningStatus { - - private Boolean isActive; - private Boolean isBackground; - private String address; - private Long speed; - private Integer numThreads; - - @JsonProperty("isActive") - public Boolean isActive() { - return isActive; - } - - public void setIsActive(Boolean isActive) { - this.isActive = isActive; - } - - @JsonProperty("isBackground") - public Boolean isBackground() { - return isBackground; - } - - public void setIsBackground(Boolean isBackground) { - this.isBackground = isBackground; - } - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public Long getSpeed() { - return speed; - } - - public void setSpeed(Long speed) { - this.speed = speed; - } - - public Integer getNumThreads() { - return numThreads; - } - - public void setNumThreads(Integer numThreads) { - this.numThreads = numThreads; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroNetworkType.java b/core/src/main/java/monero/daemon/model/MoneroNetworkType.java deleted file mode 100644 index d00fb7f3d95..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroNetworkType.java +++ /dev/null @@ -1,10 +0,0 @@ -package monero.daemon.model; - -/** - * Enumerates daemon networks. - */ -public enum MoneroNetworkType { - MAINNET, - TESTNET, - STAGENET -} diff --git a/core/src/main/java/monero/daemon/model/MoneroOutput.java b/core/src/main/java/monero/daemon/model/MoneroOutput.java deleted file mode 100644 index 71901cf8773..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroOutput.java +++ /dev/null @@ -1,167 +0,0 @@ -package monero.daemon.model; - -import static org.junit.Assert.assertTrue; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonBackReference; - -import monero.utils.MoneroUtils; - -/** - * Models a Monero transaction output. - */ -public class MoneroOutput { - - private MoneroTx tx; - private MoneroKeyImage keyImage; - private BigInteger amount; - private Integer index; - private List ringOutputIndices; - private String stealthPublicKey; - - public MoneroOutput() { - // nothing to build - } - - public MoneroOutput(final MoneroOutput output) { - if (output.keyImage != null) this.keyImage = output.keyImage.copy(); - this.amount = output.amount; - this.index = output.index; - if (output.ringOutputIndices != null) this.ringOutputIndices = new ArrayList(output.ringOutputIndices); - this.stealthPublicKey = output.stealthPublicKey; - } - - public MoneroOutput copy() { - return new MoneroOutput(this); - } - - @JsonBackReference - public MoneroTx getTx() { - return tx; - } - - public MoneroOutput setTx(MoneroTx tx) { - this.tx = tx; - return this; - } - - public MoneroKeyImage getKeyImage() { - return keyImage; - } - - public MoneroOutput setKeyImage(MoneroKeyImage keyImage) { - this.keyImage = keyImage; - return this; - } - - public BigInteger getAmount() { - return amount; - } - - public MoneroOutput setAmount(BigInteger amount) { - this.amount = amount; - return this; - } - - public Integer getIndex() { - return index; - } - - public MoneroOutput setIndex(Integer index) { - this.index = index; - return this; - } - - public List getRingOutputIndices() { - return ringOutputIndices; - } - - public MoneroOutput setRingOutputIndices(List ringOutputIndices) { - this.ringOutputIndices = ringOutputIndices; - return this; - } - - public String getStealthPublicKey() { - return stealthPublicKey; - } - - public MoneroOutput setStealthPublicKey(String stealthPublicKey) { - this.stealthPublicKey = stealthPublicKey; - return this; - } - - public String toString() { - return toString(0); - } - - public MoneroOutput merge(MoneroOutput output) { - assertTrue(output instanceof MoneroOutput); - if (this == output) return this; - - // merge txs if they're different which comes back to merging outputs - if (this.getTx() != output.getTx()) this.getTx().merge(output.getTx()); - - // otherwise merge output fields - else { - if (this.getKeyImage() == null) this.setKeyImage(output.getKeyImage()); - else if (output.getKeyImage() != null) this.getKeyImage().merge(output.getKeyImage()); - this.setAmount(MoneroUtils.reconcile(this.getAmount(), output.getAmount())); - this.setIndex(MoneroUtils.reconcile(this.getIndex(), output.getIndex())); - } - - return this; - } - - public String toString(int indent) { - StringBuilder sb = new StringBuilder(); - if (getKeyImage() != null) { - sb.append(MoneroUtils.kvLine("Key image", "", indent)); - sb.append(getKeyImage().toString(indent + 1) + "\n"); - } - sb.append(MoneroUtils.kvLine("Amount", getAmount(), indent)); - sb.append(MoneroUtils.kvLine("Index", getIndex(), indent)); - sb.append(MoneroUtils.kvLine("Ring output indices", getRingOutputIndices(), indent)); - sb.append(MoneroUtils.kvLine("Stealth public key", getStealthPublicKey(), indent)); - String str = sb.toString(); - return str.isEmpty() ? str : str.substring(0, str.length() - 1); // strip newline - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((amount == null) ? 0 : amount.hashCode()); - result = prime * result + ((index == null) ? 0 : index.hashCode()); - result = prime * result + ((keyImage == null) ? 0 : keyImage.hashCode()); - result = prime * result + ((ringOutputIndices == null) ? 0 : ringOutputIndices.hashCode()); - result = prime * result + ((stealthPublicKey == null) ? 0 : stealthPublicKey.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - MoneroOutput other = (MoneroOutput) obj; - if (amount == null) { - if (other.amount != null) return false; - } else if (!amount.equals(other.amount)) return false; - if (index == null) { - if (other.index != null) return false; - } else if (!index.equals(other.index)) return false; - if (keyImage == null) { - if (other.keyImage != null) return false; - } else if (!keyImage.equals(other.keyImage)) return false; - if (ringOutputIndices == null) { - if (other.ringOutputIndices != null) return false; - } else if (!ringOutputIndices.equals(other.ringOutputIndices)) return false; - if (stealthPublicKey == null) { - if (other.stealthPublicKey != null) return false; - } else if (!stealthPublicKey.equals(other.stealthPublicKey)) return false; - return true; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroOutputDistributionEntry.java b/core/src/main/java/monero/daemon/model/MoneroOutputDistributionEntry.java deleted file mode 100644 index 9ca69dabfcf..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroOutputDistributionEntry.java +++ /dev/null @@ -1,47 +0,0 @@ -package monero.daemon.model; - -import java.math.BigInteger; -import java.util.List; - -/** - * Monero output distribution entry. - */ -public class MoneroOutputDistributionEntry { - - private BigInteger amount; - private Integer base; - private List distribution; - private Long startHeight; - - public BigInteger getAmount() { - return amount; - } - - public void setAmount(BigInteger amount) { - this.amount = amount; - } - - public Integer getBase() { - return base; - } - - public void setBase(Integer base) { - this.base = base; - } - - public List getDistribution() { - return distribution; - } - - public void setDistribution(List distribution) { - this.distribution = distribution; - } - - public Long getStartHeight() { - return startHeight; - } - - public void setStartHeight(Long startHeight) { - this.startHeight = startHeight; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroOutputHistogramEntry.java b/core/src/main/java/monero/daemon/model/MoneroOutputHistogramEntry.java deleted file mode 100644 index e22d69bf3ad..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroOutputHistogramEntry.java +++ /dev/null @@ -1,46 +0,0 @@ -package monero.daemon.model; - -import java.math.BigInteger; - -/** - * Entry in a Monero output histogram (see get_output_histogram of Daemon RPC documentation). - */ -public class MoneroOutputHistogramEntry { - - private BigInteger amount; - private Long numInstances; - private Long numUnlockedInstances; - private Long numRecentInstances; - - public BigInteger getAmount() { - return amount; - } - - public void setAmount(BigInteger amount) { - this.amount = amount; - } - - public Long getNumInstances() { - return numInstances; - } - - public void setNumInstances(Long numInstances) { - this.numInstances = numInstances; - } - - public Long getNumUnlockedInstances() { - return numUnlockedInstances; - } - - public void setNumUnlockedInstances(Long numUnlockedInstances) { - this.numUnlockedInstances = numUnlockedInstances; - } - - public Long getNumRecentInstances() { - return numRecentInstances; - } - - public void setNumRecentInstances(Long numRecentInstances) { - this.numRecentInstances = numRecentInstances; - } -} \ No newline at end of file diff --git a/core/src/main/java/monero/daemon/model/MoneroSubmitTxResult.java b/core/src/main/java/monero/daemon/model/MoneroSubmitTxResult.java deleted file mode 100644 index d26390c3a01..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroSubmitTxResult.java +++ /dev/null @@ -1,127 +0,0 @@ -package monero.daemon.model; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Models the result from submitting a tx to a daemon. - */ -public class MoneroSubmitTxResult { - - private Boolean isGood; - private Boolean isRelayed; - private Boolean isDoubleSpend; - private Boolean isFeeTooLow; - private Boolean isMixinTooLow; - private Boolean hasInvalidInput; - private Boolean hasInvalidOutput; - private Boolean isRct; - private Boolean isOverspend; - private Boolean isTooBig; - private Boolean sanityCheckFailed; - private String reason; - - @JsonProperty("isGood") - public Boolean isGood() { - return isGood; - } - - public void setIsGood(Boolean isGood) { - this.isGood = isGood; - } - - @JsonProperty("isRelayed") - public Boolean isRelayed() { - return isRelayed; - } - - public void setIsRelayed(Boolean isRelayed) { - this.isRelayed = isRelayed; - } - - @JsonProperty("isDoubleSpend") - public Boolean isDoubleSpend() { - return isDoubleSpend; - } - - public void setIsDoubleSpend(Boolean isDoubleSpend) { - this.isDoubleSpend = isDoubleSpend; - } - - @JsonProperty("isFeeTooLow") - public Boolean isFeeTooLow() { - return isFeeTooLow; - } - - public void setIsFeeTooLow(Boolean isFeeTooLow) { - this.isFeeTooLow = isFeeTooLow; - } - - @JsonProperty("isMixinTooLow") - public Boolean isMixinTooLow() { - return isMixinTooLow; - } - - public void setIsMixinTooLow(Boolean isMixinTooLow) { - this.isMixinTooLow = isMixinTooLow; - } - - @JsonProperty("hasInvalidInput") - public Boolean hasInvalidInput() { - return hasInvalidInput; - } - - public void setHasInvalidInput(Boolean hasInvalidInput) { - this.hasInvalidInput = hasInvalidInput; - } - - public Boolean hasInvalidOutput() { - return hasInvalidOutput; - } - - public void setHasInvalidOutput(Boolean hasInvalidOutput) { - this.hasInvalidOutput = hasInvalidOutput; - } - - @JsonProperty("isRct") - public Boolean isRct() { - return isRct; - } - - public void setIsRct(Boolean isRct) { - this.isRct = isRct; - } - - @JsonProperty("isOverspend") - public Boolean isOverspend() { - return isOverspend; - } - - public void setIsOverspend(Boolean isOverspend) { - this.isOverspend = isOverspend; - } - - @JsonProperty("isTooBig") - public Boolean isTooBig() { - return isTooBig; - } - - public void setIsTooBig(Boolean isTooBig) { - this.isTooBig = isTooBig; - } - - public Boolean getSanityCheckFailed() { - return sanityCheckFailed; - } - - public void setSanityCheckFailed(Boolean sanityCheckFailed) { - this.sanityCheckFailed = sanityCheckFailed; - } - - public String getReason() { - return reason; - } - - public void setReason(String reason) { - this.reason = reason; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroTx.java b/core/src/main/java/monero/daemon/model/MoneroTx.java deleted file mode 100644 index 9dc7dff3510..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroTx.java +++ /dev/null @@ -1,843 +0,0 @@ -package monero.daemon.model; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonBackReference; -import com.fasterxml.jackson.annotation.JsonManagedReference; -import com.fasterxml.jackson.annotation.JsonProperty; - -import common.utils.GenUtils; -import monero.utils.MoneroUtils; - -/** - * Represents a transaction on the Monero network. - */ -public class MoneroTx { - - public static final String DEFAULT_PAYMENT_ID = "0000000000000000"; - - private MoneroBlock block; - private String id; - private Integer version; - private Boolean isMinerTx; - private String paymentId; - private BigInteger fee; - private Integer mixin; - private Boolean doNotRelay; - private Boolean isRelayed; - private Boolean isConfirmed; - private Boolean inTxPool; - private Long numConfirmations; - private Long unlockTime; - private Long lastRelayedTimestamp; - private Long receivedTimestamp; - private Boolean isDoubleSpendSeen; - private String key; - private String fullHex; - private String prunedHex; - private String prunableHex; - private String prunableHash; - private Long size; - private Long weight; - private List vins; - private List vouts; - private List outputIndices; - private String metadata; - private int[] extra; - private Object rctSignatures; // TODO: implement - private Object rctSigPrunable; // TODO: implement - private Boolean isKeptByBlock; - private Boolean isFailed; - private Long lastFailedHeight; - private String lastFailedId; - private Long maxUsedBlockHeight; - private String maxUsedBlockId; - private List signatures; - - public MoneroTx() { - // nothing to build - } - - /** - * Construct this transaction as a deep copy of the given transaction. - * - * @param tx is the transaction to make a deep copy of - */ - public MoneroTx(final MoneroTx tx) { - this.id = tx.id; - this.version = tx.version; - this.isMinerTx = tx.isMinerTx; - this.paymentId = tx.paymentId; - this.fee = tx.fee; - this.mixin = tx.mixin; - this.doNotRelay = tx.doNotRelay; - this.isRelayed = tx.isRelayed; - this.isConfirmed = tx.isConfirmed; - this.inTxPool = tx.inTxPool; - this.numConfirmations = tx.numConfirmations; - this.unlockTime = tx.unlockTime; - this.lastRelayedTimestamp = tx.lastRelayedTimestamp; - this.receivedTimestamp = tx.receivedTimestamp; - this.isDoubleSpendSeen = tx.isDoubleSpendSeen; - this.key = tx.key; - this.fullHex = tx.fullHex; - this.prunedHex = tx.prunedHex; - this.prunableHex = tx.prunableHex; - this.prunableHash = tx.prunableHash; - this.size = tx.size; - this.weight = tx.weight; - if (tx.vins != null) { - this.vins = new ArrayList(); - for (MoneroOutput vin : tx.vins) vins.add(vin.copy().setTx(this)); - } - if (tx.vouts != null) { - this.vouts = new ArrayList(); - for (MoneroOutput vout : tx.vouts) vouts.add(vout.copy().setTx(this)); - } - if (tx.outputIndices != null) this.outputIndices = new ArrayList(tx.outputIndices); - this.metadata = tx.metadata; - if (tx.extra != null) this.extra = tx.extra.clone(); - this.rctSignatures = tx.rctSignatures; - this.rctSigPrunable = tx.rctSigPrunable; - this.isKeptByBlock = tx.isKeptByBlock; - this.isFailed = tx.isFailed; - this.lastFailedHeight = tx.lastFailedHeight; - this.lastFailedId = tx.lastFailedId; - this.maxUsedBlockHeight = tx.maxUsedBlockHeight; - this.maxUsedBlockId = tx.maxUsedBlockId; - if (tx.signatures != null) this.signatures = new ArrayList(tx.signatures); - } - - public MoneroTx copy() { - return new MoneroTx(this); - } - - @JsonBackReference("block_txs") - public MoneroBlock getBlock() { - return block; - } - - public MoneroTx setBlock(MoneroBlock block) { - this.block = block; - return this; - } - - public Long getHeight() { - return this.getBlock() == null ? null : this.getBlock().getHeight(); - } - - public String getId() { - return id; - } - - public MoneroTx setId(String id) { - this.id = id; - return this; - } - - public Integer getVersion() { - return version; - } - - public MoneroTx setVersion(Integer version) { - this.version = version; - return this; - } - - @JsonProperty("isMinerTx") - public Boolean isMinerTx() { - return isMinerTx; - } - - public MoneroTx setIsMinerTx(Boolean isMinerTx) { - this.isMinerTx = isMinerTx; - return this; - } - - public String getPaymentId() { - return paymentId; - } - - public MoneroTx setPaymentId(String paymentId) { - this.paymentId = paymentId; - return this; - } - - public BigInteger getFee() { - return fee; - } - - public MoneroTx setFee(BigInteger fee) { - this.fee = fee; - return this; - } - - public Integer getMixin() { - return mixin; - } - - public MoneroTx setMixin(Integer mixin) { - this.mixin = mixin; - return this; - } - - public Boolean getDoNotRelay() { - return doNotRelay; - } - - public MoneroTx setDoNotRelay(Boolean doNotRelay) { - this.doNotRelay = doNotRelay; - return this; - } - - @JsonProperty("isRelayed") - public Boolean isRelayed() { - return isRelayed; - } - - public MoneroTx setIsRelayed(Boolean isRelayed) { - this.isRelayed = isRelayed; - return this; - } - - @JsonProperty("isConfirmed") - public Boolean isConfirmed() { - return isConfirmed; - } - - public MoneroTx setIsConfirmed(Boolean isConfirmed) { - this.isConfirmed = isConfirmed; - return this; - } - - @JsonProperty("inTxPool") - public Boolean inTxPool() { - return inTxPool; - } - - public MoneroTx setInTxPool(Boolean inTxPool) { - this.inTxPool = inTxPool; - return this; - } - - public Long getNumConfirmations() { - return numConfirmations; - } - - public MoneroTx setNumConfirmations(Long numConfirmations) { - this.numConfirmations = numConfirmations; - return this; - } - - public Long getUnlockTime() { - return unlockTime; - } - - public MoneroTx setUnlockTime(Long unlockTime) { - this.unlockTime = unlockTime; - return this; - } - - public Long getLastRelayedTimestamp() { - return lastRelayedTimestamp; - } - - public MoneroTx setLastRelayedTimestamp(Long lastRelayedTimestamp) { - this.lastRelayedTimestamp = lastRelayedTimestamp; - return this; - } - - public Long getReceivedTimestamp() { - return receivedTimestamp; - } - - public MoneroTx setReceivedTimestamp(Long receivedTimestamp) { - this.receivedTimestamp = receivedTimestamp; - return this; - } - - @JsonProperty("isDoubleSpendSeen") - public Boolean isDoubleSpendSeen() { - return isDoubleSpendSeen; - } - - public MoneroTx setIsDoubleSpendSeen(Boolean isDoubleSpend) { - this.isDoubleSpendSeen = isDoubleSpend; - return this; - } - - public String getKey() { - return key; - } - - public MoneroTx setKey(String key) { - this.key = key; - return this; - } - - public String getFullHex() { - return fullHex; - } - - public MoneroTx setFullHex(String fullHex) { - this.fullHex = fullHex; - return this; - } - - public String getPrunedHex() { - return prunedHex; - } - - public MoneroTx setPrunedHex(String prunedHex) { - this.prunedHex = prunedHex; - return this; - } - - public String getPrunableHex() { - return prunableHex; - } - - public MoneroTx setPrunableHex(String prunableHex) { - this.prunableHex = prunableHex; - return this; - } - - public String getPrunableHash() { - return prunableHash; - } - - public MoneroTx setPrunableHash(String prunableHash) { - this.prunableHash = prunableHash; - return this; - } - - public Long getSize() { - return size; - } - - public MoneroTx setSize(Long size) { - this.size = size; - return this; - } - - public Long getWeight() { - return weight; - } - - public MoneroTx setWeight(Long weight) { - this.weight = weight; - return this; - } - - @JsonManagedReference - public List getVins() { - return vins; - } - - public MoneroTx setVins(List vins) { - this.vins = vins; - return this; - } - - @JsonManagedReference - public List getVouts() { - return vouts; - } - - public MoneroTx setVouts(List vouts) { - this.vouts = vouts; - return this; - } - - public List getOutputIndices() { - return outputIndices; - } - - public MoneroTx setOutputIndices(List outputIndices) { - this.outputIndices = outputIndices; - return this; - } - - public String getMetadata() { - return metadata; - } - - public MoneroTx setMetadata(String metadata) { - this.metadata = metadata; - return this; - } - - public int[] getExtra() { - return extra; - } - - public MoneroTx setExtra(int[] extra) { - this.extra = extra; - return this; - } - - public Object getRctSignatures() { - return rctSignatures; - } - - public MoneroTx setRctSignatures(Object rctSignatures) { - this.rctSignatures = rctSignatures; - return this; - } - - public Object getRctSigPrunable() { - return rctSigPrunable; - } - - public MoneroTx setRctSigPrunable(Object rctSigPrunable) { - this.rctSigPrunable = rctSigPrunable; - return this; - } - - @JsonProperty("isKeptByBlock") - public Boolean isKeptByBlock() { - return isKeptByBlock; - } - - public MoneroTx setIsKeptByBlock(Boolean isKeptByBlock) { - this.isKeptByBlock = isKeptByBlock; - return this; - } - - @JsonProperty("isFailed") - public Boolean isFailed() { - return isFailed; - } - - public MoneroTx setIsFailed(Boolean isFailed) { - this.isFailed = isFailed; - return this; - } - - public Long getLastFailedHeight() { - return lastFailedHeight; - } - - public MoneroTx setLastFailedHeight(Long lastFailedHeight) { - this.lastFailedHeight = lastFailedHeight; - return this; - } - - public String getLastFailedId() { - return lastFailedId; - } - - public MoneroTx setLastFailedId(String lastFailedId) { - this.lastFailedId = lastFailedId; - return this; - } - - public Long getMaxUsedBlockHeight() { - return maxUsedBlockHeight; - } - - public MoneroTx setMaxUsedBlockHeight(Long maxUsedBlockHeight) { - this.maxUsedBlockHeight = maxUsedBlockHeight; - return this; - } - - public String getMaxUsedBlockId() { - return maxUsedBlockId; - } - - public MoneroTx setMaxUsedBlockId(String maxUsedBlockId) { - this.maxUsedBlockId = maxUsedBlockId; - return this; - } - - public List getSignatures() { - return signatures; - } - - public MoneroTx setSignatures(List signatures) { - this.signatures = signatures; - return this; - } - - public MoneroTx merge(MoneroTx tx) { - if (this == tx) return this; - - // merge blocks if they're different which comes back to merging txs - if (block != tx.getBlock()) { - if (block == null) { - block = new MoneroBlock(); - block.setTxs(this); - block.setHeight(tx.getHeight()); - } - if (tx.getBlock() == null) { - tx.setBlock(new MoneroBlock()); - tx.getBlock().setTxs(tx); - tx.getBlock().setHeight(getHeight()); - } - block.merge(tx.getBlock()); - return this; - } - - // otherwise merge tx fields - this.setId(MoneroUtils.reconcile(this.getId(), tx.getId())); - this.setVersion(MoneroUtils.reconcile(this.getVersion(), tx.getVersion())); - this.setPaymentId(MoneroUtils.reconcile(this.getPaymentId(), tx.getPaymentId())); - this.setFee(MoneroUtils.reconcile(this.getFee(), tx.getFee())); - this.setMixin(MoneroUtils.reconcile(this.getMixin(), tx.getMixin())); - this.setIsConfirmed(MoneroUtils.reconcile(this.isConfirmed(), tx.isConfirmed(), null, true, null)); - this.setDoNotRelay(MoneroUtils.reconcile(this.getDoNotRelay(), tx.getDoNotRelay(), null, false, null)); // tx can become relayed - this.setIsRelayed(MoneroUtils.reconcile(this.isRelayed(), tx.isRelayed(), null, true, null)); // tx can become relayed - this.setIsDoubleSpendSeen(MoneroUtils.reconcile(this.isDoubleSpendSeen(), tx.isDoubleSpendSeen())); - this.setKey(MoneroUtils.reconcile(this.getKey(), tx.getKey())); - this.setFullHex(MoneroUtils.reconcile(this.getFullHex(), tx.getFullHex())); - this.setPrunedHex(MoneroUtils.reconcile(this.getPrunedHex(), tx.getPrunedHex())); - this.setPrunableHex(MoneroUtils.reconcile(this.getPrunableHex(), tx.getPrunableHex())); - this.setPrunableHash(MoneroUtils.reconcile(this.getPrunableHash(), tx.getPrunableHash())); - this.setSize(MoneroUtils.reconcile(this.getSize(), tx.getSize())); - this.setWeight(MoneroUtils.reconcile(this.getWeight(), tx.getWeight())); - this.setOutputIndices(MoneroUtils.reconcile(this.getOutputIndices(), tx.getOutputIndices())); - this.setMetadata(MoneroUtils.reconcile(this.getMetadata(), tx.getMetadata())); - this.setExtra(MoneroUtils.reconcileIntArrays(this.getExtra(), tx.getExtra())); - this.setRctSignatures(MoneroUtils.reconcile(this.getRctSignatures(), tx.getRctSignatures())); - this.setRctSigPrunable(MoneroUtils.reconcile(this.getRctSigPrunable(), tx.getRctSigPrunable())); - this.setIsKeptByBlock(MoneroUtils.reconcile(this.isKeptByBlock(), tx.isKeptByBlock())); - this.setIsFailed(MoneroUtils.reconcile(this.isFailed(), tx.isFailed())); - this.setLastFailedHeight(MoneroUtils.reconcile(this.getLastFailedHeight(), tx.getLastFailedHeight())); - this.setLastFailedId(MoneroUtils.reconcile(this.getLastFailedId(), tx.getLastFailedId())); - this.setMaxUsedBlockHeight(MoneroUtils.reconcile(this.getMaxUsedBlockHeight(), tx.getMaxUsedBlockHeight())); - this.setMaxUsedBlockId(MoneroUtils.reconcile(this.getMaxUsedBlockId(), tx.getMaxUsedBlockId())); - this.setSignatures(MoneroUtils.reconcile(this.getSignatures(), tx.getSignatures())); - this.setUnlockTime(MoneroUtils.reconcile(this.getUnlockTime(), tx.getUnlockTime())); - this.setNumConfirmations(MoneroUtils.reconcile(this.getNumConfirmations(), tx.getNumConfirmations(), null, null, true)); // num confirmations can increase - - // merge vins - if (tx.getVins() != null) { - for (MoneroOutput merger : tx.getVins()) { - boolean merged = false; - merger.setTx(this); - if (this.getVins() == null) this.setVins(new ArrayList()); - for (MoneroOutput mergee : this.getVins()) { - if (mergee.getKeyImage().getHex().equals(merger.getKeyImage().getHex())) { - mergee.merge(merger); - merged = true; - break; - } - } - if (!merged) this.getVins().add(merger); - } - } - - // merge vouts - if (tx.getVouts() != null) { - for (MoneroOutput vout : tx.getVouts()) vout.setTx(this); - if (this.getVouts() == null) this.setVouts(tx.getVouts()); - else { - - // validate output indices if present - int numIndices = 0; - for (MoneroOutput vout : this.getVouts()) if (vout.getIndex() != null) numIndices++; - for (MoneroOutput vout : tx.getVouts()) if (vout.getIndex() != null) numIndices++; - assertTrue("Some vouts have an output index and some do not", numIndices == 0 || this.getVouts().size() + tx.getVouts().size() == numIndices); - - // merge by output indices if present - if (numIndices > 0) { - for (MoneroOutput merger : tx.getVouts()) { - boolean merged = false; - merger.setTx(this); - if (this.getVouts() == null) this.setVouts(new ArrayList()); - for (MoneroOutput mergee : this.getVouts()) { - if (mergee.getIndex().equals(merger.getIndex())) { - mergee.merge(merger); - merged = true; - break; - } - } - if (!merged) this.getVouts().add(merger); - } - } else { - - // determine if key images present - int numKeyImages = 0; - for (MoneroOutput vout : this.getVouts()) { - if (vout.getKeyImage() != null) { - assertNotNull(vout.getKeyImage().getHex()); - numKeyImages++; - } - } - for (MoneroOutput vout : tx.getVouts()) { - if (vout.getKeyImage() != null) { - assertNotNull(vout.getKeyImage().getHex()); - numKeyImages++; - } - } - assertTrue("Some vouts have a key image and some do not", numKeyImages == 0 || this.getVouts().size() + tx.getVouts().size() == numKeyImages); - - // merge by key images if present - if (numKeyImages > 0) { - for (MoneroOutput merger : tx.getVouts()) { - boolean merged = false; - merger.setTx(this); - if (this.getVouts() == null) this.setVouts(new ArrayList()); - for (MoneroOutput mergee : this.getVouts()) { - if (mergee.getKeyImage().getHex().equals(merger.getKeyImage().getHex())) { - mergee.merge(merger); - merged = true; - break; - } - } - if (!merged) this.getVouts().add(merger); - } - } - - // otherwise merge by position - else { - assertEquals(this.getVouts().size(), tx.getVouts().size()); - for (int i = 0; i < tx.getVouts().size(); i++) { - this.getVouts().get(i).merge(tx.getVouts().get(i)); - } - } - } - } - } - - // handle unrelayed -> relayed -> confirmed - if (this.isConfirmed()) { - this.setInTxPool(false); - this.setReceivedTimestamp(null); - this.setLastRelayedTimestamp(null); - } else { - this.setInTxPool(MoneroUtils.reconcile(this.inTxPool(), tx.inTxPool(), null, true, null)); // unrelayed -> tx pool - this.setReceivedTimestamp(MoneroUtils.reconcile(this.getReceivedTimestamp(), tx.getReceivedTimestamp(), null, null, false)); // take earliest receive time - this.setLastRelayedTimestamp(MoneroUtils.reconcile(this.getLastRelayedTimestamp(), tx.getLastRelayedTimestamp(), null, null, true)); // take latest relay time - } - - return this; // for chaining - } - - public String toString() { - return toString(0); - } - - public String toString(int indent) { - StringBuilder sb = new StringBuilder(); - sb.append(GenUtils.getIndent(indent) + "=== TX ===\n"); - sb.append(MoneroUtils.kvLine("Tx ID: ", getId(), indent)); - sb.append(MoneroUtils.kvLine("Height", getHeight(), indent)); - sb.append(MoneroUtils.kvLine("Version", getVersion(), indent)); - sb.append(MoneroUtils.kvLine("Is miner tx", isMinerTx(), indent)); - sb.append(MoneroUtils.kvLine("Payment ID", getPaymentId(), indent)); - sb.append(MoneroUtils.kvLine("Fee", getFee(), indent)); - sb.append(MoneroUtils.kvLine("Mixin", getMixin(), indent)); - sb.append(MoneroUtils.kvLine("Do not relay", getDoNotRelay(), indent)); - sb.append(MoneroUtils.kvLine("Is relayed", isRelayed(), indent)); - sb.append(MoneroUtils.kvLine("Is confirmed", isConfirmed(), indent)); - sb.append(MoneroUtils.kvLine("In tx pool", inTxPool(), indent)); - sb.append(MoneroUtils.kvLine("Num confirmations", getNumConfirmations(), indent)); - sb.append(MoneroUtils.kvLine("Unlock time", getUnlockTime(), indent)); - sb.append(MoneroUtils.kvLine("Last relayed time", getLastRelayedTimestamp(), indent)); - sb.append(MoneroUtils.kvLine("Received time", getReceivedTimestamp(), indent)); - sb.append(MoneroUtils.kvLine("Is double spend", isDoubleSpendSeen(), indent)); - sb.append(MoneroUtils.kvLine("Key", getKey(), indent)); - sb.append(MoneroUtils.kvLine("Full hex", getFullHex(), indent)); - sb.append(MoneroUtils.kvLine("Pruned hex", getPrunedHex(), indent)); - sb.append(MoneroUtils.kvLine("Prunable hex", getPrunableHex(), indent)); - sb.append(MoneroUtils.kvLine("Prunable hash", getPrunableHash(), indent)); - sb.append(MoneroUtils.kvLine("Size", getSize(), indent)); - sb.append(MoneroUtils.kvLine("Weight", getWeight(), indent)); - sb.append(MoneroUtils.kvLine("Output indices", getOutputIndices(), indent)); - sb.append(MoneroUtils.kvLine("Metadata", getMetadata(), indent)); - sb.append(MoneroUtils.kvLine("Extra", Arrays.toString(getExtra()), indent)); - sb.append(MoneroUtils.kvLine("RCT signatures", getRctSignatures(), indent)); - sb.append(MoneroUtils.kvLine("RCT sig prunable", getRctSigPrunable(), indent)); - sb.append(MoneroUtils.kvLine("Kept by block", isKeptByBlock(), indent)); - sb.append(MoneroUtils.kvLine("Is failed", isFailed(), indent)); - sb.append(MoneroUtils.kvLine("Last failed height", getLastFailedHeight(), indent)); - sb.append(MoneroUtils.kvLine("Last failed id", getLastFailedId(), indent)); - sb.append(MoneroUtils.kvLine("Max used block height", getMaxUsedBlockHeight(), indent)); - sb.append(MoneroUtils.kvLine("Max used block id", getMaxUsedBlockId(), indent)); - sb.append(MoneroUtils.kvLine("Signatures", getSignatures(), indent)); - if (getVins() != null) { - sb.append(MoneroUtils.kvLine("Vins", "", indent)); - for (int i = 0; i < getVins().size(); i++) { - sb.append(MoneroUtils.kvLine(i + 1, "", indent + 1)); - sb.append(getVins().get(i).toString(indent + 2)); - sb.append('\n'); - } - } - if (getVouts() != null) { - sb.append(MoneroUtils.kvLine("Vouts", "", indent)); - for (int i = 0; i < getVouts().size(); i++) { - sb.append(MoneroUtils.kvLine(i + 1, "", indent + 1)); - sb.append(getVouts().get(i).toString(indent + 2)); - sb.append('\n'); - } - } - String str = sb.toString(); - return str.substring(0, str.length() - 1); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((doNotRelay == null) ? 0 : doNotRelay.hashCode()); - result = prime * result + Arrays.hashCode(extra); - result = prime * result + ((fee == null) ? 0 : fee.hashCode()); - result = prime * result + ((fullHex == null) ? 0 : fullHex.hashCode()); - result = prime * result + ((id == null) ? 0 : id.hashCode()); - result = prime * result + ((inTxPool == null) ? 0 : inTxPool.hashCode()); - result = prime * result + ((isMinerTx == null) ? 0 : isMinerTx.hashCode()); - result = prime * result + ((isConfirmed == null) ? 0 : isConfirmed.hashCode()); - result = prime * result + ((isDoubleSpendSeen == null) ? 0 : isDoubleSpendSeen.hashCode()); - result = prime * result + ((isFailed == null) ? 0 : isFailed.hashCode()); - result = prime * result + ((isKeptByBlock == null) ? 0 : isKeptByBlock.hashCode()); - result = prime * result + ((isRelayed == null) ? 0 : isRelayed.hashCode()); - result = prime * result + ((key == null) ? 0 : key.hashCode()); - result = prime * result + ((lastFailedHeight == null) ? 0 : lastFailedHeight.hashCode()); - result = prime * result + ((lastFailedId == null) ? 0 : lastFailedId.hashCode()); - result = prime * result + ((lastRelayedTimestamp == null) ? 0 : lastRelayedTimestamp.hashCode()); - result = prime * result + ((maxUsedBlockHeight == null) ? 0 : maxUsedBlockHeight.hashCode()); - result = prime * result + ((maxUsedBlockId == null) ? 0 : maxUsedBlockId.hashCode()); - result = prime * result + ((metadata == null) ? 0 : metadata.hashCode()); - result = prime * result + ((mixin == null) ? 0 : mixin.hashCode()); - result = prime * result + ((numConfirmations == null) ? 0 : numConfirmations.hashCode()); - result = prime * result + ((outputIndices == null) ? 0 : outputIndices.hashCode()); - result = prime * result + ((paymentId == null) ? 0 : paymentId.hashCode()); - result = prime * result + ((prunableHash == null) ? 0 : prunableHash.hashCode()); - result = prime * result + ((prunableHex == null) ? 0 : prunableHex.hashCode()); - result = prime * result + ((prunedHex == null) ? 0 : prunedHex.hashCode()); - result = prime * result + ((rctSigPrunable == null) ? 0 : rctSigPrunable.hashCode()); - result = prime * result + ((rctSignatures == null) ? 0 : rctSignatures.hashCode()); - result = prime * result + ((receivedTimestamp == null) ? 0 : receivedTimestamp.hashCode()); - result = prime * result + ((signatures == null) ? 0 : signatures.hashCode()); - result = prime * result + ((size == null) ? 0 : size.hashCode()); - result = prime * result + ((unlockTime == null) ? 0 : unlockTime.hashCode()); - result = prime * result + ((version == null) ? 0 : version.hashCode()); - result = prime * result + ((vins == null) ? 0 : vins.hashCode()); - result = prime * result + ((vouts == null) ? 0 : vouts.hashCode()); - result = prime * result + ((weight == null) ? 0 : weight.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - MoneroTx other = (MoneroTx) obj; - if (doNotRelay == null) { - if (other.doNotRelay != null) return false; - } else if (!doNotRelay.equals(other.doNotRelay)) return false; - if (!Arrays.equals(extra, other.extra)) return false; - if (fee == null) { - if (other.fee != null) return false; - } else if (!fee.equals(other.fee)) return false; - if (fullHex == null) { - if (other.fullHex != null) return false; - } else if (!fullHex.equals(other.fullHex)) return false; - if (id == null) { - if (other.id != null) return false; - } else if (!id.equals(other.id)) return false; - if (inTxPool == null) { - if (other.inTxPool != null) return false; - } else if (!inTxPool.equals(other.inTxPool)) return false; - if (isMinerTx == null) { - if (other.isMinerTx != null) return false; - } else if (!isMinerTx.equals(other.isMinerTx)) return false; - if (isConfirmed == null) { - if (other.isConfirmed != null) return false; - } else if (!isConfirmed.equals(other.isConfirmed)) return false; - if (isDoubleSpendSeen == null) { - if (other.isDoubleSpendSeen != null) return false; - } else if (!isDoubleSpendSeen.equals(other.isDoubleSpendSeen)) return false; - if (isFailed == null) { - if (other.isFailed != null) return false; - } else if (!isFailed.equals(other.isFailed)) return false; - if (isKeptByBlock == null) { - if (other.isKeptByBlock != null) return false; - } else if (!isKeptByBlock.equals(other.isKeptByBlock)) return false; - if (isRelayed == null) { - if (other.isRelayed != null) return false; - } else if (!isRelayed.equals(other.isRelayed)) return false; - if (key == null) { - if (other.key != null) return false; - } else if (!key.equals(other.key)) return false; - if (lastFailedHeight == null) { - if (other.lastFailedHeight != null) return false; - } else if (!lastFailedHeight.equals(other.lastFailedHeight)) return false; - if (lastFailedId == null) { - if (other.lastFailedId != null) return false; - } else if (!lastFailedId.equals(other.lastFailedId)) return false; - if (lastRelayedTimestamp == null) { - if (other.lastRelayedTimestamp != null) return false; - } else if (!lastRelayedTimestamp.equals(other.lastRelayedTimestamp)) return false; - if (maxUsedBlockHeight == null) { - if (other.maxUsedBlockHeight != null) return false; - } else if (!maxUsedBlockHeight.equals(other.maxUsedBlockHeight)) return false; - if (maxUsedBlockId == null) { - if (other.maxUsedBlockId != null) return false; - } else if (!maxUsedBlockId.equals(other.maxUsedBlockId)) return false; - if (metadata == null) { - if (other.metadata != null) return false; - } else if (!metadata.equals(other.metadata)) return false; - if (mixin == null) { - if (other.mixin != null) return false; - } else if (!mixin.equals(other.mixin)) return false; - if (numConfirmations == null) { - if (other.numConfirmations != null) return false; - } else if (!numConfirmations.equals(other.numConfirmations)) return false; - if (outputIndices == null) { - if (other.outputIndices != null) return false; - } else if (!outputIndices.equals(other.outputIndices)) return false; - if (paymentId == null) { - if (other.paymentId != null) return false; - } else if (!paymentId.equals(other.paymentId)) return false; - if (prunableHash == null) { - if (other.prunableHash != null) return false; - } else if (!prunableHash.equals(other.prunableHash)) return false; - if (prunableHex == null) { - if (other.prunableHex != null) return false; - } else if (!prunableHex.equals(other.prunableHex)) return false; - if (prunedHex == null) { - if (other.prunedHex != null) return false; - } else if (!prunedHex.equals(other.prunedHex)) return false; - if (rctSigPrunable == null) { - if (other.rctSigPrunable != null) return false; - } else if (!rctSigPrunable.equals(other.rctSigPrunable)) return false; - if (rctSignatures == null) { - if (other.rctSignatures != null) return false; - } else if (!rctSignatures.equals(other.rctSignatures)) return false; - if (receivedTimestamp == null) { - if (other.receivedTimestamp != null) return false; - } else if (!receivedTimestamp.equals(other.receivedTimestamp)) return false; - if (signatures == null) { - if (other.signatures != null) return false; - } else if (!signatures.equals(other.signatures)) return false; - if (size == null) { - if (other.size != null) return false; - } else if (!size.equals(other.size)) return false; - if (unlockTime == null) { - if (other.unlockTime != null) return false; - } else if (!unlockTime.equals(other.unlockTime)) return false; - if (version == null) { - if (other.version != null) return false; - } else if (!version.equals(other.version)) return false; - if (vins == null) { - if (other.vins != null) return false; - } else if (!vins.equals(other.vins)) return false; - if (vouts == null) { - if (other.vouts != null) return false; - } else if (!vouts.equals(other.vouts)) return false; - if (weight == null) { - if (other.weight != null) return false; - } else if (!weight.equals(other.weight)) return false; - return true; - } -} diff --git a/core/src/main/java/monero/daemon/model/MoneroTxBacklogEntry.java b/core/src/main/java/monero/daemon/model/MoneroTxBacklogEntry.java deleted file mode 100644 index 853c730c429..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroTxBacklogEntry.java +++ /dev/null @@ -1,8 +0,0 @@ -package monero.daemon.model; - -/** - * TODO. - */ -public class MoneroTxBacklogEntry { - -} diff --git a/core/src/main/java/monero/daemon/model/MoneroTxPoolStats.java b/core/src/main/java/monero/daemon/model/MoneroTxPoolStats.java deleted file mode 100644 index 3f133857f93..00000000000 --- a/core/src/main/java/monero/daemon/model/MoneroTxPoolStats.java +++ /dev/null @@ -1,125 +0,0 @@ -package monero.daemon.model; - -/** - * Models transaction pool statistics. - */ -public class MoneroTxPoolStats { - - private Integer numTxs; - private Integer numNotRelayed; - private Integer numFailing; - private Integer numDoubleSpends; - private Integer num10m; - private Long feeTotal; - private Long bytesMax; - private Long bytesMed; - private Long bytesMin; - private Long bytesTotal; - private Object histo; - private Long histo98pc; - private Long oldestTimestamp; - - public Integer getNumTxs() { - return numTxs; - } - - public void setNumTxs(Integer numTxs) { - this.numTxs = numTxs; - } - - public Integer getNumNotRelayed() { - return numNotRelayed; - } - - public void setNumNotRelayed(Integer numNotRelayed) { - this.numNotRelayed = numNotRelayed; - } - - public Integer getNumFailing() { - return numFailing; - } - - public void setNumFailing(Integer numFailing) { - this.numFailing = numFailing; - } - - public Integer getNumDoubleSpends() { - return numDoubleSpends; - } - - public void setNumDoubleSpends(Integer numDoubleSpends) { - this.numDoubleSpends = numDoubleSpends; - } - - public Integer getNum10m() { - return num10m; - } - - public void setNum10m(Integer num10m) { - this.num10m = num10m; - } - - public Long getFeeTotal() { - return feeTotal; - } - - public void setFeeTotal(Long feeTotal) { - this.feeTotal = feeTotal; - } - - public Long getBytesMax() { - return bytesMax; - } - - public void setBytesMax(Long bytesMax) { - this.bytesMax = bytesMax; - } - - public Long getBytesMed() { - return bytesMed; - } - - public void setBytesMed(Long bytesMed) { - this.bytesMed = bytesMed; - } - - public Long getBytesMin() { - return bytesMin; - } - - public void setBytesMin(Long bytesMin) { - this.bytesMin = bytesMin; - } - - public Long getBytesTotal() { - return bytesTotal; - } - - public void setBytesTotal(Long bytesTotal) { - this.bytesTotal = bytesTotal; - } - - public Object getHisto() { - return histo; - } - - public void setHisto(Object histo) { - this.histo = histo; - } - - public Long getHisto98pc() { - return histo98pc; - } - - public void setHisto98pc(Long histo98pc) { - this.histo98pc = histo98pc; - } - - public Long getOldestTimestamp() { - return oldestTimestamp; - } - - public void setOldestTimestamp(Long oldestTimestamp) { - this.oldestTimestamp = oldestTimestamp; - } -} diff --git a/core/src/main/java/monero/utils/MoneroCppUtils.java b/core/src/main/java/monero/utils/MoneroCppUtils.java deleted file mode 100644 index 90ff4a7cc53..00000000000 --- a/core/src/main/java/monero/utils/MoneroCppUtils.java +++ /dev/null @@ -1,82 +0,0 @@ -package monero.utils; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.core.type.TypeReference; - -import common.utils.JsonUtils; -import monero.rpc.MoneroRpcConnection; - -/** - * Collection of utilties bridged from Monero Core C++ to Java. - */ -public class MoneroCppUtils { - - static { - System.loadLibrary("monero-java"); - } - - public static byte[] mapToBinary(Map map) { - return jsonToBinaryJni(JsonUtils.serialize(map)); - } - - public static Map binaryToMap(byte[] bin) { - return JsonUtils.deserialize(binaryToJsonJni(bin), new TypeReference>(){}); - } - - @SuppressWarnings("unchecked") - public static Map binaryBlocksToMap(byte[] binBlocks) { - - // convert binary blocks to json then to map - Map map = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, binaryBlocksToJsonJni(binBlocks), new TypeReference>(){}); - - // parse blocks to maps - List> blockMaps = new ArrayList>(); - for (String blockStr : (List) map.get("blocks")) { - blockMaps.add(JsonUtils.deserialize(MoneroRpcConnection.MAPPER, blockStr, new TypeReference>(){})); - } - map.put("blocks", blockMaps); // overwrite block strings - - // parse txs to maps, one array of txs per block - List>> allTxs = new ArrayList>>(); - List rpcAllTxs = (List) map.get("txs"); - for (Object rpcTxs : rpcAllTxs) { - if ("".equals(rpcTxs)) { - allTxs.add(new ArrayList>()); - } else { - List> txs = new ArrayList>(); - allTxs.add(txs); - for (String rpcTx : (List) rpcTxs) { - txs.add(JsonUtils.deserialize(MoneroRpcConnection.MAPPER, rpcTx.replaceFirst(",", "{") + "}", new TypeReference>(){})); // modify tx string to proper json and parse // TODO: more efficient way than this json manipulation? - } - } - } - map.put("txs", allTxs); // overwrite tx strings - - // return map containing blocks and txs as maps - return map; - } - - public static void initLogging(String path, int level, boolean console) { - initLoggingJni(path, console); - setLogLevelJni(level); - } - - public static void setLogLevel(int level) { - setLogLevelJni(level); - } - - // ------------------------------- NATIVE METHODS --------------------------- - - private native static byte[] jsonToBinaryJni(String json); - - private native static String binaryToJsonJni(byte[] bin); - - private native static String binaryBlocksToJsonJni(byte[] binBlocks); - - private native static void initLoggingJni(String path, boolean console); - - private native static void setLogLevelJni(int level); -} diff --git a/core/src/main/java/monero/wallet/MoneroWallet.java b/core/src/main/java/monero/wallet/MoneroWallet.java deleted file mode 100644 index 02b69fbe845..00000000000 --- a/core/src/main/java/monero/wallet/MoneroWallet.java +++ /dev/null @@ -1,1161 +0,0 @@ -/** - * Copyright (c) 2017-2019 woodser - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package monero.wallet; - -import java.math.BigInteger; -import java.util.Collection; -import java.util.List; - -import monero.daemon.model.MoneroKeyImage; -import monero.wallet.model.MoneroAccount; -import monero.wallet.model.MoneroAccountTag; -import monero.wallet.model.MoneroAddressBookEntry; -import monero.wallet.model.MoneroCheckReserve; -import monero.wallet.model.MoneroCheckTx; -import monero.wallet.model.MoneroIncomingTransfer; -import monero.wallet.model.MoneroIntegratedAddress; -import monero.wallet.model.MoneroKeyImageImportResult; -import monero.wallet.model.MoneroMultisigInfo; -import monero.wallet.model.MoneroMultisigInitResult; -import monero.wallet.model.MoneroMultisigSignResult; -import monero.wallet.model.MoneroOutgoingTransfer; -import monero.wallet.model.MoneroOutputQuery; -import monero.wallet.model.MoneroOutputWallet; -import monero.wallet.model.MoneroSendPriority; -import monero.wallet.model.MoneroSendRequest; -import monero.wallet.model.MoneroSubaddress; -import monero.wallet.model.MoneroSyncListener; -import monero.wallet.model.MoneroSyncResult; -import monero.wallet.model.MoneroTransfer; -import monero.wallet.model.MoneroTransferQuery; -import monero.wallet.model.MoneroTxQuery; -import monero.wallet.model.MoneroTxSet; -import monero.wallet.model.MoneroTxWallet; - -/** - * Monero wallet interface. - */ -public interface MoneroWallet { - - public static final String DEFAULT_LANGUAGE = "English"; - - /** - * Get the wallet's path. - * - * @return the path the wallet can be opened with - */ - public String getPath(); - - /** - * Get the wallet's seed. - * - * @return the wallet's seed - */ - public String getSeed(); - - /** - * Get the wallet's mnemonic phrase derived from the seed. - * - * @return the wallet's mnemonic phrase - */ - public String getMnemonic(); - - /** - * Get a list of available languages for the wallet's mnemonic phrase. - * - * @return the available languages for the wallet's mnemonic phrase - */ - public List getLanguages(); - - /** - * Get the wallet's private view key. - * - * @return the wallet's private view key - */ - public String getPrivateViewKey(); - - /** - * Get the wallet's private spend key. - * - * @return the wallet's private spend key - */ - public String getPrivateSpendKey(); - - /** - * Get the wallet's primary address. - * - * @return the wallet's primary address - */ - public String getPrimaryAddress(); - - /** - * Get the address of a specific subaddress. - * - * @param accountIdx specifies the account index of the address's subaddress - * @param subaddressIdx specifies the subaddress index within the account - * @return the receive address of the specified subaddress - */ - public String getAddress(int accountIdx, int subaddressIdx); - - /** - * Get the account and subaddress index of the given address. - * - * @param address is the address to get the account and subaddress index from - * @return the account and subaddress indices - */ - public MoneroSubaddress getAddressIndex(String address); - - /** - * Get an integrated address based on this wallet's primary address and a - * randomly generated payment ID. Generates a random payment ID if none is - * given. - * - * @return the integrated address - */ - public MoneroIntegratedAddress getIntegratedAddress(); - - /** - * Get an integrated address based on this wallet's primary address and the - * given payment ID. Generates a random payment ID if none is given. - * - * @param paymentId is the payment ID to generate an integrated address from (randomly generated if null) - * @return the integrated address - */ - public MoneroIntegratedAddress getIntegratedAddress(String paymentId); - - /** - * Decode an integrated address to get its standard address and payment id. - * - * @param integratedAddress is an integrated address to decode - * @return the decoded integrated address including standard address and payment id - */ - public MoneroIntegratedAddress decodeIntegratedAddress(String integratedAddress); - - /** - * Get the height of the last block processed by the wallet (its index + 1). - * - * @return the height of the last block processed by the wallet - */ - public long getHeight(); - - /** - * Get the blockchain's height. - * - * @return the blockchain's height - */ - public long getDaemonHeight(); - - /** - * Synchronize the wallet with the daemon as a one-time synchronous process. - * - * @return the sync result - */ - public MoneroSyncResult sync(); - - /** - * Synchronize the wallet with the daemon as a one-time synchronous process. - * - * @param listener is invoked as sync progress is made - * @return the sync result - */ - public MoneroSyncResult sync(MoneroSyncListener listener); - - /** - * Synchronize the wallet with the daemon as a one-time synchronous process. - * - * @param startHeight is the start height to sync from (defaults to the last synced block) - * @return the sync result - */ - public MoneroSyncResult sync(Long startHeight); - - /** - * Synchronize the wallet with the daemon as a one-time synchronous process. - * - * @param startHeight is the start height to sync from (defaults to the last synced block) - * @param listener is invoked as sync progress is made - * @return the sync result - */ - public MoneroSyncResult sync(Long startHeight, MoneroSyncListener listener); - - /** - * Start an asynchronous thread to continuously synchronize the wallet with the daemon. - */ - public void startSyncing(); - - /** - * Stop the asynchronous thread to continuously synchronize the wallet with the daemon. - */ - public void stopSyncing(); - - /** - * Rescan the blockchain for spent outputs. - * - * Note: this can only be called with a trusted daemon. - * - * Example use case: peer multisig hex is import when connected to an untrusted daemon, - * so the wallet will not rescan spent outputs. Then the wallet connects to a trusted - * daemon. This method should be manually invoked to rescan outputs. - */ - public void rescanSpent(); - - /** - * Rescan the blockchain from scratch, losing any information which cannot be recovered from - * the blockchain itself. - * - * WARNING: This method discards local wallet data like destination addresses, tx secret keys, - * tx notes, etc. - */ - public void rescanBlockchain(); - - /** - * Get the wallet's balance. - * - * @return the wallet's balance - */ - public BigInteger getBalance(); - - /** - * Get an account's balance. - * - * @param accountIdx is the index of the account to get the balance of - * @return the account's balance - */ - public BigInteger getBalance(int accountIdx); - - /** - * Get a subaddress's balance. - * - * @param accountIdx is the index of the subaddress's account to get the balance of - * @param subaddressIdx is the index of the subaddress to get the balance of - * @return the subaddress's balance - */ - public BigInteger getBalance(int accountIdx, int subaddressIdx); - - /** - * Get the wallet's unlocked balance. - * - * @return the wallet's unlocked balance - */ - public BigInteger getUnlockedBalance(); - - /** - * Get an account's unlocked balance. - * - * @param accountIdx is the index of the account to get the unlocked balance of - * @return the account's unlocked balance - */ - public BigInteger getUnlockedBalance(int accountIdx); - - /** - * Get a subaddress's unlocked balance. - * - * @param accountIdx is the index of the subaddress's account to get the unlocked balance of - * @param subaddressIdx is the index of the subaddress to get the unlocked balance of - * @return the subaddress's balance - */ - public BigInteger getUnlockedBalance(int accountIdx, int subaddressIdx); - - /** - * Get all accounts. - * - * @return all accounts - */ - public List getAccounts(); - - /** - * Get all accounts. - * - * @param includeSubaddresses specifies if subaddresses should be included - * @return all accounts - */ - public List getAccounts(boolean includeSubaddresses); - - /** - * Get accounts with a given tag. - * - * @param tag is the tag for filtering accounts, all accounts if null - * @return all accounts with the given tag - */ - public List getAccounts(String tag); - - /** - * Get accounts with a given tag. - * - * @param includeSubaddresses specifies if subaddresses should be included - * @param tag is the tag for filtering accounts, all accounts if null - * @return all accounts with the given tag - */ - public List getAccounts(boolean includeSubaddresses, String tag); - - /** - * Get an account without subaddress information. - * - * @param accountIdx specifies the account to get - * @return the retrieved account - */ - public MoneroAccount getAccount(int accountIdx); - - /** - * Get an account. - * - * @param accountIdx specifies the account to get - * @param includeSubaddresses specifies if subaddresses should be included - * @return the retrieved account - */ - public MoneroAccount getAccount(int accountIdx, boolean includeSubaddresses); - - /** - * Create a new account. - * - * @return the created account - */ - public MoneroAccount createAccount(); - - /** - * Create a new account with a label for the first subaddress. - * - * @param label specifies the label for account's first subaddress (optional) - * @return the created account - */ - public MoneroAccount createAccount(String label); - - /** - * Get all subaddresses in an account. - * - * @param accountIdx specifies the account to get subaddresses within - * @return the retrieved subaddresses - */ - public List getSubaddresses(int accountIdx); - - /** - * Get subaddresses in an account. - * - * @param accountIdx specifies the account to get subaddresses within - * @param subaddressIndices are specific subaddresses to get (optional) - * @return the retrieved subaddresses - */ - public List getSubaddresses(int accountIdx, List subaddressIndices); - - /** - * Get a subaddress. - * - * @param accountIdx specifies the index of the subaddress's account - * @param subaddressIdx specifies index of the subaddress within the account - * @return the retrieved subaddress - */ - public MoneroSubaddress getSubaddress(int accountIdx, int subaddressIdx); - - /** - * Create a subaddress within an account and without a label. - * - * @param accountIdx specifies the index of the account to create the subaddress within - * @return the created subaddress - */ - public MoneroSubaddress createSubaddress(int accountIdx); - - /** - * Create a subaddress within an account. - * - * @param accountIdx specifies the index of the account to create the subaddress within - * @param label specifies the the label for the subaddress (optional) - * @return the created subaddress - */ - public MoneroSubaddress createSubaddress(int accountIdx, String label); - - /** - * Get a wallet transaction by id. - * - * @param txId is an id of a transaction to get - * @return the identified transactions - */ - public MoneroTxWallet getTx(String txId); - - /** - * Get all wallet transactions. Wallet transactions contain one or more - * transfers that are either incoming or outgoing to the wallet. - * - * @return all wallet transactions - */ - public List getTxs(); - - /** - * Get wallet transactions by id. - * - * @param txIds are ids of transactions to get - * @return the identified transactions - */ - public List getTxs(String... txIds); - - /** - * Get wallet transactions by id. - * - * @param txIds are ids of transactions to get - * @return the identified transactions - */ - public List getTxs(List txIds); - - /** - * Get wallet transactions. Wallet transactions contain one or more - * transfers that are either incoming or outgoing to the wallet. - * - * Query results can be filtered by passing a transaction query. - * Transactions must meet every criteria defined in the query in order to - * be returned. All filtering is optional and no filtering is applied when - * not defined. - * - * @param query specifies attributes of transactions to get - * @return wallet transactions per the query - */ - public List getTxs(MoneroTxQuery query); - - /** - * Get all incoming and outgoing transfers to and from this wallet. An - * outgoing transfer represents a total amount sent from one or more - * subaddresses within an account to individual destination addresses, each - * with their own amount. An incoming transfer represents a total amount - * received into a subaddress within an account. Transfers belong to - * transactions which are stored on the blockchain. - * - * @return all wallet transfers - */ - public List getTransfers(); - - /** - * Get incoming and outgoing transfers to and from an account. An outgoing - * transfer represents a total amount sent from one or more subaddresses - * within an account to individual destination addresses, each with their - * own amount. An incoming transfer represents a total amount received into - * a subaddress within an account. Transfers belong to transactions which - * are stored on the blockchain. - * - * @param accountIdx is the index of the account to get transfers from - * @return transfers to/from the account - */ - public List getTransfers(int accountIdx); - - /** - * Get incoming and outgoing transfers to and from a subaddress. An outgoing - * transfer represents a total amount sent from one or more subaddresses - * within an account to individual destination addresses, each with their - * own amount. An incoming transfer represents a total amount received into - * a subaddress within an account. Transfers belong to transactions which - * are stored on the blockchain. - * - * @param accountIdx is the index of the account to get transfers from - * @param subaddressIdx is the index of the subaddress to get transfers from - * @return transfers to/from the subaddress - */ - public List getTransfers(int accountIdx, int subaddressIdx); - - /** - * Get incoming and outgoing transfers to and from this wallet. An outgoing - * transfer represents a total amount sent from one or more subaddresses - * within an account to individual destination addresses, each with their - * own amount. An incoming transfer represents a total amount received into - * a subaddress within an account. Transfers belong to transactions which - * are stored on the blockchain. - * - * Query results can be filtered by passing in a MoneroTransferQuery. - * Transfers must meet every criteria defined in the query in order to be - * returned. All filtering is optional and no filtering is applied when not - * defined. - * - * @param query specifies attributes of transfers to get - * @return wallet transfers per the query - */ - public List getTransfers(MoneroTransferQuery query); - - /** - * Get all of the wallet's incoming transfers. - * - * @return the wallet's incoming transfers - */ - public List getIncomingTransfers(); - - /** - * Get the wallet's incoming transfers according to the given query. - * - * @param query specifies which incoming transfers to get - * @return the wallet's incoming transfers according to the given query - */ - public List getIncomingTransfers(MoneroTransferQuery query); - - /** - * Get all of the wallet's outgoing transfers. - * - * @return the wallet's outgoing transfers - */ - public List getOutgoingTransfers(); - - /** - * Get the wallet's outgoing transfers according to the given query. - * - * @param query specifies which outgoing transfers to get - * @return the wallet's outgoing transfers according to the given query - */ - public List getOutgoingTransfers(MoneroTransferQuery query); - - /** - * Get outputs created from previous transactions that belong to the wallet - * (i.e. that the wallet can spend one time). Outputs are part of - * transactions which are stored in blocks on the blockchain. - * - * @return List are all wallet outputs - */ - public List getOutputs(); - - /** - * Get outputs created from previous transactions that belong to the wallet - * (i.e. that the wallet can spend one time). Outputs are part of - * transactions which are stored in blocks on the blockchain. - * - * Results can be configured by passing a MoneroOutputQuery. Outputs must - * meet every criteria defined in the query in order to be returned. All - * filtering is optional and no filtering is applied when not defined. - * - * @param query specifies attributes of outputs to get - * @return List are wallet outputs per the query - */ - public List getOutputs(MoneroOutputQuery query); - - /** - * Export all outputs in hex format. - * - * @return all outputs in hex format, null if no outputs - */ - public String getOutputsHex(); - - /** - * Import outputs in hex format. - * - * @param outputsHex are outputs in hex format - * @return the number of outputs imported - */ - public int importOutputsHex(String outputsHex); - - /** - * Get all signed key images. - * - * @return the wallet's signed key images - */ - public List getKeyImages(); - - /** - * Import signed key images and verify their spent status. - * - * @param keyImages are key images to import and verify (requires hex and signature) - * @return results of the import - */ - public MoneroKeyImageImportResult importKeyImages(List keyImages); - - /** - * Get new key images from the last imported outputs. - * - * @return the key images from the last imported outputs - */ - public List getNewKeyImagesFromLastImport(); - - /** - * Create a transaction to transfer funds from this wallet according to the - * given request. The transaction may be relayed later. - * - * @param request configures the transaction to create - * @return a tx set for the requested transaction if possible - */ - public MoneroTxSet createTx(MoneroSendRequest request); - - /** - * Create a transaction to transfers funds from this wallet to a destination address. - * The transaction may be relayed later. - * - * @param accountIndex is the index of the account to withdraw funds from - * @param address is the destination address to send funds to - * @param amount is the amount being sent - * @return a tx set for the requested transaction if possible - */ - public MoneroTxSet createTx(int accountIndex, String address, BigInteger amount); - - /** - * Create a transaction to transfers funds from this wallet to a destination address. - * The transaction may be relayed later. - * - * @param accountIndex is the index of the account to withdraw funds from - * @param address is the destination address to send funds to - * @param amount is the amount being sent - * @param priority is the send priority (default normal) - * @return a tx set for the requested transaction if possible - */ - public MoneroTxSet createTx(int accountIndex, String address, BigInteger amount, MoneroSendPriority priority); - - /** - * Create one or more transactions to transfer funds from this wallet - * according to the given request. The transactions may later be relayed. - * - * @param request configures the transactions to create - * @return a tx set for the requested transactions if possible - */ - public MoneroTxSet createTxs(MoneroSendRequest request); - - /** - * Relay a previously created transaction. - * - * @param txMetadata is transaction metadata previously created without relaying - * @return the id of the relayed tx - */ - public String relayTx(String txMetadata); - - /** - * Relay a previously created transaction. - * - * @param tx is the transaction to relay - * @return the id of the relayed tx - */ - public String relayTx(MoneroTxWallet tx); - - /** - * Relay previously created transactions. - * - * @param txMetadatas are transaction metadata previously created without relaying - * @return the ids of the relayed txs - */ - public List relayTxs(Collection txMetadatas); - - /** - * Relay previously created transactions. - * - * @param txs are the transactions to relay - * @return the ids of the relayed txs - */ - public List relayTxs(List txs); - - /** - * Create and relay a transaction to transfer funds from this wallet - * according to the given request. - * - * @param request configures the transaction - * @return a tx set with the requested transaction if possible - */ - public MoneroTxSet send(MoneroSendRequest request); - - /** - * Create and relay a transaction to transfers funds from this wallet to - * a destination address. - * - * @param accountIndex is the index of the account to withdraw funds from - * @param address is the destination address to send funds to - * @param amount is the amount being sent - * @return a tx set with the requested transaction if possible - */ - public MoneroTxSet send(int accountIndex, String address, BigInteger amount); - - /** - * Create and relay a transaction to transfers funds from this wallet to - * a destination address. - * - * @param accountIndex is the index of the account to withdraw funds from - * @param address is the destination address to send funds to - * @param amount is the amount being sent - * @param priority is the send priority (default normal) - * @return a tx set with the requested transaction if possible - */ - public MoneroTxSet send(int accountIndex, String address, BigInteger amount, MoneroSendPriority priority); - - /** - * Create and relay one or more transactions to transfer funds from this - * wallet according to the given request. - * - * @param request configures the transactions - * @return a tx set with the requested transaction if possible - */ - public MoneroTxSet sendSplit(MoneroSendRequest request); - - /** - * Create and relay one or more transactions which transfer funds from this - * wallet to a destination address. - * - * @param accountIndex is the index of the account to withdraw funds from - * @param address is the destination address to send funds to - * @param amount is the amount being sent - * @return a tx set with the requested transaction if possible - */ - public MoneroTxSet sendSplit(int accountIndex, String address, BigInteger amount); - - /** - * Create and relay one or more transactions to transfer funds from this - * wallet to a destination address with a priority. - * - * @param accountIndex is the index of the account to withdraw funds from - * @param address is the destination address to send funds to - * @param amount is the amount being sent - * @param priority is the send priority (default normal) - * @return a tx set with the requested transaction if possible - */ - public MoneroTxSet sendSplit(int accountIndex, String address, BigInteger amount, MoneroSendPriority priority); - - /** - * Sweep an output with a given key image. - * - * @param request configures the sweep transaction - * @return a tx set with the requested transaction if possible - */ - public MoneroTxSet sweepOutput(MoneroSendRequest request); - - /** - * Sweep an output with a given key image. - * - * @param address is the destination address to send to - * @param keyImage is the key image hex of the output to sweep - * @return a tx set with the requested transaction if possible - */ - public MoneroTxSet sweepOutput(String address, String keyImage); - - /** - * Sweep an output with a given key image. - * - * @param address is the destination address to send to - * @param keyImage is the key image hex of the output to sweep - * @param priority is the transaction priority (optional) - * @return a tx set with the requested transaction if possible - */ - public MoneroTxSet sweepOutput(String address, String keyImage, MoneroSendPriority priority); - - /** - * Sweep a subaddress's unlocked funds to an address. - * - * @param accountIdx is the index of the account - * @param subaddressIdx is the index of the subaddress - * @param address is the address to sweep the subaddress's funds to - * @return a tx set with the requested transactions if possible - */ - public MoneroTxSet sweepSubaddress(int accountIdx, int subaddressIdx, String address); - - /** - * Sweep an acount's unlocked funds to an address. - * - * @param accountIdx is the index of the account - * @param address is the address to sweep the account's funds to - * @return a tx set with the requested transactions if possible - */ - public MoneroTxSet sweepAccount(int accountIdx, String address); - - /** - * Sweep the wallet's unlocked funds to an address. - * - * @param address is the address to sweep the wallet's funds to - * @return the tx sets with the transactions which sweep the wallet - */ - public List sweepWallet(String address); - - /** - * Sweep all unlocked funds according to the given request. - * - * @param request is the sweep configuration - * @return the tx sets with the requested transactions - */ - public List sweepUnlocked(MoneroSendRequest request); - - /** - * Sweep all unmixable dust outputs back to the wallet to make them easier to spend and mix. - * - * NOTE: Dust only exists pre RCT, so this method will throw "no dust to sweep" on new wallets. - * - * @return a tx set with the requested transactions if possible - */ - public MoneroTxSet sweepDust(); - - /** - * Sweep all unmixable dust outputs back to the wallet to make them easier to spend and mix. - * - * @param doNotRelay specifies if the resulting transaction should not be relayed (defaults to false i.e. relayed) - * @return a tx set with the requested transactions if possible - */ - public MoneroTxSet sweepDust(boolean doNotRelay); - - /** - * Sign a message. - * - * @param message is the message to sign - * @return the signature - */ - public String sign(String message); - - /** - * Verify a signature on a message. - * - * @param message is the signed message - * @param address is the signing address - * @param signature is the signature - * @return true if the signature is good, false otherwise - */ - public boolean verify(String message, String address, String signature); - - /** - * Get a transaction's secret key from its id. - * - * @param txId is the transaction's id - * @return is the transaction's secret key - */ - public String getTxKey(String txId); - - /** - * Check a transaction in the blockchain with its secret key. - * - * @param txId specifies the transaction to check - * @param txKey is the transaction's secret key - * @param address is the destination public address of the transaction - * @return the result of the check - */ - public MoneroCheckTx checkTxKey(String txId, String txKey, String address); - - /** - * Get a transaction signature to prove it. - * - * @param txId specifies the transaction to prove - * @param address is the destination public address of the transaction - * @return the transaction signature - */ - public String getTxProof(String txId, String address); - - /** - * Get a transaction signature to prove it. - * - * @param txId specifies the transaction to prove - * @param address is the destination public address of the transaction - * @param message is a message to include with the signature to further authenticate the proof (optional) - * @return the transaction signature - */ - public String getTxProof(String txId, String address, String message); - - /** - * Prove a transaction by checking its signature. - * - * @param txId specifies the transaction to prove - * @param address is the destination public address of the transaction - * @param message is a message included with the signature to further authenticate the proof (optional) - * @param signature is the transaction signature to confirm - * @return the result of the check - */ - public MoneroCheckTx checkTxProof(String txId, String address, String message, String signature); - - /** - * Generate a signature to prove a spend. Unlike proving a transaction, it does not require the destination public address. - * - * @param txId specifies the transaction to prove - * @return the transaction signature - */ - public String getSpendProof(String txId); - - /** - * Generate a signature to prove a spend. Unlike proving a transaction, it does not require the destination public address. - * - * @param txId specifies the transaction to prove - * @param message is a message to include with the signature to further authenticate the proof (optional) - * @return the transaction signature - */ - public String getSpendProof(String txId, String message); - - /** - * Prove a spend using a signature. Unlike proving a transaction, it does not require the destination public address. - * - * @param txId specifies the transaction to prove - * @param message is a message included with the signature to further authenticate the proof (optional) - * @param signature is the transaction signature to confirm - * @return true if the signature is good, false otherwise - */ - public boolean checkSpendProof(String txId, String message, String signature); - - /** - * Generate a signature to prove the entire balance of the wallet. - * - * @param message is a message included with the signature to further authenticate the proof (optional) - * @return the reserve proof signature - */ - public String getReserveProofWallet(String message); - - /** - * Generate a signature to prove an available amount in an account. - * - * @param accountIdx specifies the account to prove ownership of the amount - * @param amount is the minimum amount to prove as available in the account - * @param message is a message to include with the signature to further authenticate the proof (optional) - * @return the reserve proof signature - */ - public String getReserveProofAccount(int accountIdx, BigInteger amount, String message); - - /** - * Proves a wallet has a disposable reserve using a signature. - * - * @param address is the public wallet address - * @param message is a message included with the signature to further authenticate the proof (optional) - * @param signature is the reserve proof signature to check - * @return the result of checking the signature proof - */ - public MoneroCheckReserve checkReserveProof(String address, String message, String signature); - - /** - * Get a transaction note. - * - * @param txId specifies the transaction to get the note of - * @return the tx note - */ - public String getTxNote(String txId); - - /** - * Get notes for multiple transactions. - * - * @param txIds identify the transactions to get notes for - * @return notes for the transactions - */ - public List getTxNotes(Collection txIds); - - /** - * Set a note for a specific transaction. - * - * @param txId specifies the transaction - * @param note specifies the note - */ - public void setTxNote(String txId, String note); - - /** - * Set notes for multiple transactions. - * - * @param txIds specify the transactions to set notes for - * @param notes are the notes to set for the transactions - */ - public void setTxNotes(Collection txIds, Collection notes); - - /** - * Get all address book entries. - * - * @return the address book entries - */ - public List getAddressBookEntries(); - - /** - * Get address book entries. - * - * @param entryIndices are indices of the entries to get - * @return the address book entries - */ - public List getAddressBookEntries(Collection entryIndices); - - /** - * Add an address book entry. - * - * @param address is the entry address - * @param description is the entry description (optional) - * @return the index of the added entry - */ - public int addAddressBookEntry(String address, String description); - - /** - * Add an address book entry. - * - * @param address is the entry address - * @param description is the entry description (optional) - * @param paymentId is the entry paymet id (optional) - * @return the index of the added entry - */ - public int addAddressBookEntry(String address, String description, String paymentId); - - /** - * Delete an address book entry. - * - * @param entryIdx is the index of the entry to delete - */ - public void deleteAddressBookEntry(int entryIdx); - - /** - * Tag accounts. - * - * @param tag is the tag to apply to the specified accounts - * @param accountIndices are the indices of the accounts to tag - */ - public void tagAccounts(String tag, Collection accountIndices); - - /** - * Untag acconts. - * - * @param accountIndices are the indices of the accounts to untag - */ - public void untagAccounts(Collection accountIndices); - - /** - * Return all account tags. - * - * @return the wallet's account tags - */ - public List getAccountTags(); - - /** - * Sets a human-readable description for a tag. - * - * @param tag is the tag to set a description for - * @param label is the label to set for the tag - */ - public void setAccountTagLabel(String tag, String label); - - /** - * Creates a payment URI from a send configuration. - * - * @param request specifies configuration for a potential tx - * @return the payment uri - */ - public String createPaymentUri(MoneroSendRequest request); - - /** - * Parses a payment URI to a send request. - * - * @param uri is the payment uri to parse - * @return the send configuration parsed from the uri - */ - public MoneroSendRequest parsePaymentUri(String uri); - - /** - * Get an attribute. - * - * @param key is the attribute to get the value of - * @return the attribute's value - */ - public String getAttribute(String key); - - /** - * Set an arbitrary attribute. - * - * @param key is the attribute key - * @param val is the attribute value - */ - public void setAttribute(String key, String val); - - /** - * Start mining. - * - * @param numThreads is the number of threads created for mining (optional) - * @param backgroundMining specifies if mining should occur in the background (optional) - * @param ignoreBattery specifies if the battery should be ignored for mining (optional) - */ - public void startMining(Long numThreads, Boolean backgroundMining, Boolean ignoreBattery); - - /** - * Stop mining. - */ - public void stopMining(); - - /** - * Indicates if importing multisig data is needed for returning a correct balance. - * - * @return true if importing multisig data is needed for returning a correct balance, false otherwise - */ - public boolean isMultisigImportNeeded(); - - /** - * Indicates if this wallet is a multisig wallet. - * - * @return true if this is a multisig wallet, false otherwise - */ - public boolean isMultisig(); - - /** - * Get multisig info about this wallet. - * - * @return multisig info about this wallet - */ - public MoneroMultisigInfo getMultisigInfo(); - - /** - * Get multisig info as hex to share with participants to begin creating a - * multisig wallet. - * - * @return this wallet's multisig hex to share with participants - */ - public String prepareMultisig(); - - /** - * Make this wallet multisig by importing multisig hex from participants. - * - * @param multisigHexes are multisig hex from each participant - * @param threshold is the number of signatures needed to sign transfers - * @param password is the wallet password - * @return the result which has the multisig's address xor this wallet's multisig hex to share with participants iff not N/N - */ - public MoneroMultisigInitResult makeMultisig(List multisigHexes, int threshold, String password); - - /** - * Exchange multisig hex with participants in a M/N multisig wallet. - * - * This process must be repeated with participants exactly N-M times. - * - * @param multisigHexes are multisig hex from each participant - * @param password is the wallet's password // TODO monero core: redundant? wallet is created with password - * @return the result which has the multisig's address xor this wallet's multisig hex to share with participants iff not done - */ - public MoneroMultisigInitResult exchangeMultisigKeys(List multisigHexes, String password); - - /** - * Export this wallet's multisig info as hex for other participants. - * - * @return this wallet's multisig info as hex for other participants - */ - public String getMultisigHex(); - - /** - * Import multisig info as hex from other participants. - * - * @param multisigHexes are multisig hex from each participant - * @return the number of outputs signed with the given multisig hex - */ - public int importMultisigHex(List multisigHexes); - - /** - * Sign previously created multisig transactions as represented by hex. - * - * @param multisigTxHex is the hex shared among the multisig transactions when they were created - * @return the result of signing the multisig transactions - */ - public MoneroMultisigSignResult signMultisigTxHex(String multisigTxHex); - - /** - * Submit signed multisig transactions as represented by a hex string. - * - * @param signedMultisigTxHex is the signed multisig hex returned from signMultisigTxs() - * @return the resulting transaction ids - */ - public List submitMultisigTxHex(String signedMultisigTxHex); - - /** - * Save the wallet at its current path. - */ - public void save(); - - /** - * Close the wallet (does not save). - */ - public void close(); - - /** - * Optionally save then close the wallet. - * - * @param save specifies if the wallet should be saved before being closed (default false) - */ - public void close(boolean save); -} \ No newline at end of file diff --git a/core/src/main/java/monero/wallet/MoneroWalletDefault.java b/core/src/main/java/monero/wallet/MoneroWalletDefault.java deleted file mode 100644 index 14eae753222..00000000000 --- a/core/src/main/java/monero/wallet/MoneroWalletDefault.java +++ /dev/null @@ -1,386 +0,0 @@ -/** - * Copyright (c) 2017-2019 woodser - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package monero.wallet; - -import static org.junit.Assert.assertEquals; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import monero.utils.MoneroException; -import monero.wallet.model.MoneroAccount; -import monero.wallet.model.MoneroAddressBookEntry; -import monero.wallet.model.MoneroIncomingTransfer; -import monero.wallet.model.MoneroIntegratedAddress; -import monero.wallet.model.MoneroOutgoingTransfer; -import monero.wallet.model.MoneroOutputWallet; -import monero.wallet.model.MoneroSendPriority; -import monero.wallet.model.MoneroSendRequest; -import monero.wallet.model.MoneroSubaddress; -import monero.wallet.model.MoneroSyncListener; -import monero.wallet.model.MoneroSyncResult; -import monero.wallet.model.MoneroTransfer; -import monero.wallet.model.MoneroTransferQuery; -import monero.wallet.model.MoneroTxQuery; -import monero.wallet.model.MoneroTxSet; -import monero.wallet.model.MoneroTxWallet; - -/** - * Default implementation of a Monero Wallet. - */ -public abstract class MoneroWalletDefault implements MoneroWallet { - - @Override - public String getPrimaryAddress() { - return getAddress(0, 0); - } - - @Override - public MoneroIntegratedAddress getIntegratedAddress() { - return getIntegratedAddress(null); - } - - @Override - public MoneroSyncResult sync() { - return sync(null, null); - } - - @Override - public MoneroSyncResult sync(MoneroSyncListener listener) { - return sync(null, listener); - } - - @Override - public MoneroSyncResult sync(Long startHeight) { - return sync(startHeight, null); - } - - @Override - public MoneroSyncResult sync(Long startHeight, MoneroSyncListener listener) { - return sync(startHeight, listener); - } - - @Override - public List getAccounts() { - return getAccounts(false, null); - } - - @Override - public List getAccounts(boolean includeSubaddresses) { - return getAccounts(includeSubaddresses, null); - } - - @Override - public List getAccounts(String tag) { - return getAccounts(false, tag); - } - - @Override - public MoneroAccount getAccount(int accountIdx) { - return getAccount(accountIdx, false); - } - - @Override - public MoneroAccount createAccount() { - return createAccount(null); - } - - @Override - public List getSubaddresses(int accountIdx) { - return getSubaddresses(accountIdx, null); - } - - @Override - public MoneroSubaddress getSubaddress(int accountIdx, int subaddressIdx) { - List subaddresses = getSubaddresses(accountIdx, Arrays.asList(subaddressIdx)); - if (subaddresses.isEmpty()) throw new MoneroException("Subaddress at index " + subaddressIdx + " is not initialized"); - assertEquals("Only 1 subaddress should be returned", 1, subaddresses.size()); - return subaddresses.get(0); - } - - @Override - public MoneroSubaddress createSubaddress(int accountIdx) { - return createSubaddress(accountIdx, null); - } - - @Override - public MoneroTxWallet getTx(String txId) { - return getTxs(txId).get(0); - } - - @Override - public List getTxs() { - return getTxs(new MoneroTxQuery()); - } - - public List getTxs(String... txIds) { - return getTxs(new MoneroTxQuery().setTxIds(txIds)); - } - - public List getTxs(List txIds) { - return getTxs(new MoneroTxQuery().setTxIds(txIds)); - } - - @Override - public List getTransfers() { - return getTransfers(null); - } - - @Override - public List getTransfers(int accountIdx) { - MoneroTransferQuery query = new MoneroTransferQuery().setAccountIndex(accountIdx); - return getTransfers(query); - } - - @Override - public List getTransfers(int accountIdx, int subaddressIdx) { - MoneroTransferQuery query = new MoneroTransferQuery().setAccountIndex(accountIdx).setSubaddressIndex(subaddressIdx); - return getTransfers(query); - } - - - @Override - public List getIncomingTransfers() { - return getIncomingTransfers(null); - } - - @Override - public List getIncomingTransfers(MoneroTransferQuery query) { - - // copy query and set direction - MoneroTransferQuery _query; - if (query == null) _query = new MoneroTransferQuery(); - else { - if (Boolean.FALSE.equals(query.isIncoming())) throw new MoneroException("Transfer query contradicts getting incoming transfers"); - _query = query.copy(); - } - _query.setIsIncoming(true); - - // fetch and cast transfers - List inTransfers = new ArrayList(); - for (MoneroTransfer transfer : getTransfers(_query)) { - inTransfers.add((MoneroIncomingTransfer) transfer); - } - return inTransfers; - } - - @Override - public List getOutgoingTransfers() { - return getOutgoingTransfers(null); - } - - @Override - public List getOutgoingTransfers(MoneroTransferQuery query) { - - // copy query and set direction - MoneroTransferQuery _query; - if (query == null) _query = new MoneroTransferQuery(); - else { - if (Boolean.FALSE.equals(query.isOutgoing())) throw new MoneroException("Transfer query contradicts getting outgoing transfers"); - _query = query.copy(); - } - _query.setIsOutgoing(true); - - // fetch and cast transfers - List outTransfers = new ArrayList(); - for (MoneroTransfer transfer : getTransfers(_query)) { - outTransfers.add((MoneroOutgoingTransfer) transfer); - } - return outTransfers; - } - - @Override - public List getOutputs() { - return getOutputs(null); - } - - @Override - public MoneroTxSet createTx(MoneroSendRequest request) { - if (request == null) throw new MoneroException("Send request cannot be null"); - if (Boolean.TRUE.equals(request.getCanSplit())) throw new MoneroException("Cannot request split transactions with createTx() which prevents splitting; use createTxs() instead"); - request = request.copy(); - request.setCanSplit(false); - return createTxs(request); - } - - @Override - public MoneroTxSet createTx(int accountIndex, String address, BigInteger sendAmount) { - return createTx(accountIndex, address, sendAmount, null); - } - - @Override - public MoneroTxSet createTx(int accountIndex, String address, BigInteger sendAmount, MoneroSendPriority priority) { - return createTx(new MoneroSendRequest(accountIndex, address, sendAmount, priority)); - } - - @Override - public MoneroTxSet createTxs(MoneroSendRequest request) { - if (request == null) throw new MoneroException("Send request cannot be null"); - - // modify request to not relay - Boolean requestedDoNotRelay = request.getDoNotRelay(); - request.setDoNotRelay(true); - - // invoke common method which doesn't relay - MoneroTxSet txSet = sendSplit(request); - - // restore doNotRelay of request and txs - request.setDoNotRelay(requestedDoNotRelay); - if (txSet.getTxs() != null) { - for (MoneroTxWallet tx : txSet.getTxs()) tx.setDoNotRelay(requestedDoNotRelay); - } - - // return results - return txSet; - } - - @Override - public String relayTx(String txMetadata) { - return relayTxs(Arrays.asList(txMetadata)).get(0); - } - - @Override - public String relayTx(MoneroTxWallet tx) { - return relayTx(tx.getMetadata()); - } - - // TODO: this method is not tested - @Override - public List relayTxs(List txs) { - List txHexes = new ArrayList(); - for (MoneroTxWallet tx : txs) txHexes.add(tx.getMetadata()); - return relayTxs(txHexes); - } - - @Override - public MoneroTxSet send(MoneroSendRequest request) { - if (request == null) throw new MoneroException("Send request cannot be null"); - if (Boolean.TRUE.equals(request.getCanSplit())) throw new MoneroException("Cannot request split transactions with send() which prevents splitting; use sendSplit() instead"); - request = request.copy(); - request.setCanSplit(false); - return sendSplit(request); - } - - @Override - public MoneroTxSet send(int accountIndex, String address, BigInteger sendAmount) { - return send(accountIndex, address, sendAmount, null); - } - - @Override - public MoneroTxSet send(int accountIndex, String address, BigInteger sendAmount, MoneroSendPriority priority) { - return send(new MoneroSendRequest(accountIndex, address, sendAmount, priority)); - } - - @Override - public MoneroTxSet sendSplit(int accountIndex, String address, BigInteger sendAmount) { - return sendSplit(new MoneroSendRequest(accountIndex, address, sendAmount)); - } - - @Override - public MoneroTxSet sendSplit(int accountIndex, String address, BigInteger sendAmount, MoneroSendPriority priority) { - return sendSplit(new MoneroSendRequest(accountIndex, address, sendAmount, priority)); - } - - @Override - public MoneroTxSet sweepOutput(String address, String keyImage) { - return sweepOutput(address, keyImage, null); - } - - @Override - public MoneroTxSet sweepOutput(String address, String keyImage, MoneroSendPriority priority) { - MoneroSendRequest request = new MoneroSendRequest(address).setPriority(priority); - request.setKeyImage(keyImage); - return sweepOutput(request); - } - - @Override - public MoneroTxSet sweepSubaddress(int accountIdx, int subaddressIdx, String address) { - MoneroSendRequest request = new MoneroSendRequest(address); - request.setAccountIndex(accountIdx); - request.setSubaddressIndices(subaddressIdx); - List txSets = sweepUnlocked(request); - assertEquals("Only one tx set should be created when sweeping from a subaddress", 1, (int) txSets.size()); - return txSets.get(0); - } - - @Override - public MoneroTxSet sweepAccount(int accountIdx, String address) { - MoneroSendRequest request = new MoneroSendRequest(address); - request.setAccountIndex(accountIdx); - List txSets = sweepUnlocked(request); - assertEquals("Only one tx set should be created when sweeping from an account", 1, (int) txSets.size()); - return txSets.get(0); - } - - @Override - public List sweepWallet(String address) { - return sweepUnlocked(new MoneroSendRequest(address)); - } - - @Override - public MoneroTxSet sweepDust() { - return sweepDust(false); - } - - @Override - public String getTxProof(String txId, String address) { - return getTxProof(txId, address, null); - } - - @Override - public String getSpendProof(String txId) { - return getSpendProof(txId, null); - } - - @Override - public String getTxNote(String txId) { - return getTxNotes(Arrays.asList(txId)).get(0); - } - - @Override - public void setTxNote(String txId, String note) { - setTxNotes(Arrays.asList(txId), Arrays.asList(note)); - } - - @Override - public List getAddressBookEntries() { - return getAddressBookEntries(null); - } - - @Override - public int addAddressBookEntry(String address, String description) { - return addAddressBookEntry(address, description, null); - } - - @Override - public boolean isMultisig() { - return getMultisigInfo().isMultisig(); - } - - @Override - public void close() { - close(false); // close without saving - } -} diff --git a/core/src/main/java/monero/wallet/MoneroWalletJni.java b/core/src/main/java/monero/wallet/MoneroWalletJni.java deleted file mode 100644 index 955c74fc72d..00000000000 --- a/core/src/main/java/monero/wallet/MoneroWalletJni.java +++ /dev/null @@ -1,1635 +0,0 @@ -/** - * Copyright (c) 2017-2019 woodser - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package monero.wallet; - -import static org.junit.Assert.assertNull; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.apache.log4j.Logger; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import common.utils.GenUtils; -import common.utils.JsonUtils; -import monero.daemon.model.MoneroBlock; -import monero.daemon.model.MoneroKeyImage; -import monero.daemon.model.MoneroNetworkType; -import monero.daemon.model.MoneroTx; -import monero.rpc.MoneroRpcConnection; -import monero.utils.MoneroException; -import monero.wallet.model.MoneroAccount; -import monero.wallet.model.MoneroAccountTag; -import monero.wallet.model.MoneroAddressBookEntry; -import monero.wallet.model.MoneroCheckReserve; -import monero.wallet.model.MoneroCheckTx; -import monero.wallet.model.MoneroIncomingTransfer; -import monero.wallet.model.MoneroIntegratedAddress; -import monero.wallet.model.MoneroKeyImageImportResult; -import monero.wallet.model.MoneroMultisigInfo; -import monero.wallet.model.MoneroMultisigInitResult; -import monero.wallet.model.MoneroMultisigSignResult; -import monero.wallet.model.MoneroOutputQuery; -import monero.wallet.model.MoneroOutputWallet; -import monero.wallet.model.MoneroSendRequest; -import monero.wallet.model.MoneroSubaddress; -import monero.wallet.model.MoneroSyncListener; -import monero.wallet.model.MoneroSyncResult; -import monero.wallet.model.MoneroTransfer; -import monero.wallet.model.MoneroTransferQuery; -import monero.wallet.model.MoneroTxQuery; -import monero.wallet.model.MoneroTxSet; -import monero.wallet.model.MoneroTxWallet; -import monero.wallet.model.MoneroWalletListener; -import monero.wallet.model.MoneroWalletListenerI; - -/** - * Implements a Monero wallet using JNI to bridge to Monero Core C++. - */ -public class MoneroWalletJni extends MoneroWalletDefault { - - // ----------------------------- PRIVATE SETUP ------------------------------ - - // load Monero Core C++ as a dynamic library - static { - System.loadLibrary("monero-java"); - } - - // logger - private static final Logger LOGGER = Logger.getLogger(MoneroWalletJni.class); - - // instance variables - private long jniWalletHandle; // memory address of the wallet in c++; this variable is read directly by name in c++ - private long jniListenerHandle; // memory address of the wallet listener in c++; this variable is read directly by name in c++ - private WalletJniListener jniListener; // receives notifications from jni c++ - private Set listeners; // externally subscribed wallet listeners - private boolean isClosed; // whether or not wallet is closed - - /** - * Private constructor with a handle to the memory address of the wallet in c++. - * - * @param jniWalletHandle is the memory address of the wallet in c++ - */ - private MoneroWalletJni(long jniWalletHandle) { - this.jniWalletHandle = jniWalletHandle; - this.jniListener = new WalletJniListener(); - this.listeners = new LinkedHashSet(); - this.isClosed = false; - } - - // --------------------- WALLET MANAGEMENT UTILITIES ------------------------ - - /** - * Indicates if a wallet exists at the given path. - * - * @param path is the path to check for a wallet - * @return true if a wallet exists at the given path, false otherwise - */ - public static boolean walletExists(String path) { - return walletExistsJni(path); - } - - /** - * Open an existing wallet. - * - * @param path is the path to the wallet file to open - * @param password is the password of the wallet file to open - * @param networkType is the wallet's network type - * @param daemonConnection is connection configuration to a daemon (default = an unconnected wallet) - * @return the opened wallet - */ - public static MoneroWalletJni openWallet(String path, String password, MoneroNetworkType networkType) { return openWallet(path, password, networkType, (MoneroRpcConnection) null); } - public static MoneroWalletJni openWallet(String path, String password, MoneroNetworkType networkType, String daemonUri) { return openWallet(path, password, networkType, daemonUri == null ? null : new MoneroRpcConnection(daemonUri)); } - public static MoneroWalletJni openWallet(String path, String password, MoneroNetworkType networkType, MoneroRpcConnection daemonConnection) { - if (!walletExistsJni(path)) throw new MoneroException("Wallet does not exist at path: " + path); - if (networkType == null) throw new MoneroException("Must provide a network type"); - long jniWalletHandle = openWalletJni(path, password, networkType.ordinal()); - MoneroWalletJni wallet = new MoneroWalletJni(jniWalletHandle); - if (daemonConnection != null) wallet.setDaemonConnection(daemonConnection); - return wallet; - } - - /** - * Create a new wallet with a randomly generated seed. - * - * @param path is the path to create the wallet - * @param password is the password encrypt the wallet - * @param networkType is the wallet's network type (default = MoneroNetworkType.MAINNET) - * @param daemonConnection is connection configuration to a daemon (default = an unconnected wallet) - * @param language is the wallet and mnemonic's language (default = "English") - * @return the newly created wallet - */ - public static MoneroWalletJni createWalletRandom(String path, String password) { return createWalletRandom(path, password, null, null, null); } - public static MoneroWalletJni createWalletRandom(String path, String password, MoneroNetworkType networkType) { return createWalletRandom(path, password, networkType, null, null); } - public static MoneroWalletJni createWalletRandom(String path, String password, MoneroNetworkType networkType, String daemonUri) { return createWalletRandom(path, password, networkType, daemonUri == null ? null : new MoneroRpcConnection(daemonUri), null); } - public static MoneroWalletJni createWalletRandom(String path, String password, MoneroNetworkType networkType, MoneroRpcConnection daemonConnection) { return createWalletRandom(path, password, networkType, daemonConnection, null); } - public static MoneroWalletJni createWalletRandom(String path, String password, MoneroNetworkType networkType, MoneroRpcConnection daemonConnection, String language) { - if (networkType == null) networkType = MoneroNetworkType.MAINNET; - if (language == null) language = DEFAULT_LANGUAGE; - long jniWalletHandle; - if (daemonConnection == null) jniWalletHandle = createWalletRandomJni(path, password, networkType.ordinal(), null, null, null, language); - else jniWalletHandle = createWalletRandomJni(path, password, networkType.ordinal(), daemonConnection.getUri(), daemonConnection.getUsername(), daemonConnection.getPassword(), language); - return new MoneroWalletJni(jniWalletHandle); - } - - /** - * Create a wallet from an existing mnemonic phrase. - * - * @param path is the path to create the wallet - * @param password is the password encrypt the wallet - * @param networkType is the wallet's network type - * @param mnemonic is the mnemonic of the wallet to construct - * @param daemonConnection is connection configuration to a daemon (default = an unconnected wallet) - * @param restoreHeight is the block height to restore from (default = 0) - */ - public static MoneroWalletJni createWalletFromMnemonic(String path, String password, MoneroNetworkType networkType, String mnemonic) { return createWalletFromMnemonic(path, password, networkType, mnemonic, null, null); } - public static MoneroWalletJni createWalletFromMnemonic(String path, String password, MoneroNetworkType networkType, String mnemonic, MoneroRpcConnection daemonConnection) { return createWalletFromMnemonic(path, password, networkType, mnemonic, daemonConnection, null); } - public static MoneroWalletJni createWalletFromMnemonic(String path, String password, MoneroNetworkType networkType, String mnemonic, MoneroRpcConnection daemonConnection, Long restoreHeight) { - if (networkType == null) throw new MoneroException("Must provide a network type"); - if (restoreHeight == null) restoreHeight = 0l; - long jniWalletHandle = createWalletFromMnemonicJni(path, password, networkType.ordinal(), mnemonic, restoreHeight); - MoneroWalletJni wallet = new MoneroWalletJni(jniWalletHandle); - wallet.setDaemonConnection(daemonConnection); - return wallet; - } - - /** - * Create a wallet from an address, view key, and spend key. - * - * @param path is the path to create the wallet - * @param password is the password encrypt the wallet - * @param networkType is the wallet's network type - * @param address is the address of the wallet to construct - * @param viewKey is the view key of the wallet to construct - * @param spendKey is the spend key of the wallet to construct - * @param daemonConnection is connection configuration to a daemon (default = an unconnected wallet) - * @param restoreHeight is the block height to restore (i.e. scan the chain) from (default = 0) - * @param language is the wallet and mnemonic's language (default = "English") - */ - public static MoneroWalletJni createWalletFromKeys(String path, String password, MoneroNetworkType networkType, String address, String viewKey, String spendKey) { return createWalletFromKeys(path, password, networkType, address, viewKey, spendKey, null, null, null); } - public static MoneroWalletJni createWalletFromKeys(String path, String password, MoneroNetworkType networkType, String address, String viewKey, String spendKey, MoneroRpcConnection daemonConnection, Long restoreHeight) { return createWalletFromKeys(path, password, networkType, address, viewKey, spendKey, daemonConnection, restoreHeight, null); } - public static MoneroWalletJni createWalletFromKeys(String path, String password, MoneroNetworkType networkType, String address, String viewKey, String spendKey, MoneroRpcConnection daemonConnection, Long restoreHeight, String language) { - if (restoreHeight == null) restoreHeight = 0l; - if (networkType == null) throw new MoneroException("Must provide a network type"); - if (language == null) language = DEFAULT_LANGUAGE; - try { - long jniWalletHandle = createWalletFromKeysJni(path, password, networkType.ordinal(), address, viewKey, spendKey, restoreHeight, language); - MoneroWalletJni wallet = new MoneroWalletJni(jniWalletHandle); - wallet.setDaemonConnection(daemonConnection); - return wallet; - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - // ------------ WALLET METHODS SPECIFIC TO JNI IMPLEMENTATION --------------- - - /** - * Set the wallet's daemon connection. - * - * @param uri is the uri of the daemon for the wallet to use - */ - public void setDaemonConnection(String uri) { - setDaemonConnection(uri, null, null); - } - - /** - * Set the wallet's daemon connection. - * - * @param uri is the daemon's URI - * @param username is the username to authenticate with the daemon (optional) - * @param password is the password to authenticate with the daemon (optional) - */ - public void setDaemonConnection(String uri, String username, String password) { - if (uri == null) setDaemonConnection((MoneroRpcConnection) null); - else setDaemonConnection(new MoneroRpcConnection(uri, username, password)); - } - - /** - * Set the wallet's daemon connection - * - * @param daemonConnection manages daemon connection information - */ - public void setDaemonConnection(MoneroRpcConnection daemonConnection) { - assertNotClosed(); - if (daemonConnection == null) setDaemonConnectionJni("", "", ""); - else { - try { - setDaemonConnectionJni(daemonConnection.getUri() == null ? "" : daemonConnection.getUri().toString(), daemonConnection.getUsername(), daemonConnection.getPassword()); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - } - - /** - * Get the wallet's daemon connection. - * - * @return the wallet's daemon connection - */ - public MoneroRpcConnection getDaemonConnection() { - assertNotClosed(); - try { - String[] vals = getDaemonConnectionJni(); - return vals == null ? null : new MoneroRpcConnection(vals[0], vals[1], vals[2]); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - /** - * Indicates if the wallet is connected a daemon. - * - * @return true if the wallet is connected to a daemon, false otherwise - */ - public boolean isConnected() { - assertNotClosed(); - try { - return isConnectedJni(); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - /** - * Get the maximum height of the peers the wallet's daemon is connected to. - * - * @return the maximum height of the peers the wallet's daemon is connected to - */ - public long getDaemonMaxPeerHeight() { - assertNotClosed(); - try { - return getDaemonMaxPeerHeightJni(); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - /** - * Indicates if the wallet's daemon is synced with the network. - * - * @return true if the daemon is synced with the network, false otherwise - */ - public boolean isDaemonSynced() { - assertNotClosed(); - try { - return isDaemonSyncedJni(); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - /** - * Indicates if the wallet is synced with the daemon. - * - * @return true if the wallet is synced with the daemon, false otherwise - */ - public boolean isSynced() { - assertNotClosed(); - try { - return isSyncedJni(); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - /** - * Get the wallet's network type (mainnet, testnet, or stagenet). - * - * @return the wallet's network type - */ - public MoneroNetworkType getNetworkType() { - assertNotClosed(); - return MoneroNetworkType.values()[getNetworkTypeJni()]; - } - - /** - * Get the height of the first block that the wallet scans. - * - * @return the height of the first block that the wallet scans - */ - public long getRestoreHeight() { - assertNotClosed(); - return getRestoreHeightJni(); - } - - /** - * Set the height of the first block that the wallet scans. - * - * @param restoreHeight is the height of the first block that the wallet scans - */ - public void setRestoreHeight(long restoreHeight) { - assertNotClosed(); - setRestoreHeightJni(restoreHeight); - } - - /** - * Get the language of the wallet's mnemonic phrase. - * - * @return the language of the wallet's mnemonic phrase - */ - public String getLanguage() { - assertNotClosed(); - return getLanguageJni(); - } - - /** - * Get the wallet's public view key. - * - * @return the wallet's public view key - */ - public String getPublicViewKey() { - assertNotClosed(); - return getPublicViewKeyJni(); - } - - /** - * Get the wallet's public spend key. - * - * @return the wallet's public spend key - */ - public String getPublicSpendKey() { - assertNotClosed(); - return getPublicSpendKeyJni(); - } - - /** - * Register a listener receive wallet notifications. - * - * @param listener is the listener to receive wallet notifications - */ - public void addListener(MoneroWalletListenerI listener) { - assertNotClosed(); - listeners.add(listener); - jniListener.setIsListening(true); - } - - /** - * Unregister a listener to receive wallet notifications. - * - * @param listener is the listener to unregister - */ - public void removeListener(MoneroWalletListenerI listener) { - assertNotClosed(); - if (!listeners.contains(listener)) throw new MoneroException("Listener is not registered to wallet"); - listeners.remove(listener); - if (listeners.isEmpty()) jniListener.setIsListening(false); - } - - /** - * Get the listeners registered with the wallet. - */ - public Set getListeners() { - assertNotClosed(); - return listeners; - } - - /** - * Move the wallet from its current path to the given path. - * - * @param path is the new wallet's path - * @param password is the new wallet's password - */ - public void moveTo(String path, String password) { - assertNotClosed(); - moveToJni(path, password); - } - - /** - * Indicates if this wallet is closed or not. - * - * @return true if the wallet is closed, false otherwise - */ - public boolean isClosed() { - return isClosed; - } - - // -------------------------- COMMON WALLET METHODS ------------------------- - - @Override - public String getPath() { - assertNotClosed(); - String path = getPathJni(); - return path.isEmpty() ? null : path; - } - - @Override - public String getSeed() { - assertNotClosed(); - throw new RuntimeException("Not implemented"); - } - - @Override - public String getMnemonic() { - assertNotClosed(); - return getMnemonicJni(); - } - - @Override - public List getLanguages() { - assertNotClosed(); - return Arrays.asList(getLanguagesJni()); - } - - @Override - public String getPrivateViewKey() { - assertNotClosed(); - return getPrivateViewKeyJni(); - } - - @Override - public String getPrivateSpendKey() { - assertNotClosed(); - return getPrivateSpendKeyJni(); - } - - @Override - public MoneroIntegratedAddress getIntegratedAddress(String paymentId) { - assertNotClosed(); - try { - String integratedAddressJson = getIntegratedAddressJni("", paymentId); - return JsonUtils.deserialize(MoneroRpcConnection.MAPPER, integratedAddressJson, MoneroIntegratedAddress.class); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public MoneroIntegratedAddress decodeIntegratedAddress(String integratedAddress) { - assertNotClosed(); - try { - String integratedAddressJson = decodeIntegratedAddressJni(integratedAddress); - return JsonUtils.deserialize(MoneroRpcConnection.MAPPER, integratedAddressJson, MoneroIntegratedAddress.class); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public long getHeight() { - assertNotClosed(); - return getHeightJni(); - } - - @Override - public long getDaemonHeight() { - assertNotClosed(); - try { - return getDaemonHeightJni(); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public MoneroSyncResult sync(Long startHeight, MoneroSyncListener listener) { - assertNotClosed(); - if (startHeight == null) startHeight = Math.max(getHeight(), getRestoreHeight()); - - // wrap and register sync listener as wallet listener if given - SyncListenerWrapper syncListenerWrapper = null; - if (listener != null) { - syncListenerWrapper = new SyncListenerWrapper(listener); - addListener(syncListenerWrapper); - } - - // sync wallet and handle exception - try { - Object[] results = syncJni(startHeight); - return new MoneroSyncResult((long) results[0], (boolean) results[1]); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } finally { - if (syncListenerWrapper != null) removeListener(syncListenerWrapper); // unregister sync listener - } - } - - @Override - public void startSyncing() { - assertNotClosed(); - try { - startSyncingJni(); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - public void stopSyncing() { - assertNotClosed(); - try { - stopSyncingJni(); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public void rescanSpent() { - assertNotClosed(); - try { - rescanSpentJni(); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public void rescanBlockchain() { - assertNotClosed(); - try { - rescanBlockchainJni(); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public List getAccounts(boolean includeSubaddresses, String tag) { - assertNotClosed(); - String accountsJson = getAccountsJni(includeSubaddresses, tag); - List accounts = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, accountsJson, AccountsContainer.class).accounts; - for (MoneroAccount account : accounts) sanitizeAccount(account); - return accounts; - } - - @Override - public MoneroAccount getAccount(int accountIdx, boolean includeSubaddresses) { - assertNotClosed(); - String accountJson = getAccountJni(accountIdx, includeSubaddresses); - MoneroAccount account = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, accountJson, MoneroAccount.class); - sanitizeAccount(account); - return account; - } - - @Override - public MoneroAccount createAccount(String label) { - assertNotClosed(); - String accountJson = createAccountJni(label); - MoneroAccount account = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, accountJson, MoneroAccount.class); - sanitizeAccount(account); - return account; - } - - @Override - public List getSubaddresses(int accountIdx, List subaddressIndices) { - assertNotClosed(); - String subaddresses_json = getSubaddressesJni(accountIdx, GenUtils.listToIntArray(subaddressIndices)); - List subaddresses = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, subaddresses_json, SubaddressesContainer.class).subaddresses; - for (MoneroSubaddress subaddress : subaddresses) sanitizeSubaddress(subaddress); - return subaddresses; - } - - @Override - public MoneroSubaddress createSubaddress(int accountIdx, String label) { - assertNotClosed(); - String subaddressJson = createSubaddressJni(accountIdx, label); - MoneroSubaddress subaddress = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, subaddressJson, MoneroSubaddress.class); - sanitizeSubaddress(subaddress); - return subaddress; - } - - @Override - public String getAddress(int accountIdx, int subaddressIdx) { - assertNotClosed(); - return getAddressJni(accountIdx, subaddressIdx); - } - - @Override - public MoneroSubaddress getAddressIndex(String address) { - assertNotClosed(); - try { - String subaddressJson = getAddressIndexJni(address); - MoneroSubaddress subaddress = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, subaddressJson, MoneroSubaddress.class); - return sanitizeSubaddress(subaddress); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public BigInteger getBalance() { - assertNotClosed(); - try { - return new BigInteger(getBalanceWalletJni()); - } catch (MoneroException e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public BigInteger getBalance(int accountIdx) { - assertNotClosed(); - try { - return new BigInteger(getBalanceAccountJni(accountIdx)); - } catch (MoneroException e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public BigInteger getBalance(int accountIdx, int subaddressIdx) { - assertNotClosed(); - try { - return new BigInteger(getBalanceSubaddressJni(accountIdx, subaddressIdx)); - } catch (MoneroException e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public BigInteger getUnlockedBalance() { - assertNotClosed(); - try { - return new BigInteger(getUnlockedBalanceWalletJni()); - } catch (MoneroException e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public BigInteger getUnlockedBalance(int accountIdx) { - assertNotClosed(); - try { - return new BigInteger(getUnlockedBalanceAccountJni(accountIdx)); - } catch (MoneroException e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public BigInteger getUnlockedBalance(int accountIdx, int subaddressIdx) { - assertNotClosed(); - try { - return new BigInteger(getUnlockedBalanceSubaddressJni(accountIdx, subaddressIdx)); - } catch (MoneroException e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public List getTxs(MoneroTxQuery query) { - assertNotClosed(); - - // copy and normalize tx query up to block - query = query == null ? new MoneroTxQuery() : query.copy(); - if (query.getBlock() == null) query.setBlock(new MoneroBlock().setTxs(query)); - - // serialize query from block and fetch txs from jni - String blocksJson; - try { - blocksJson = getTxsJni(JsonUtils.serialize(query.getBlock())); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - - // deserialize blocks - List blocks = deserializeBlocks(blocksJson); - - // collect txs - List txs = new ArrayList(); - for (MoneroBlock block : blocks) { - sanitizeBlock(block); - for (MoneroTx tx : block.getTxs()) { - if (block.getHeight() == null) tx.setBlock(null); // dereference placeholder block for unconfirmed txs - txs.add((MoneroTxWallet) tx); - } - } - - // re-sort txs which is lost over jni serialization - if (query.getTxIds() != null) { - Map txMap = new HashMap(); - for (MoneroTxWallet tx : txs) txMap.put(tx.getId(), tx); - List txsSorted = new ArrayList(); - for (String txId : query.getTxIds()) txsSorted.add(txMap.get(txId)); - txs = txsSorted; - } - LOGGER.debug("getTxs() returning " + txs.size() + " transactions"); - return txs; - } - - @Override - public List getTransfers(MoneroTransferQuery query) { - assertNotClosed(); - - // copy and normalize query up to block - if (query == null) query = new MoneroTransferQuery(); - else { - if (query.getTxQuery() == null) query = query.copy(); - else { - MoneroTxQuery txQuery = query.getTxQuery().copy(); - if (query.getTxQuery().getTransferQuery() == query) query = txQuery.getTransferQuery(); - else { - assertNull("Transfer query's tx query must be circular reference or null", query.getTxQuery().getTransferQuery()); - query = query.copy(); - query.setTxQuery(txQuery); - } - } - } - if (query.getTxQuery() == null) query.setTxQuery(new MoneroTxQuery()); - query.getTxQuery().setTransferQuery(query); - if (query.getTxQuery().getBlock() == null) query.getTxQuery().setBlock(new MoneroBlock().setTxs(query.getTxQuery())); - - // serialize query from block and fetch transfers from jni - String blocksJson; - try { - blocksJson = getTransfersJni(JsonUtils.serialize(query.getTxQuery().getBlock())); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - - // deserialize blocks - List blocks = deserializeBlocks(blocksJson); - - // collect transfers - List transfers = new ArrayList(); - for (MoneroBlock block : blocks) { - sanitizeBlock(block); - for (MoneroTx tx : block.getTxs()) { - if (block.getHeight() == null) tx.setBlock(null); // dereference placeholder block for unconfirmed txs - MoneroTxWallet txWallet = (MoneroTxWallet) tx; - if (txWallet.getOutgoingTransfer() != null) transfers.add(txWallet.getOutgoingTransfer()); - if (txWallet.getIncomingTransfers() != null) { - for (MoneroIncomingTransfer transfer : txWallet.getIncomingTransfers()) transfers.add(transfer); - } - } - } - return transfers; - } - - @Override - public List getOutputs(MoneroOutputQuery query) { - assertNotClosed(); - - // copy and normalize query up to block - if (query == null) query = new MoneroOutputQuery(); - else { - if (query.getTxQuery() == null) query = query.copy(); - else { - MoneroTxQuery txQuery = query.getTxQuery().copy(); - if (query.getTxQuery().getOutputQuery() == query) query = txQuery.getOutputQuery(); - else { - assertNull("Output query's tx query must be circular reference or null", query.getTxQuery().getOutputQuery()); - query = query.copy(); - query.setTxQuery(txQuery); - } - } - } - if (query.getTxQuery() == null) query.setTxQuery(new MoneroTxQuery()); - query.getTxQuery().setOutputQuery(query); - if (query.getTxQuery().getBlock() == null) query.getTxQuery().setBlock(new MoneroBlock().setTxs(query.getTxQuery())); - - // serialize query from block and fetch outputs from jni - String blocksJson = getOutputsJni(JsonUtils.serialize(query.getTxQuery().getBlock())); - - // deserialize blocks - List blocks = deserializeBlocks(blocksJson); - - // collect outputs - List outputs = new ArrayList(); - for (MoneroBlock block : blocks) { - sanitizeBlock(block); - for (MoneroTx tx : block.getTxs()) { - MoneroTxWallet txWallet = (MoneroTxWallet) tx; - outputs.addAll(txWallet.getVoutsWallet()); - } - } - return outputs; - } - - @Override - public String getOutputsHex() { - assertNotClosed(); - String outputsHex = getOutputsHexJni(); - return outputsHex.isEmpty() ? null : outputsHex; - } - - @Override - public int importOutputsHex(String outputsHex) { - assertNotClosed(); - return importOutputsHexJni(outputsHex); - } - - @Override - public List getKeyImages() { - assertNotClosed(); - String keyImagesJson = getKeyImagesJni(); - List keyImages = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, keyImagesJson, KeyImagesContainer.class).keyImages; - return keyImages; - } - - @Override - public MoneroKeyImageImportResult importKeyImages(List keyImages) { - assertNotClosed(); - - // wrap and serialize key images in container for jni - KeyImagesContainer keyImageContainer = new KeyImagesContainer(keyImages); - String importResultJson = importKeyImagesJni(JsonUtils.serialize(keyImageContainer)); - - // deserialize response - return JsonUtils.deserialize(MoneroRpcConnection.MAPPER, importResultJson, MoneroKeyImageImportResult.class); - } - - @Override - public List getNewKeyImagesFromLastImport() { - assertNotClosed(); - throw new RuntimeException("Not implemented"); - } - - @Override - public List relayTxs(Collection txMetadatas) { - assertNotClosed(); - String[] txMetadatasArr = txMetadatas.toArray(new String[txMetadatas.size()]); // convert to array for jni - try { - return Arrays.asList(relayTxsJni(txMetadatasArr)); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public MoneroTxSet sendSplit(MoneroSendRequest request) { - assertNotClosed(); - LOGGER.debug("java sendSplit(request)"); - LOGGER.debug("Send request: " + JsonUtils.serialize(request)); - - // validate request - if (request == null) throw new MoneroException("Send request cannot be null"); - - // submit send request to JNI and get response as json rooted at tx set - String txSetJson; - try { - txSetJson = sendSplitJni(JsonUtils.serialize(request)); - LOGGER.debug("Received sendSplit() response from JNI: " + txSetJson.substring(0, Math.min(5000, txSetJson.length())) + "..."); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - - // deserialize and return tx set - MoneroTxSet txSet = JsonUtils.deserialize(txSetJson, MoneroTxSet.class); - if (txSet.getTxs() == null) LOGGER.debug("Created tx set without txs: " + JsonUtils.serialize(txSet) + " in sendSplit()"); - else LOGGER.debug("Created " + txSet.getTxs().size() + " transaction(s) in last send request"); - return txSet; - } - - @Override - public List sweepUnlocked(MoneroSendRequest request) { - assertNotClosed(); - - // validate request - if (request == null) throw new MoneroException("Send request cannot be null"); - - // submit send request to JNI and get response as json rooted at tx set - String txSetsJson; - try { - txSetsJson = sweepUnlockedJni(JsonUtils.serialize(request)); - LOGGER.debug("Received sweepUnlocked() response from JNI: " + txSetsJson.substring(0, Math.min(5000, txSetsJson.length())) + "..."); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - - // deserialize and return tx sets - return JsonUtils.deserialize(MoneroRpcConnection.MAPPER, txSetsJson, TxSetsContainer.class).txSets; - } - - @Override - public MoneroTxSet sweepOutput(MoneroSendRequest request) { - assertNotClosed(); - try { - String txSetJson = sweepOutputJni(JsonUtils.serialize(request)); - MoneroTxSet txSet = JsonUtils.deserialize(txSetJson, MoneroTxSet.class); - return txSet; - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public MoneroTxSet sweepDust(boolean doNotRelay) { - assertNotClosed(); - String txSetJson; - try { txSetJson = sweepDustJni(doNotRelay); } - catch (Exception e) { throw new MoneroException(e.getMessage()); } - MoneroTxSet txSet = JsonUtils.deserialize(txSetJson, MoneroTxSet.class); - return txSet; - } - - @Override - public MoneroCheckTx checkTxKey(String txId, String txKey, String address) { - assertNotClosed(); - try { - String checkStr = checkTxKeyJni(txId, txKey, address); - return JsonUtils.deserialize(MoneroRpcConnection.MAPPER, checkStr, MoneroCheckTx.class); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public String getTxProof(String txId, String address, String message) { - assertNotClosed(); - try { - return getTxProofJni(txId, address, message); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public MoneroCheckTx checkTxProof(String txId, String address, String message, String signature) { - assertNotClosed(); - try { - String checkStr = checkTxProofJni(txId, address, message, signature); - return JsonUtils.deserialize(MoneroRpcConnection.MAPPER, checkStr, MoneroCheckTx.class); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public String getSpendProof(String txId, String message) { - assertNotClosed(); - try { - return getSpendProofJni(txId, message); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public boolean checkSpendProof(String txId, String message, String signature) { - assertNotClosed(); - try { - return checkSpendProofJni(txId, message, signature); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public String getReserveProofWallet(String message) { - try { - return getReserveProofWalletJni(message); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public String getReserveProofAccount(int accountIdx, BigInteger amount, String message) { - assertNotClosed(); - try { - return getReserveProofAccountJni(accountIdx, amount.toString(), message); - } catch (Exception e) { - throw new MoneroException(e.getMessage(), -1); - } - } - - @Override - public MoneroCheckReserve checkReserveProof(String address, String message, String signature) { - assertNotClosed(); - try { - String checkStr = checkReserveProofJni(address, message, signature); - return JsonUtils.deserialize(MoneroRpcConnection.MAPPER, checkStr, MoneroCheckReserve.class); - } catch (Exception e) { - throw new MoneroException(e.getMessage(), -1); - } - } - - @Override - public String sign(String msg) { - assertNotClosed(); - return signJni(msg); - } - - @Override - public boolean verify(String msg, String address, String signature) { - assertNotClosed(); - return verifyJni(msg, address, signature); - } - - @Override - public String getTxKey(String txId) { - assertNotClosed(); - try { - return getTxKeyJni(txId); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public List getTxNotes(Collection txIds) { - assertNotClosed(); - return Arrays.asList(getTxNotesJni(txIds.toArray(new String[txIds.size()]))); // convert to array for jni - } - - @Override - public void setTxNotes(Collection txIds, Collection notes) { - assertNotClosed(); - setTxNotesJni(txIds.toArray(new String[txIds.size()]), notes.toArray(new String[notes.size()])); - } - - @Override - public List getAddressBookEntries(Collection entryIndices) { - assertNotClosed(); - throw new RuntimeException("Not implemented"); - } - - @Override - public int addAddressBookEntry(String address, String description, String paymentId) { - assertNotClosed(); - throw new RuntimeException("Not implemented"); - } - - @Override - public void deleteAddressBookEntry(int entryIdx) { - assertNotClosed(); - throw new RuntimeException("Not implemented"); - } - - @Override - public void tagAccounts(String tag, Collection accountIndices) { - assertNotClosed(); - throw new RuntimeException("Not implemented"); - } - - @Override - public void untagAccounts(Collection accountIndices) { - assertNotClosed(); - throw new RuntimeException("Not implemented"); - } - - @Override - public List getAccountTags() { - assertNotClosed(); - throw new RuntimeException("Not implemented"); - } - - @Override - public void setAccountTagLabel(String tag, String label) { - assertNotClosed(); - throw new RuntimeException("Not implemented"); - } - - @Override - public String createPaymentUri(MoneroSendRequest request) { - assertNotClosed(); - try { - return createPaymentUriJni(JsonUtils.serialize(request)); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public MoneroSendRequest parsePaymentUri(String uri) { - assertNotClosed(); - try { - String sendRequestJson = parsePaymentUriJni(uri); - return JsonUtils.deserialize(MoneroRpcConnection.MAPPER, sendRequestJson, MoneroSendRequest.class); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public String getAttribute(String key) { - assertNotClosed(); - String value = getAttributeJni(key); - return value.isEmpty() ? null : value; - } - - @Override - public void setAttribute(String key, String val) { - assertNotClosed(); - setAttributeJni(key, val); - } - - @Override - public void startMining(Long numThreads, Boolean backgroundMining, Boolean ignoreBattery) { - assertNotClosed(); - try { - startMiningJni(numThreads == null ? 0l : (long) numThreads, backgroundMining, ignoreBattery); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public void stopMining() { - assertNotClosed(); - try { - stopMiningJni(); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - - @Override - public boolean isMultisigImportNeeded() { - assertNotClosed(); - return isMultisigImportNeededJni(); - } - - @Override - public MoneroMultisigInfo getMultisigInfo() { - try { - String multisigInfoJson = getMultisigInfoJni(); - return JsonUtils.deserialize(multisigInfoJson, MoneroMultisigInfo.class); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public String prepareMultisig() { - return prepareMultisigJni(); - } - - @Override - public MoneroMultisigInitResult makeMultisig(List multisigHexes, int threshold, String password) { - try { - String initMultisigResultJson = makeMultisigJni(multisigHexes.toArray(new String[multisigHexes.size()]), threshold, password); - return JsonUtils.deserialize(initMultisigResultJson, MoneroMultisigInitResult.class); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public MoneroMultisigInitResult exchangeMultisigKeys(List multisigHexes, String password) { - try { - String initMultisigResultJson = exchangeMultisigKeysJni(multisigHexes.toArray(new String[multisigHexes.size()]), password); - return JsonUtils.deserialize(initMultisigResultJson, MoneroMultisigInitResult.class); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public String getMultisigHex() { - try { - return getMultisigHexJni(); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public int importMultisigHex(List multisigHexes) { - try { - return importMultisigHexJni(multisigHexes.toArray(new String[multisigHexes.size()])); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public MoneroMultisigSignResult signMultisigTxHex(String multisigTxHex) { - try { - String signMultisigResultJson = signMultisigTxHexJni(multisigTxHex); - return JsonUtils.deserialize(signMultisigResultJson, MoneroMultisigSignResult.class); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public List submitMultisigTxHex(String signedMultisigTxHex) { - try { - return Arrays.asList(submitMultisigTxHexJni(signedMultisigTxHex)); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - @Override - public void save() { - assertNotClosed(); - saveJni(); - } - - @Override - public void close(boolean save) { - if (isClosed) return; // closing a closed wallet has no effect - isClosed = true; - try { - closeJni(save); - } catch (Exception e) { - throw new MoneroException(e.getMessage()); - } - } - - // ------------------------------ NATIVE METHODS ---------------------------- - - private native static boolean walletExistsJni(String path); - - private native static long openWalletJni(String path, String password, int networkType); - - private native static long createWalletRandomJni(String path, String password, int networkType, String daemonUrl, String daemonUsername, String daemonPassword, String language); - - private native static long createWalletFromMnemonicJni(String path, String password, int networkType, String mnemonic, long restoreHeight); - - private native static long createWalletFromKeysJni(String path, String password, int networkType, String address, String viewKey, String spendKey, long restoreHeight, String language); - - private native long getHeightJni(); - - private native long getRestoreHeightJni(); - - private native void setRestoreHeightJni(long height); - - private native long getDaemonHeightJni(); - - private native long getDaemonMaxPeerHeightJni(); - - private native String[] getDaemonConnectionJni(); // returns [uri, username, password] - - private native void setDaemonConnectionJni(String uri, String username, String password); - - private native boolean isConnectedJni(); - - private native boolean isDaemonSyncedJni(); - - private native boolean isSyncedJni(); - - private native int getNetworkTypeJni(); - - private native String getPathJni(); - - private native String getMnemonicJni(); - - private native String getLanguageJni(); - - private native String[] getLanguagesJni(); - - private native String getPublicViewKeyJni(); - - private native String getPrivateViewKeyJni(); - - private native String getPublicSpendKeyJni(); - - private native String getPrivateSpendKeyJni(); - - private native String getAddressJni(int accountIdx, int subaddressIdx); - - private native String getAddressIndexJni(String address); - - private native String getIntegratedAddressJni(String standardAddress, String paymentId); - - private native String decodeIntegratedAddressJni(String integratedAddress); - - private native long setListenerJni(WalletJniListener listener); - - private native Object[] syncJni(long startHeight); - - private native void startSyncingJni(); - - private native void stopSyncingJni(); - - private native void rescanSpentJni(); - - private native void rescanBlockchainJni(); - - private native String getBalanceWalletJni(); - - private native String getBalanceAccountJni(int accountIdx); - - private native String getBalanceSubaddressJni(int accountIdx, int subaddressIdx); - - private native String getUnlockedBalanceWalletJni(); - - private native String getUnlockedBalanceAccountJni(int accountIdx); - - private native String getUnlockedBalanceSubaddressJni(int accountIdx, int subaddressIdx); - - private native String getAccountsJni(boolean includeSubaddresses, String tag); - - private native String getAccountJni(int accountIdx, boolean includeSubaddresses); - - private native String createAccountJni(String label); - - private native String getSubaddressesJni(int accountIdx, int[] subaddressIndices); - - private native String createSubaddressJni(int accountIdx, String label); - - /** - * Gets txs from the native layer using strings to communicate. - * - * @param txQueryJson is a tx query serialized to a json string - * @return a serialized BlocksContainer to preserve model relationships - */ - private native String getTxsJni(String txQueryJson); - - private native String getTransfersJni(String transferQueryJson); - - private native String getOutputsJni(String outputQueryJson); - - private native String getOutputsHexJni(); - - private native int importOutputsHexJni(String outputsHex); - - private native String getKeyImagesJni(); - - private native String importKeyImagesJni(String keyImagesJson); - - private native String[] relayTxsJni(String[] txMetadatas); - - private native String sendSplitJni(String sendRequestJson); - - private native String sweepUnlockedJni(String sendRequestJson); - - private native String sweepOutputJni(String sendRequestJson); - - private native String sweepDustJni(boolean doNotRelay); - - private native String[] getTxNotesJni(String[] txIds); - - private native void setTxNotesJni(String[] txIds, String[] notes); - - private native String signJni(String msg); - - private native boolean verifyJni(String msg, String address, String signature); - - private native String getTxKeyJni(String txId); - - private native String checkTxKeyJni(String txId, String txKey, String address); - - private native String getTxProofJni(String txId, String address, String message); - - private native String checkTxProofJni(String txId, String address, String message, String signature); - - private native String getSpendProofJni(String txId, String message); - - private native boolean checkSpendProofJni(String txId, String message, String signature); - - private native String getReserveProofWalletJni(String message); - - private native String getReserveProofAccountJni(int accountIdx, String amount, String message); - - private native String checkReserveProofJni(String address, String message, String signature); - - private native String createPaymentUriJni(String sendRequestJson); - - private native String parsePaymentUriJni(String uri); - - private native String getAttributeJni(String key); - - private native void setAttributeJni(String key, String val); - - private native void startMiningJni(long numThreads, Boolean backgroundMining, Boolean ignoreBattery); - - private native void stopMiningJni(); - - private native boolean isMultisigImportNeededJni(); - - private native String getMultisigInfoJni(); - - private native String prepareMultisigJni(); - - private native String makeMultisigJni(String[] multisigHexes, int threshold, String password); - - private native String exchangeMultisigKeysJni(String[] multisigHexes, String password); - - private native String getMultisigHexJni(); - - private native int importMultisigHexJni(String[] multisigHexes); - - private native String signMultisigTxHexJni(String multisigTxHex); - - private native String[] submitMultisigTxHexJni(String signedMultisigTxHex); - - private native void saveJni(); - - private native void moveToJni(String path, String password); - - private native void closeJni(boolean save); - - // ------------------------------- LISTENERS -------------------------------- - - /** - * Receives notifications directly from jni c++. - */ - @SuppressWarnings("unused") // called directly from jni c++ - private class WalletJniListener { - - /** - * Enables or disables listening in the c++ wallet. - */ - public void setIsListening(boolean isEnabled) { - jniListenerHandle = setListenerJni(isEnabled ? this : null); - } - - public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { - for (MoneroWalletListenerI listener : listeners) { - listener.onSyncProgress(height, startHeight, endHeight, percentDone, message); - } - } - - public void onNewBlock(long height) { - for (MoneroWalletListenerI listener : listeners) listener.onNewBlock(height); - } - - public void onOutputReceived(long height, String txId, String amountStr, int accountIdx, int subaddressIdx, int version, long unlockTime) { - - // build received output - MoneroOutputWallet output = new MoneroOutputWallet(); - output.setAmount(new BigInteger(amountStr)); - output.setAccountIndex(accountIdx); - output.setSubaddressIndex(subaddressIdx); - MoneroTxWallet tx = new MoneroTxWallet(); - tx.setId(txId); - tx.setVersion(version); - tx.setUnlockTime(unlockTime); - output.setTx(tx); - tx.setVouts(Arrays.asList(output)); - if (height > 0) { - MoneroBlock block = new MoneroBlock().setHeight(height); - block.setTxs(Arrays.asList(tx)); - tx.setBlock(block); - } - - // announce output - for (MoneroWalletListenerI listener : listeners) listener.onOutputReceived((MoneroOutputWallet) tx.getVouts().get(0)); - } - - public void onOutputSpent(long height, String txId, String amountStr, int accountIdx, int subaddressIdx, int version) { - - // build spent output - MoneroOutputWallet output = new MoneroOutputWallet(); - output.setAmount(new BigInteger(amountStr)); - output.setAccountIndex(accountIdx); - output.setSubaddressIndex(subaddressIdx); - MoneroTxWallet tx = new MoneroTxWallet(); - tx.setId(txId); - tx.setVersion(version); - output.setTx(tx); - tx.setVins(Arrays.asList(output)); - if (height > 0) { - MoneroBlock block = new MoneroBlock().setHeight(height); - block.setTxs(Arrays.asList(tx)); - tx.setBlock(block); - } - - // announce output - for (MoneroWalletListenerI listener : listeners) listener.onOutputSpent((MoneroOutputWallet) tx.getVins().get(0)); - } - } - - /** - * Wraps a sync listener as a general wallet listener. - */ - private class SyncListenerWrapper extends MoneroWalletListener { - - private MoneroSyncListener listener; - - public SyncListenerWrapper(MoneroSyncListener listener) { - this.listener = listener; - } - - @Override - public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { - listener.onSyncProgress(height, startHeight, endHeight, percentDone, message); - } - } - - // ------------------------ RESPONSE DESERIALIZATION ------------------------ - - /** - * Override MoneroBlock with wallet types for polymorphic deserialization. - */ - private static class MoneroBlockWallet extends MoneroBlock { - - // default constructor necessary for serialization - @SuppressWarnings("unused") - public MoneroBlockWallet() { - super(); - } - - @JsonProperty("txs") - public MoneroBlockWallet setTxWallets(List txs) { - super.setTxs(new ArrayList(txs)); - return this; - } - - /** - * Initializes a new MoneroBlock with direct references to this block. - * - * TODO: more efficient way to deserialize directly into MoneroBlock? - * - * @return MoneroBlock is the newly initialized block with direct references to this block - */ - public MoneroBlock toBlock() { - MoneroBlock block = new MoneroBlock(); - block.setId(getId()); - block.setHeight(getHeight()); - block.setTimestamp(getTimestamp()); - block.setSize(getSize()); - block.setWeight(getWeight()); - block.setLongTermWeight(getLongTermWeight()); - block.setDepth(getDepth()); - block.setDifficulty(getDifficulty()); - block.setCumulativeDifficulty(getCumulativeDifficulty()); - block.setMajorVersion(getMajorVersion()); - block.setMinorVersion(getMinorVersion()); - block.setNonce(getNonce()); - block.setMinerTxId(getMinerTxId()); - block.setNumTxs(getNumTxs()); - block.setOrphanStatus(getOrphanStatus()); - block.setPrevId(getPrevId()); - block.setReward(getReward()); - block.setPowHash(getPowHash()); - block.setHex(getHex()); - block.setMinerTx(getMinerTx()); - block.setTxs(getTxs()); - block.setTxIds(getTxIds()); - for (MoneroTx tx : getTxs()) tx.setBlock(block); // re-assign tx block references - return block; - } - } - - private static class AccountsContainer { - public List accounts; - }; - - private static class SubaddressesContainer { - public List subaddresses; - }; - - private static class BlocksContainer { - public List blocks; - } - - private static class TxSetsContainer { - public List txSets; - } - - private static class KeyImagesContainer { - public List keyImages; - @SuppressWarnings("unused") public KeyImagesContainer() { } // necessary for serialization - public KeyImagesContainer(List keyImages) { this.keyImages = keyImages; }; - } - - private static List deserializeBlocks(String blocksJson) { - List blockWallets = JsonUtils.deserialize(MoneroRpcConnection.MAPPER, blocksJson, BlocksContainer.class).blocks; - List blocks = new ArrayList(); - if (blockWallets == null) return blocks; - for (MoneroBlockWallet blockWallet: blockWallets) blocks.add(blockWallet.toBlock()); - return blocks; - } - - // ---------------------------- PRIVATE HELPERS ----------------------------- - - private void assertNotClosed() { - if (isClosed) throw new MoneroException("Wallet is closed"); - } - - private static MoneroAccount sanitizeAccount(MoneroAccount account) { - if (account.getSubaddresses() != null) { - for (MoneroSubaddress subaddress : account.getSubaddresses()) sanitizeSubaddress(subaddress); - } - return account; - } - - private static MoneroSubaddress sanitizeSubaddress(MoneroSubaddress subaddress) { - if ("".equals(subaddress.getLabel())) subaddress.setLabel(null); - return subaddress; - } - - private static MoneroBlock sanitizeBlock(MoneroBlock block) { - for (MoneroTx tx : block.getTxs()) sanitizeTxWallet((MoneroTxWallet) tx); - return block; - } - - private static MoneroTxWallet sanitizeTxWallet(MoneroTxWallet tx) { - return tx; - } -} diff --git a/core/src/main/java/monero/wallet/MoneroWalletRpc.java b/core/src/main/java/monero/wallet/MoneroWalletRpc.java deleted file mode 100644 index d8632ec5aff..00000000000 --- a/core/src/main/java/monero/wallet/MoneroWalletRpc.java +++ /dev/null @@ -1,2207 +0,0 @@ -/** - * Copyright (c) 2017-2019 woodser - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package monero.wallet; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.math.BigInteger; -import java.net.URI; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.apache.log4j.Logger; - -import monero.daemon.model.MoneroBlock; -import monero.daemon.model.MoneroBlockHeader; -import monero.daemon.model.MoneroKeyImage; -import monero.daemon.model.MoneroOutput; -import monero.daemon.model.MoneroTx; -import monero.rpc.MoneroRpcConnection; -import monero.rpc.MoneroRpcException; -import monero.utils.MoneroException; -import monero.wallet.model.MoneroAccount; -import monero.wallet.model.MoneroAccountTag; -import monero.wallet.model.MoneroAddressBookEntry; -import monero.wallet.model.MoneroCheckReserve; -import monero.wallet.model.MoneroCheckTx; -import monero.wallet.model.MoneroDestination; -import monero.wallet.model.MoneroIncomingTransfer; -import monero.wallet.model.MoneroIntegratedAddress; -import monero.wallet.model.MoneroKeyImageImportResult; -import monero.wallet.model.MoneroMultisigInfo; -import monero.wallet.model.MoneroMultisigInitResult; -import monero.wallet.model.MoneroMultisigSignResult; -import monero.wallet.model.MoneroOutgoingTransfer; -import monero.wallet.model.MoneroOutputQuery; -import monero.wallet.model.MoneroOutputWallet; -import monero.wallet.model.MoneroSendRequest; -import monero.wallet.model.MoneroSubaddress; -import monero.wallet.model.MoneroSyncListener; -import monero.wallet.model.MoneroSyncResult; -import monero.wallet.model.MoneroTransfer; -import monero.wallet.model.MoneroTransferQuery; -import monero.wallet.model.MoneroTxQuery; -import monero.wallet.model.MoneroTxSet; -import monero.wallet.model.MoneroTxWallet; - -/** - * Implements a Monero wallet using monero-wallet-rpc. - */ -public class MoneroWalletRpc extends MoneroWalletDefault { - - private String path; // wallet's path identifier - private MoneroRpcConnection rpc; // handles rpc interactions - private Map> addressCache; // cache static addresses to reduce requests - - // static - private static final int ERROR_CODE_INVALID_PAYMENT_ID = -5; // invalid payment id error code - private static final Logger LOGGER = Logger.getLogger(MoneroWalletRpc.class); // logger - private static final TxHeightComparator TX_HEIGHT_COMPARATOR = new TxHeightComparator(); - - public MoneroWalletRpc(URI uri) { - this(new MoneroRpcConnection(uri)); - } - - public MoneroWalletRpc(String uri) { - this(new MoneroRpcConnection(uri)); - } - - public MoneroWalletRpc(String uri, String username, String password) { - this(new MoneroRpcConnection(uri, username, password)); - } - - public MoneroWalletRpc(URI uri, String username, String password) { - this(new MoneroRpcConnection(uri, username, password)); - } - - public MoneroWalletRpc(MoneroRpcConnection rpc) { - this.rpc = rpc; - addressCache = new HashMap>(); - } - - // --------------------------- RPC WALLET METHODS --------------------------- - - /** - * Get the wallet's RPC connection. - * - * @return the wallet's rpc connection - */ - public MoneroRpcConnection getRpcConnection() { - return rpc; - } - - /** - * Open an existing wallet on the RPC server. - * - * @param name is the name of the wallet file to open - * @param password is the wallet's password - */ - public void openWallet(String name, String password) { - if (name == null || name.isEmpty()) throw new MoneroException("Filename is not initialized"); - if (password == null || password.isEmpty()) throw new MoneroException("Password is not initialized"); - Map params = new HashMap(); - params.put("filename", name); - params.put("password", password); - rpc.sendJsonRequest("open_wallet", params); - addressCache.clear(); - path = name; - } - - /** - * Create and open a new wallet with a randomly generated seed on the RPC server. - * - * @param name is the name of the wallet file to create - * @param password is the wallet's password - * @param language is the language for the wallet's mnemonic seed - */ - public void createWalletRandom(String name, String password) { createWalletRandom(name, password, null); } - public void createWalletRandom(String name, String password, String language) { - if (name == null || name.isEmpty()) throw new MoneroException("Wallet name is not initialized"); - if (password == null || password.isEmpty()) throw new MoneroException("Password is not initialized"); - if (language == null || language.isEmpty()) language = DEFAULT_LANGUAGE; - Map params = new HashMap(); - params.put("filename", name); - params.put("password", password); - params.put("language", language); - rpc.sendJsonRequest("create_wallet", params); - path = name; - } - - /** - * Create and open a wallet from an existing mnemonic phrase on the RPC server, - * closing the currently open wallet if applicable. - * - * @param name is the name of the wallet to create on the RPC server - * @param password is the wallet's password - * @param mnemonic is the mnemonic of the wallet to construct - * @param restoreHeight is the block height to restore from (default = 0) - * @param language is the language of the mnemonic in case the old language is invalid - * @param offset is the offset for restoring from mnemonic - * @param saveCurrent specifies if the current RPC wallet should be saved before being closed - */ - public void createWalletFromMnemonic(String name, String password, String mnemonic) { createWalletFromMnemonic(name, password, mnemonic, null, null, null, null); } - public void createWalletFromMnemonic(String name, String password, String mnemonic, Long restoreHeight) { createWalletFromMnemonic(name, password, mnemonic, restoreHeight, null, null, null); } - public void createWalletFromMnemonic(String name, String password, String mnemonic, Long restoreHeight, String language, String offset, Boolean saveCurrent) { - if (language == null) language = DEFAULT_LANGUAGE; - Map params = new HashMap(); - params.put("filename", name); - params.put("password", password); - params.put("seed", mnemonic); - params.put("seed_offset", offset); - params.put("restore_height", restoreHeight); - params.put("language", language); - params.put("autosave_current", saveCurrent); - rpc.sendJsonRequest("restore_deterministic_wallet", params); - path = name; - } - - /** - * Save and close the current wallet and stop the RPC server. - */ - public void stop() { - rpc.sendJsonRequest("stop_wallet"); - addressCache.clear(); - path = null; - } - - // -------------------------- COMMON WALLET METHODS ------------------------- - - @Override - public String getPath() { - return path; - } - - @Override - public String getSeed() { - throw new MoneroException("monero-wallet-rpc does not support getting the wallet seed"); - } - - @SuppressWarnings("unchecked") - @Override - public String getMnemonic() { - Map params = new HashMap(); - params.put("key_type", "mnemonic"); - Map resp = rpc.sendJsonRequest("query_key", params); - Map result = (Map) resp.get("result"); - return (String) result.get("key"); - } - - @SuppressWarnings("unchecked") - @Override - public List getLanguages() { - Map resp = rpc.sendJsonRequest("get_languages"); - Map result = (Map) resp.get("result"); - return (List) result.get("languages"); - } - - @SuppressWarnings("unchecked") - @Override - public String getPrivateViewKey() { - Map params = new HashMap(); - params.put("key_type", "view_key"); - Map resp = rpc.sendJsonRequest("query_key", params); - Map result = (Map) resp.get("result"); - return (String) result.get("key"); - } - - @SuppressWarnings("unchecked") - @Override - public String getPrivateSpendKey() { - Map params = new HashMap(); - params.put("key_type", "spend_key"); - Map resp = rpc.sendJsonRequest("query_key", params); - Map result = (Map) resp.get("result"); - return (String) result.get("key"); - } - - @Override - public String getAddress(int accountIdx, int subaddressIdx) { - Map subaddressMap = addressCache.get(accountIdx); - if (subaddressMap == null) { - getSubaddresses(accountIdx, null, true); // cache's all addresses at this account - return getAddress(accountIdx, subaddressIdx); // uses cache - } - String address = subaddressMap.get(subaddressIdx); - if (address == null) { - getSubaddresses(accountIdx, null, true); // cache's all addresses at this account - return addressCache.get(accountIdx).get(subaddressIdx); - } - return address; - } - - // TODO: use cache - @SuppressWarnings("unchecked") - @Override - public MoneroSubaddress getAddressIndex(String address) { - - // fetch result and normalize error if address does not belong to the wallet - Map result; - try { - Map params = new HashMap(); - params.put("address", address); - Map resp = rpc.sendJsonRequest("get_address_index", params); - result = (Map) resp.get("result"); - } catch (MoneroRpcException e) { - System.out.println(e.getMessage()); - if (e.getCode() == -2) throw new MoneroException(e.getMessage(), e.getCode()); - throw e; - } - - // convert rpc response - Map rpcIndices = (Map) result.get("index"); - MoneroSubaddress subaddress = new MoneroSubaddress(address); - subaddress.setAccountIndex(rpcIndices.get("major").intValue()); - subaddress.setIndex(rpcIndices.get("minor").intValue()); - return subaddress; - } - - @SuppressWarnings("unchecked") - @Override - public MoneroIntegratedAddress getIntegratedAddress(String paymentId) { - try { - Map params = new HashMap(); - params.put("payment_id", paymentId); - Map resp = rpc.sendJsonRequest("make_integrated_address", params); - Map result = (Map) resp.get("result"); - String integratedAddressStr = (String) result.get("integrated_address"); - return decodeIntegratedAddress(integratedAddressStr); - } catch (MoneroRpcException e) { - if (e.getMessage().contains("Invalid payment ID")) throw new MoneroException("Invalid payment ID: " + paymentId, ERROR_CODE_INVALID_PAYMENT_ID); - throw e; - } - } - - @SuppressWarnings("unchecked") - @Override - public MoneroIntegratedAddress decodeIntegratedAddress(String integratedAddress) { - Map params = new HashMap(); - params.put("integrated_address", integratedAddress); - Map resp = rpc.sendJsonRequest("split_integrated_address", params); - Map result = (Map) resp.get("result"); - return new MoneroIntegratedAddress((String) result.get("standard_address"), (String) result.get("payment_id"), integratedAddress); - } - - @SuppressWarnings("unchecked") - @Override - public long getHeight() { - Map resp = rpc.sendJsonRequest("get_height"); - Map result = (Map) resp.get("result"); - return ((BigInteger) result.get("height")).longValue(); - } - - @Override - public long getDaemonHeight() { - throw new MoneroException("monero-wallet-rpc does not support getting the chain height"); - } - - @SuppressWarnings("unchecked") - @Override - public MoneroSyncResult sync(Long startHeight, MoneroSyncListener listener) { - if (listener != null) throw new MoneroException("Monero Wallet RPC does not support reporting sync progress"); - Map params = new HashMap(); - params.put("start_height", startHeight); - Map resp = rpc.sendJsonRequest("refresh", params); - Map result = (Map) resp.get("result"); - return new MoneroSyncResult(((BigInteger) result.get("blocks_fetched")).longValue(), (Boolean) result.get("received_money")); - } - - @Override - public void startSyncing() { - // nothing to do because wallet rpc syncs automatically - } - - @Override - public void stopSyncing() { - throw new MoneroException("Monero Wallet RPC does not support the ability to stop syncing"); - } - - @Override - public void rescanSpent() { - rpc.sendJsonRequest("rescan_spent"); - } - - public void rescanBlockchain() { - rpc.sendJsonRequest("rescan_blockchain"); - } - - @Override - public BigInteger getBalance() { - return getBalances(null, null)[0]; - } - - @Override - public BigInteger getBalance(int accountIdx) { - return getBalances(accountIdx, null)[0]; - } - - @Override - public BigInteger getBalance(int accountIdx, int subaddressIdx) { - return getBalances(accountIdx, subaddressIdx)[0]; - } - - @Override - public BigInteger getUnlockedBalance() { - return getBalances(null, null)[1]; - } - - @Override - public BigInteger getUnlockedBalance(int accountIdx) { - return getBalances(accountIdx, null)[1]; - } - - @Override - public BigInteger getUnlockedBalance(int accountIdx, int subaddressIdx) { - return getBalances(accountIdx, subaddressIdx)[1]; - } - - @Override - public List getAccounts(boolean includeSubaddresses, String tag) { - return getAccounts(includeSubaddresses, tag, false); - } - - @SuppressWarnings("unchecked") - public List getAccounts(boolean includeSubaddresses, String tag, boolean skipBalances) { - - // fetch accounts from rpc - Map params = new HashMap(); - params.put("tag", tag); - Map resp = rpc.sendJsonRequest("get_accounts", params); - Map result = (Map) resp.get("result"); - - // build account objects and fetch subaddresses per account using get_address - // TODO monero-wallet-rpc: get_address should support all_accounts so not called once per account - List accounts = new ArrayList(); - for (Map rpcAccount : (List>) result.get("subaddress_accounts")) { - MoneroAccount account = convertRpcAccount(rpcAccount); - if (includeSubaddresses) account.setSubaddresses(getSubaddresses(account.getIndex(), null, true)); - accounts.add(account); - } - - // fetch and merge fields from get_balance across all accounts - if (includeSubaddresses && !skipBalances) { - - // these fields are not initialized if subaddress is unused and therefore not returned from `get_balance` - for (MoneroAccount account : accounts) { - for (MoneroSubaddress subaddress : account.getSubaddresses()) { - subaddress.setBalance(BigInteger.valueOf(0)); - subaddress.setUnlockedBalance(BigInteger.valueOf(0)); - subaddress.setNumUnspentOutputs(0l); - subaddress.setNumBlocksToUnlock(0l); - } - } - - // fetch and merge info from get_balance - params.clear(); - params.put("all_accounts", true); - resp = rpc.sendJsonRequest("get_balance", params); - result = (Map) resp.get("result"); - if (result.containsKey("per_subaddress")) { - for (Map rpcSubaddress : (List>) result.get("per_subaddress")) { - MoneroSubaddress subaddress = convertRpcSubaddress(rpcSubaddress); - - // merge info - MoneroAccount account = accounts.get(subaddress.getAccountIndex()); - assertEquals("RPC accounts are out of order", account.getIndex(), subaddress.getAccountIndex()); // would need to switch lookup to loop - MoneroSubaddress tgtSubaddress = account.getSubaddresses().get(subaddress.getIndex()); - assertEquals("RPC subaddresses are out of order", tgtSubaddress.getIndex(), subaddress.getIndex()); - if (subaddress.getBalance() != null) tgtSubaddress.setBalance(subaddress.getBalance()); - if (subaddress.getUnlockedBalance() != null) tgtSubaddress.setUnlockedBalance(subaddress.getUnlockedBalance()); - if (subaddress.getNumUnspentOutputs() != null) tgtSubaddress.setNumUnspentOutputs(subaddress.getNumUnspentOutputs()); - if (subaddress.getNumBlocksToUnlock() != null) tgtSubaddress.setNumBlocksToUnlock(subaddress.getNumBlocksToUnlock()); - } - } - } - - // return accounts - return accounts; - } - - // TODO: getAccountByIndex(), getAccountByTag() - @Override - public MoneroAccount getAccount(int accountIdx, boolean includeSubaddresses) { - return getAccount(accountIdx, includeSubaddresses, false); - } - - public MoneroAccount getAccount(int accountIdx, boolean includeSubaddresses, boolean skipBalances) { - if (accountIdx < 0) throw new MoneroException("Account index must be greater than or equal to 0"); - for (MoneroAccount account : getAccounts()) { - if (account.getIndex() == accountIdx) { - if (includeSubaddresses) account.setSubaddresses(getSubaddresses(accountIdx, null, skipBalances)); - return account; - } - } - throw new MoneroException("Account with index " + accountIdx + " does not exist"); - } - - @SuppressWarnings("unchecked") - @Override - public MoneroAccount createAccount(String label) { - label = label == null || label.isEmpty() ? null : label; - Map params = new HashMap(); - params.put("label", label); - Map resp = rpc.sendJsonRequest("create_account", params); - Map result = (Map) resp.get("result"); - return new MoneroAccount(((BigInteger) result.get("account_index")).intValue(), (String) result.get("address"), BigInteger.valueOf(0), BigInteger.valueOf(0), null); - } - - @Override - public List getSubaddresses(int accountIdx, List subaddressIndices) { - return getSubaddresses(accountIdx, subaddressIndices, false); - } - - @SuppressWarnings("unchecked") - public List getSubaddresses(int accountIdx, List subaddressIndices, boolean skipBalances) { - - // fetch subaddresses - Map params = new HashMap(); - params.put("account_index", accountIdx); - if (subaddressIndices != null && !subaddressIndices.isEmpty()) params.put("address_index", subaddressIndices); - Map resp = rpc.sendJsonRequest("get_address", params); - Map result = (Map) resp.get("result"); - - // initialize subaddresses - List subaddresses = new ArrayList(); - for (Map rpcSubaddress : (List>) result.get("addresses")) { - MoneroSubaddress subaddress = convertRpcSubaddress(rpcSubaddress); - subaddress.setAccountIndex(accountIdx); - subaddresses.add(subaddress); - } - - // fetch and initialize subaddress balances - if (!skipBalances) { - - // these fields are not initialized if subaddress is unused and therefore not returned from `get_balance` - for (MoneroSubaddress subaddress : subaddresses) { - subaddress.setBalance(BigInteger.valueOf(0)); - subaddress.setUnlockedBalance(BigInteger.valueOf(0)); - subaddress.setNumUnspentOutputs(0l); - subaddress.setNumBlocksToUnlock(0l); - } - - // fetch and initialize balances - resp = rpc.sendJsonRequest("get_balance", params); - result = (Map) resp.get("result"); - if (result.containsKey("per_subaddress")) { - for (Map rpcSubaddress : (List>) result.get("per_subaddress")) { - MoneroSubaddress subaddress = convertRpcSubaddress(rpcSubaddress); - - // transfer info to existing subaddress object - for (MoneroSubaddress tgtSubaddress : subaddresses) { - if (!tgtSubaddress.getIndex().equals(subaddress.getIndex())) continue; // skip to subaddress with same index - if (subaddress.getBalance() != null) tgtSubaddress.setBalance(subaddress.getBalance()); - if (subaddress.getUnlockedBalance() != null) tgtSubaddress.setUnlockedBalance(subaddress.getUnlockedBalance()); - if (subaddress.getNumUnspentOutputs() != null) tgtSubaddress.setNumUnspentOutputs(subaddress.getNumUnspentOutputs()); - if (subaddress.getNumBlocksToUnlock() != null) tgtSubaddress.setNumBlocksToUnlock(subaddress.getNumBlocksToUnlock()); - } - } - } - } - - // cache addresses - Map subaddressMap = addressCache.get(accountIdx); - if (subaddressMap == null) { - subaddressMap = new HashMap(); - addressCache.put(accountIdx, subaddressMap); - } - for (MoneroSubaddress subaddress : subaddresses) { - subaddressMap.put(subaddress.getIndex(), subaddress.getAddress()); - } - - // return results - return subaddresses; - } - - @SuppressWarnings("unchecked") - @Override - public MoneroSubaddress createSubaddress(int accountIdx, String label) { - - // send request - Map params = new HashMap(); - params.put("account_index", accountIdx); - params.put("label", label); - Map resp = rpc.sendJsonRequest("create_address", params); - Map result = (Map) resp.get("result"); - - // build subaddress object - MoneroSubaddress subaddress = new MoneroSubaddress(); - subaddress.setAccountIndex(accountIdx); - subaddress.setIndex(((BigInteger) result.get("address_index")).intValue()); - subaddress.setAddress((String) result.get("address")); - subaddress.setLabel(label); - subaddress.setBalance(BigInteger.valueOf(0)); - subaddress.setUnlockedBalance(BigInteger.valueOf(0)); - subaddress.setNumUnspentOutputs(0l); - subaddress.setIsUsed(false); - subaddress.setNumBlocksToUnlock(0l); - return subaddress; - } - - @Override - public List getTxs(MoneroTxQuery query) { - - // copy and normalize tx query - query = query == null ? new MoneroTxQuery() : query.copy(); - if (query.getTransferQuery() == null) query.setTransferQuery(new MoneroTransferQuery()); - if (query.getOutputQuery() == null) query.setOutputQuery(new MoneroOutputQuery()); - - // temporarily disable transfer and output queries in order to collect all tx information - MoneroTransferQuery transferQuery = query.getTransferQuery(); - MoneroOutputQuery outputQuery = query.getOutputQuery(); - query.setTransferQuery(null); - query.setOutputQuery(null); - - // fetch all transfers that meet tx query - List transfers = getTransfers(new MoneroTransferQuery().setTxQuery(query)); - - // collect unique txs from transfers while retaining order - List txs = new ArrayList(); - Set txsSet = new HashSet(); - for (MoneroTransfer transfer : transfers) { - if (!txsSet.contains(transfer.getTx())) { - txs.add(transfer.getTx()); - txsSet.add(transfer.getTx()); - } - } - - // cache types into maps for merging and lookup - Map txMap = new HashMap(); - Map blockMap = new HashMap(); - for (MoneroTxWallet tx : txs) { - mergeTx(tx, txMap, blockMap, false); - } - - // fetch and merge outputs if queried - if (Boolean.TRUE.equals(query.getIncludeOutputs()) || !outputQuery.isDefault()) { - List outputs = getOutputs(new MoneroOutputQuery().setTxQuery(query)); - - // merge output txs one time while retaining order - Set outputTxs = new HashSet(); - for (MoneroOutputWallet output : outputs) { - if (!outputTxs.contains(output.getTx())) { - mergeTx(output.getTx(), txMap, blockMap, true); - outputTxs.add(output.getTx()); - } - } - } - - // restore transfer and output queries - query.setTransferQuery(transferQuery); - query.setOutputQuery(outputQuery); - - // filter txs that don't meet transfer and output queries - List txsQueried = new ArrayList(); - for (MoneroTxWallet tx : txs) { - if (query.meetsCriteria(tx)) txsQueried.add(tx); - else if (tx.getBlock() != null) tx.getBlock().getTxs().remove(tx); - } - txs = txsQueried; - - // verify all specified tx ids found - if (query.getTxIds() != null) { - for (String txId : query.getTxIds()) { - boolean found = false; - for (MoneroTxWallet tx : txs) { - if (txId.equals(tx.getId())) { - found = true; - break; - } - } - if (!found) throw new MoneroException("Tx not found in wallet: " + txId); - } - } - - // special case: re-fetch txs if inconsistency caused by needing to make multiple rpc calls - for (MoneroTxWallet tx : txs) { - if (tx.isConfirmed() && tx.getBlock() == null) return getTxs(query); - } - - // order txs if tx ids given - if (query.getTxIds() != null && !query.getTxIds().isEmpty()) { - Map txsById = new HashMap(); // store txs in temporary map for sorting - for (MoneroTxWallet tx : txs) txsById.put(tx.getId(), tx); - List orderedTxs = new ArrayList(); - for (String txId : query.getTxIds()) orderedTxs.add(txsById.get(txId)); - txs = orderedTxs; - } - return txs; - } - - @SuppressWarnings("unchecked") - @Override - public List getTransfers(MoneroTransferQuery query) { - - // copy and normalize query up to block - if (query == null) query = new MoneroTransferQuery(); - else { - if (query.getTxQuery() == null) query = query.copy(); - else { - MoneroTxQuery txQuery = query.getTxQuery().copy(); - if (query.getTxQuery().getTransferQuery() == query) query = txQuery.getTransferQuery(); - else { - assertNull("Transfer query's tx query must be circular reference or null", query.getTxQuery().getTransferQuery()); - query = query.copy(); - query.setTxQuery(txQuery); - } - } - } - if (query.getTxQuery() == null) query.setTxQuery(new MoneroTxQuery()); - MoneroTxQuery txQuery = query.getTxQuery(); - txQuery.setTransferQuery(null); // break circular link for meetsCriteria() - - // build params for get_transfers rpc call - Map params = new HashMap(); - boolean canBeConfirmed = !Boolean.FALSE.equals(txQuery.isConfirmed()) && !Boolean.TRUE.equals(txQuery.inTxPool()) && !Boolean.TRUE.equals(txQuery.isFailed()) && !Boolean.FALSE.equals(txQuery.isRelayed()); - boolean canBeInTxPool = !Boolean.TRUE.equals(txQuery.isConfirmed()) && !Boolean.FALSE.equals(txQuery.inTxPool()) && !Boolean.TRUE.equals(txQuery.isFailed()) && !Boolean.FALSE.equals(txQuery.isRelayed()) && txQuery.getHeight() == null && txQuery.getMinHeight() == null && txQuery.getMaxHeight() == null; - boolean canBeIncoming = !Boolean.FALSE.equals(query.isIncoming()) && !Boolean.TRUE.equals(query.isOutgoing()) && !Boolean.TRUE.equals(query.hasDestinations()); - boolean canBeOutgoing = !Boolean.FALSE.equals(query.isOutgoing()) && !Boolean.TRUE.equals(query.isIncoming()); - params.put("in", canBeIncoming && canBeConfirmed); - params.put("out", canBeOutgoing && canBeConfirmed); - params.put("pool", canBeIncoming && canBeInTxPool); - params.put("pending", canBeOutgoing && canBeInTxPool); - params.put("failed", !Boolean.FALSE.equals(txQuery.isFailed()) && !Boolean.TRUE.equals(txQuery.isConfirmed()) && !Boolean.TRUE.equals(txQuery.inTxPool())); - if (txQuery.getMinHeight() != null) { - if (txQuery.getMinHeight() > 0) params.put("min_height", txQuery.getMinHeight() - 1); // TODO monero core: wallet2::get_payments() min_height is exclusive, so manually offset to match intended range (issues #5751, #5598) - else params.put("min_height", txQuery.getMinHeight()); - } - if (txQuery.getMaxHeight() != null) params.put("max_height", txQuery.getMaxHeight()); - params.put("filter_by_height", txQuery.getMinHeight() != null || txQuery.getMaxHeight() != null); - if (query.getAccountIndex() == null) { - assertTrue("Filter specifies a subaddress index but not an account index", query.getSubaddressIndex() == null && query.getSubaddressIndices() == null); - params.put("all_accounts", true); - } else { - params.put("account_index", query.getAccountIndex()); - - // set subaddress indices param - Set subaddressIndices = new HashSet(); - if (query.getSubaddressIndex() != null) subaddressIndices.add(query.getSubaddressIndex()); - if (query.getSubaddressIndices() != null) { - for (int subaddressIdx : query.getSubaddressIndices()) subaddressIndices.add(subaddressIdx); - } - if (!subaddressIndices.isEmpty()) params.put("subaddr_indices", new ArrayList(subaddressIndices)); - } - - // cache unique txs and blocks - Map txMap = new HashMap(); - Map blockMap = new HashMap(); - - // build txs using `get_transfers` - Map resp = rpc.sendJsonRequest("get_transfers", params); - Map result = (Map) resp.get("result"); - for (String key : result.keySet()) { - for (Map rpcTx :((List>) result.get(key))) { - MoneroTxWallet tx = convertRpcTxWithTransfer(rpcTx, null, null); - if (tx.isConfirmed()) assertTrue(tx.getBlock().getTxs().contains(tx)); -// if (tx.getId().equals("38436c710dfbebfb24a14cddfd430d422e7282bbe94da5e080643a1bd2880b44")) { -// System.out.println(rpcTx); -// System.out.println(tx.getOutgoingAmount().compareTo(BigInteger.valueOf(0)) == 0); -// } - - // replace transfer amount with destination sum - // TODO monero-wallet-rpc: confirmed tx from/to same account has amount 0 but cached transfers - if (tx.getOutgoingTransfer() != null && Boolean.TRUE.equals(tx.isRelayed()) && !Boolean.TRUE.equals(tx.isFailed()) && - tx.getOutgoingTransfer().getDestinations() != null && tx.getOutgoingAmount().compareTo(BigInteger.valueOf(0)) == 0) { - MoneroOutgoingTransfer outgoingTransfer = tx.getOutgoingTransfer(); - BigInteger transferTotal = BigInteger.valueOf(0); - for (MoneroDestination destination : outgoingTransfer.getDestinations()) transferTotal = transferTotal.add(destination.getAmount()); - tx.getOutgoingTransfer().setAmount(transferTotal); - } - - // merge tx - mergeTx(tx, txMap, blockMap, false); - } - } - - // sort txs by block height - List txs = new ArrayList(txMap.values()); - Collections.sort(txs, new TxHeightComparator()); - - // filter and return transfers - List transfers = new ArrayList(); - for (MoneroTxWallet tx : txs) { - - // sort transfers - if (tx.getIncomingTransfers() != null) Collections.sort(tx.getIncomingTransfers(), new IncomingTransferComparator()); - - // collect outgoing transfer, erase if excluded - if (tx.getOutgoingTransfer() != null && query.meetsCriteria(tx.getOutgoingTransfer())) transfers.add(tx.getOutgoingTransfer()); - else tx.setOutgoingTransfer(null); - - // collect incoming transfers, erase if excluded - if (tx.getIncomingTransfers() != null) { - List toRemoves = new ArrayList(); - for (MoneroIncomingTransfer transfer : tx.getIncomingTransfers()) { - if (query.meetsCriteria(transfer)) transfers.add(transfer); - else toRemoves.add(transfer); - } - tx.getIncomingTransfers().removeAll(toRemoves); - if (tx.getIncomingTransfers().isEmpty()) tx.setIncomingTransfers(null); - } - - // remove excluded txs from block - if (tx.getBlock() != null && tx.getOutgoingTransfer() == null && tx.getIncomingTransfers() == null ) { - tx.getBlock().getTxs().remove(tx); - } - } - return transfers; - } - - @SuppressWarnings("unchecked") - @Override - public List getOutputs(MoneroOutputQuery query) { - - // copy and normalize query up to block - if (query == null) query = new MoneroOutputQuery(); - else { - if (query.getTxQuery() == null) query = query.copy(); - else { - MoneroTxQuery txQuery = query.getTxQuery().copy(); - if (query.getTxQuery().getOutputQuery() == query) query = txQuery.getOutputQuery(); - else { - assertNull("Output request's tx request must be circular reference or null", query.getTxQuery().getOutputQuery()); - query = query.copy(); - query.setTxQuery(txQuery); - } - } - } - if (query.getTxQuery() == null) query.setTxQuery(new MoneroTxQuery()); - MoneroTxQuery txQuery = query.getTxQuery(); - txQuery.setOutputQuery(null); // break circular link for meetsCriteria() - - // determine account and subaddress indices to be queried - Map> indices = new HashMap>(); - if (query.getAccountIndex() != null) { - Set subaddressIndices = new HashSet(); - if (query.getSubaddressIndex() != null) subaddressIndices.add(query.getSubaddressIndex()); - if (query.getSubaddressIndices() != null) for (int subaddressIdx : query.getSubaddressIndices()) subaddressIndices.add(subaddressIdx); - indices.put(query.getAccountIndex(), subaddressIndices.isEmpty() ? null : new ArrayList(subaddressIndices)); // null will fetch from all subaddresses - } else { - assertEquals("Request specifies a subaddress index but not an account index", null, query.getSubaddressIndex()); - assertTrue("Request specifies subaddress indices but not an account index", query.getSubaddressIndices() == null || query.getSubaddressIndices().size() == 0); - indices = getAccountIndices(false); // fetch all account indices without subaddresses - } - - // cache unique txs and blocks - Map txMap = new HashMap(); - Map blockMap = new HashMap(); - - // collect txs with vouts for each indicated account using `incoming_transfers` rpc call - Map params = new HashMap(); - String transferType; - if (Boolean.TRUE.equals(query.isSpent())) transferType = "unavailable"; - else if (Boolean.FALSE.equals(query.isSpent())) transferType = "available"; - else transferType = "all"; - params.put("transfer_type", transferType); - params.put("verbose", true); - for (int accountIdx : indices.keySet()) { - - // send request - params.put("account_index", accountIdx); - params.put("subaddr_indices", indices.get(accountIdx)); - Map resp = rpc.sendJsonRequest("incoming_transfers", params); - Map result = (Map) resp.get("result"); - - // convert response to txs with vouts and merge - if (!result.containsKey("transfers")) continue; - for (Map rpcVout : (List>) result.get("transfers")) { - MoneroTxWallet tx = convertRpcTxWithVout(rpcVout); - mergeTx(tx, txMap, blockMap, false); - } - } - - // sort txs by block height - List txs = new ArrayList(txMap.values()); - Collections.sort(txs, new TxHeightComparator()); - - // collect queried vouts - List vouts = new ArrayList(); - for (MoneroTxWallet tx : txs) { - - // sort vouts - if (tx.getVouts() != null) Collections.sort(tx.getVouts(), new VoutComparator()); - - // collect queried vouts - List toRemoves = new ArrayList(); - for (MoneroOutput vout : tx.getVouts()) { - if (query.meetsCriteria((MoneroOutputWallet) vout)) vouts.add((MoneroOutputWallet) vout); - else toRemoves.add(vout); - } - - // remove excluded vouts from tx - tx.getVouts().removeAll(toRemoves); - - // remove excluded txs from block - if (tx.getVouts().isEmpty() && tx.getBlock() != null) tx.getBlock().getTxs().remove(tx); - } - return vouts; - } - - @SuppressWarnings("unchecked") - @Override - public String getOutputsHex() { - Map resp = rpc.sendJsonRequest("export_outputs"); - Map result = (Map) resp.get("result"); - return (String) result.get("outputs_data_hex"); - } - - @SuppressWarnings("unchecked") - @Override - public int importOutputsHex(String outputsHex) { - Map params = new HashMap(); - params.put("outputs_data_hex", outputsHex); - Map resp = rpc.sendJsonRequest("import_outputs", params); - Map result = (Map) resp.get("result"); - return ((BigInteger) result.get("num_imported")).intValue(); - } - - @Override - public List getKeyImages() { - return rpcExportKeyImages(true); - } - - @SuppressWarnings("unchecked") - @Override - public MoneroKeyImageImportResult importKeyImages(List keyImages) { - - // convert key images to rpc parameter - List> rpcKeyImages = new ArrayList>(); - for (MoneroKeyImage keyImage : keyImages) { - Map rpcKeyImage = new HashMap(); - rpcKeyImage.put("key_image", keyImage.getHex()); - rpcKeyImage.put("signature", keyImage.getSignature()); - rpcKeyImages.add(rpcKeyImage); - } - - // send rpc request - Map params = new HashMap(); - params.put("signed_key_images", rpcKeyImages); - Map resp = rpc.sendJsonRequest("import_key_images", params); - Map result = (Map) resp.get("result"); - - // build and return result - MoneroKeyImageImportResult importResult = new MoneroKeyImageImportResult(); - importResult.setHeight(((BigInteger) result.get("height")).longValue()); - importResult.setSpentAmount((BigInteger) result.get("spent")); - importResult.setUnspentAmount((BigInteger) result.get("unspent")); - return importResult; - } - - @Override - public List getNewKeyImagesFromLastImport() { - return rpcExportKeyImages(false); - } - - @SuppressWarnings("unchecked") - @Override - public List relayTxs(Collection txMetadatas) { - if (txMetadatas == null || txMetadatas.isEmpty()) throw new MoneroException("Must provide an array of tx metadata to relay"); - List txIds = new ArrayList(); - for (String txMetadata : txMetadatas) { - Map params = new HashMap(); - params.put("hex", txMetadata); - Map resp = rpc.sendJsonRequest("relay_tx", params); - Map result = (Map) resp.get("result"); - txIds.add((String) result.get("tx_hash")); - } - return txIds; - } - - @SuppressWarnings("unchecked") - public MoneroTxSet sendSplit(MoneroSendRequest request) { - - // validate, copy, and sanitize request - if (request == null) throw new MoneroException("Send request cannot be null"); - assertNotNull(request.getDestinations()); - assertNull(request.getSweepEachSubaddress()); - assertNull(request.getBelowAmount()); - if (request.getCanSplit() == null) { - request = request.copy(); - request.setCanSplit(true); - } - - // determine account and subaddresses to send from - Integer accountIdx = request.getAccountIndex(); - if (accountIdx == null) throw new MoneroException("Must specify the account index to send from"); - List subaddressIndices = request.getSubaddressIndices() == null ? null : new ArrayList(request.getSubaddressIndices()); // fetch all or copy given indices - - // build request parameters - Map params = new HashMap(); - List> destinationMaps = new ArrayList>(); - params.put("destinations", destinationMaps); - for (MoneroDestination destination : request.getDestinations()) { - assertNotNull("Destination address is not defined", destination.getAddress()); - assertNotNull("Destination amount is not defined", destination.getAmount()); - Map destinationMap = new HashMap(); - destinationMap.put("address", destination.getAddress()); - destinationMap.put("amount", destination.getAmount().toString()); - destinationMaps.add(destinationMap); - } - params.put("account_index", accountIdx); - params.put("subaddr_indices", subaddressIndices); - params.put("payment_id", request.getPaymentId()); - params.put("mixin", request.getMixin()); - params.put("ring_size", request.getRingSize()); - params.put("unlock_time", request.getUnlockTime()); - params.put("do_not_relay", request.getDoNotRelay()); - params.put("priority", request.getPriority() == null ? null : request.getPriority().ordinal()); - params.put("get_tx_hex", true); - params.put("get_tx_metadata", true); - if (request.getCanSplit()) params.put("get_tx_keys", true); // param to get tx key(s) depends if split - else params.put("get_tx_key", true); - - // send request - Map resp = rpc.sendJsonRequest(request.getCanSplit() ? "transfer_split" : "transfer", params); - Map result = (Map) resp.get("result"); - - // pre-initialize txs iff present. multisig and watch-only wallets will have tx set without transactions - List txs = null; - int numTxs = request.getCanSplit() ? (result.containsKey("fee_list") ? ((List) result.get("fee_list")).size() : 0) : (result.containsKey("fee") ? 1 : 0); - if (numTxs > 0) txs = new ArrayList(); - for (int i = 0; i < numTxs; i++) { - MoneroTxWallet tx = new MoneroTxWallet(); - initSentTxWallet(request, tx); - tx.getOutgoingTransfer().setAccountIndex(accountIdx); - if (subaddressIndices != null && subaddressIndices.size() == 1) tx.getOutgoingTransfer().setSubaddressIndices(subaddressIndices); - txs.add(tx); - } - - // initialize tx set from rpc response with pre-initialized txs - if (request.getCanSplit()) return convertRpcSentTxsToTxSet(result, txs); - else return convertRpcTxToTxSet(result, txs == null ? null : txs.get(0), true); - } - - @SuppressWarnings("unchecked") - @Override - public MoneroTxSet sweepOutput(MoneroSendRequest request) { - - // validate request - assertNull(request.getSweepEachSubaddress()); - assertNull(request.getBelowAmount()); - assertNull("Splitting is not applicable when sweeping output", request.getCanSplit()); - - // build request parameters - Map params = new HashMap(); - params.put("address", request.getDestinations().get(0).getAddress()); - params.put("account_index", request.getAccountIndex()); - params.put("subaddr_indices", request.getSubaddressIndices()); - params.put("key_image", request.getKeyImage()); - params.put("mixin", request.getMixin()); - params.put("ring_size", request.getRingSize()); - params.put("unlock_time", request.getUnlockTime()); - params.put("do_not_relay", request.getDoNotRelay()); - params.put("priority", request.getPriority() == null ? null : request.getPriority().ordinal()); - params.put("payment_id", request.getPaymentId()); - params.put("get_tx_key", true); - params.put("get_tx_hex", true); - params.put("get_tx_metadata", true); - - // send request - Map resp = (Map) rpc.sendJsonRequest("sweep_single", params); - Map result = (Map) resp.get("result"); - - // build and return tx response - MoneroTxWallet tx = initSentTxWallet(request, null); - MoneroTxSet txSet = convertRpcTxToTxSet(result, tx, true); - tx.getOutgoingTransfer().getDestinations().get(0).setAmount(tx.getOutgoingTransfer().getAmount()); // initialize destination amount - return txSet; - } - - @Override - public List sweepUnlocked(MoneroSendRequest request) { - - // validate request - if (request == null) throw new MoneroException("Sweep request cannot be null"); - if (request.getDestinations() == null || request.getDestinations().size() != 1) throw new MoneroException("Must specify exactly one destination to sweep to"); - if (request.getDestinations().get(0).getAddress() == null) throw new MoneroException("Must specify destination address to sweep to"); - if (request.getDestinations().get(0).getAmount() != null) throw new MoneroException("Cannot specify amount in sweep request"); - if (request.getKeyImage() != null) throw new MoneroException("Key image defined; use sweepOutput() to sweep an output by its key image"); - if (request.getSubaddressIndices() != null && request.getSubaddressIndices().isEmpty()) request.setSubaddressIndices((List) null); - if (request.getAccountIndex() == null && request.getSubaddressIndices() != null) throw new MoneroException("Must specify account index if subaddress indices are specified"); - - // determine account and subaddress indices to sweep; default to all with unlocked balance if not specified - LinkedHashMap> indices = new LinkedHashMap>(); // java type preserves insertion order - if (request.getAccountIndex() != null) { - if (request.getSubaddressIndices() != null) { - indices.put(request.getAccountIndex(), request.getSubaddressIndices()); - } else { - List subaddressIndices = new ArrayList(); - indices.put(request.getAccountIndex(), subaddressIndices); - for (MoneroSubaddress subaddress : getSubaddresses(request.getAccountIndex())) { - if (subaddress.getUnlockedBalance().compareTo(BigInteger.valueOf(0)) > 0) subaddressIndices.add(subaddress.getIndex()); - } - } - } else { - List accounts = getAccounts(true); - for (MoneroAccount account : accounts) { - if (account.getUnlockedBalance().compareTo(BigInteger.valueOf(0)) > 0) { - List subaddressIndices = new ArrayList(); - indices.put(account.getIndex(), subaddressIndices); - for (MoneroSubaddress subaddress : account.getSubaddresses()) { - if (subaddress.getUnlockedBalance().compareTo(BigInteger.valueOf(0)) > 0) subaddressIndices.add(subaddress.getIndex()); - } - } - } - } - - // sweep from each account and collect resulting tx sets - List txSets = new ArrayList(); - for (Integer accountIdx : indices.keySet()) { - - // copy and modify the original request - MoneroSendRequest copy = request.copy(); - copy.setAccountIndex(accountIdx); - copy.setSweepEachSubaddress(false); - - // sweep all subaddresses together // TODO monero core: can this reveal outputs belong to the same wallet? - if (!Boolean.TRUE.equals(copy.getSweepEachSubaddress())) { - copy.setSubaddressIndices(indices.get(accountIdx)); - txSets.add(rpcSweepAccount(copy)); - } - - // otherwise sweep each subaddress individually - else { - for (int subaddressIdx : indices.get(accountIdx)) { - copy.setSubaddressIndices(subaddressIdx); - txSets.add(rpcSweepAccount(copy)); - } - } - } - - // return resulting tx sets - return txSets; - } - - @SuppressWarnings("unchecked") - @Override - public MoneroTxSet sweepDust(boolean doNotRelay) { - Map params = new HashMap(); - params.put("do_not_relay", doNotRelay); - Map resp = rpc.sendJsonRequest("sweep_dust", params); - Map result = (Map) resp.get("result"); - MoneroTxSet txSet = convertRpcSentTxsToTxSet(result, null); - if (txSet.getTxs() != null) { - for (MoneroTxWallet tx : txSet.getTxs()) { - tx.setIsRelayed(!doNotRelay); - tx.setInTxPool(tx.isRelayed()); - } - } else if (txSet.getMultisigTxHex() == null && txSet.getSignedTxHex() == null && txSet.getUnsignedTxHex() == null) { - throw new MoneroException("No dust to sweep"); - } - return txSet; - } - - @SuppressWarnings("unchecked") - @Override - public String sign(String msg) { - Map params = new HashMap(); - params.put("data", msg); - Map resp = rpc.sendJsonRequest("sign", params); - Map result = (Map) resp.get("result"); - return (String) result.get("signature"); - } - - @SuppressWarnings("unchecked") - @Override - public boolean verify(String msg, String address, String signature) { - Map params = new HashMap(); - params.put("data", msg); - params.put("address", address); - params.put("signature", signature); - Map resp = rpc.sendJsonRequest("verify", params); - Map result = (Map) resp.get("result"); - return (boolean) result.get("good"); - } - - @SuppressWarnings("unchecked") - @Override - public String getTxKey(String txId) { - Map params = new HashMap(); - params.put("txid", txId); - Map resp = rpc.sendJsonRequest("get_tx_key", params); - Map result = (Map) resp.get("result"); - return (String) result.get("tx_key"); - } - - @SuppressWarnings("unchecked") - @Override - public MoneroCheckTx checkTxKey(String txId, String txKey, String address) { - - // send request - Map params = new HashMap(); - params.put("txid", txId); - params.put("tx_key", txKey); - params.put("address", address); - Map resp = rpc.sendJsonRequest("check_tx_key", params); - - // interpret result - Map result = (Map) resp.get("result"); - MoneroCheckTx check = new MoneroCheckTx(); - check.setIsGood(true); - check.setNumConfirmations(((BigInteger) result.get("confirmations")).longValue()); - check.setInTxPool((Boolean) result.get("in_pool")); - check.setReceivedAmount((BigInteger) result.get("received")); - return check; - } - - @SuppressWarnings("unchecked") - @Override - public String getTxProof(String txId, String address, String message) { - Map params = new HashMap(); - params.put("txid", txId); - params.put("address", address); - params.put("message", message); - Map resp = rpc.sendJsonRequest("get_tx_proof", params); - Map result = (Map) resp.get("result"); - return (String) result.get("signature"); - } - - @SuppressWarnings("unchecked") - @Override - public MoneroCheckTx checkTxProof(String txId, String address, String message, String signature) { - - // send request - Map params = new HashMap(); - params.put("txid", txId); - params.put("address", address); - params.put("message", message); - params.put("signature", signature); - Map resp = rpc.sendJsonRequest("check_tx_proof", params); - - // interpret response - Map result = (Map) resp.get("result"); - boolean isGood = (boolean) result.get("good"); - MoneroCheckTx check = new MoneroCheckTx(); - check.setIsGood(isGood); - if (isGood) { - check.setNumConfirmations(((BigInteger) result.get("confirmations")).longValue()); - check.setInTxPool((boolean) result.get("in_pool")); - check.setReceivedAmount((BigInteger) result.get("received")); - } - return check; - } - - @SuppressWarnings("unchecked") - @Override - public String getSpendProof(String txId, String message) { - Map params = new HashMap(); - params.put("txid", txId); - params.put("message", message); - Map resp = rpc.sendJsonRequest("get_spend_proof", params); - Map result = (Map) resp.get("result"); - return (String) result.get("signature"); - } - - @SuppressWarnings("unchecked") - @Override - public boolean checkSpendProof(String txId, String message, String signature) { - Map params = new HashMap(); - params.put("txid", txId); - params.put("message", message); - params.put("signature", signature); - Map resp = rpc.sendJsonRequest("check_spend_proof", params); - Map result = (Map) resp.get("result"); - return (boolean) result.get("good"); - } - - @SuppressWarnings("unchecked") - @Override - public String getReserveProofWallet(String message) { - Map params = new HashMap(); - params.put("all", true); - params.put("message", message); - Map resp = rpc.sendJsonRequest("get_reserve_proof", params); - Map result = (Map) resp.get("result"); - return (String) result.get("signature"); - } - - @SuppressWarnings("unchecked") - @Override - public String getReserveProofAccount(int accountIdx, BigInteger amount, String message) { - Map params = new HashMap(); - params.put("account_index", accountIdx); - params.put("amount", amount.toString()); - params.put("message", message); - Map resp = rpc.sendJsonRequest("get_reserve_proof", params); - Map result = (Map) resp.get("result"); - return (String) result.get("signature"); - } - - @SuppressWarnings("unchecked") - @Override - public MoneroCheckReserve checkReserveProof(String address, String message, String signature) { - - // send request - Map params = new HashMap(); - params.put("address", address); - params.put("message", message); - params.put("signature", signature); - Map resp = rpc.sendJsonRequest("check_reserve_proof", params); - Map result = (Map) resp.get("result"); - - // interpret results - boolean isGood = (boolean) result.get("good"); - MoneroCheckReserve check = new MoneroCheckReserve(); - check.setIsGood(isGood); - if (isGood) { - check.setTotalAmount((BigInteger) result.get("total")); - check.setUnconfirmedSpentAmount((BigInteger) result.get("spent")); - } - return check; - } - - @SuppressWarnings("unchecked") - @Override - public List getTxNotes(Collection txIds) { - Map params = new HashMap(); - params.put("txids", txIds); - Map resp = rpc.sendJsonRequest("get_tx_notes", params); - Map result = (Map) resp.get("result"); - return (List) result.get("notes"); - } - - @Override - public void setTxNotes(Collection txIds, Collection notes) { - Map params = new HashMap(); - params.put("txids", txIds); - params.put("notes", notes); - rpc.sendJsonRequest("set_tx_notes", params); - } - - @SuppressWarnings("unchecked") - @Override - public List getAddressBookEntries(Collection entryIndices) { - Map params = new HashMap(); - params.put("entries", entryIndices); - Map respMap = rpc.sendJsonRequest("get_address_book", params); - Map resultMap = (Map) respMap.get("result"); - List entries = new ArrayList(); - if (!resultMap.containsKey("entries")) return entries; - for (Map entryMap : (List>) resultMap.get("entries")) { - MoneroAddressBookEntry entry = new MoneroAddressBookEntry( - ((BigInteger) entryMap.get("index")).intValue(), - (String) entryMap.get("address"), - (String) entryMap.get("payment_id"), - (String) entryMap.get("description") - ); - entries.add(entry); - } - return entries; - } - - @SuppressWarnings("unchecked") - @Override - public int addAddressBookEntry(String address, String description, String paymentId) { - Map params = new HashMap(); - params.put("address", address); - params.put("payment_id", paymentId); - params.put("description", description); - Map respMap = rpc.sendJsonRequest("add_address_book", params); - Map resultMap = (Map) respMap.get("result"); - return ((BigInteger) resultMap.get("index")).intValue(); - } - - @Override - public void deleteAddressBookEntry(int entryIdx) { - Map params = new HashMap(); - params.put("index", entryIdx); - rpc.sendJsonRequest("delete_address_book", params); - } - - @Override - public void tagAccounts(String tag, Collection accountIndices) { - Map params = new HashMap(); - params.put("tag", tag); - params.put("accounts", accountIndices); - rpc.sendJsonRequest("tag_accounts", params); - } - - @Override - public void untagAccounts(Collection accountIndices) { - Map params = new HashMap(); - params.put("accounts", accountIndices); - rpc.sendJsonRequest("untag_accounts", params); - } - - @SuppressWarnings("unchecked") - @Override - public List getAccountTags() { - List tags = new ArrayList(); - Map respMap = rpc.sendJsonRequest("get_account_tags"); - Map resultMap = (Map) respMap.get("result"); - List> accountTagMaps = (List>) resultMap.get("account_tags"); - if (accountTagMaps != null) { - for (Map accountTagMap : accountTagMaps) { - MoneroAccountTag tag = new MoneroAccountTag(); - tags.add(tag); - tag.setTag((String) accountTagMap.get("tag")); - tag.setLabel((String) accountTagMap.get("label")); - List accountIndicesBI = (List) accountTagMap.get("accounts"); - List accountIndices = new ArrayList(); - for (BigInteger idx : accountIndicesBI) accountIndices.add(idx.intValue()); - tag.setAccountIndices(accountIndices); - } - } - return tags; - } - - @Override - public void setAccountTagLabel(String tag, String label) { - Map params = new HashMap(); - params.put("tag", tag); - params.put("description", label); - rpc.sendJsonRequest("set_account_tag_description", params); - } - - @SuppressWarnings("unchecked") - @Override - public String createPaymentUri(MoneroSendRequest request) { - assertNotNull("Must provide send request to create a payment URI", request); - Map params = new HashMap(); - params.put("address", request.getDestinations().get(0).getAddress()); - params.put("amount", request.getDestinations().get(0).getAmount() != null ? request.getDestinations().get(0).getAmount().toString() : null); - params.put("payment_id", request.getPaymentId()); - params.put("recipient_name", request.getRecipientName()); - params.put("tx_description", request.getNote()); - Map resp = rpc.sendJsonRequest("make_uri", params); - Map result = (Map) resp.get("result"); - return (String) result.get("uri"); - } - - @SuppressWarnings("unchecked") - @Override - public MoneroSendRequest parsePaymentUri(String uri) { - assertNotNull("Must provide URI to parse", uri); - Map params = new HashMap(); - params.put("uri", uri); - Map resp = rpc.sendJsonRequest("parse_uri", params); - Map result = (Map) resp.get("result"); - Map rpcUri = (Map) result.get("uri"); - MoneroSendRequest request = new MoneroSendRequest((String) rpcUri.get("address"), (BigInteger) rpcUri.get("amount")); - request.setPaymentId((String) rpcUri.get("payment_id")); - request.setRecipientName((String) rpcUri.get("recipient_name")); - request.setNote((String) rpcUri.get("tx_description")); - if ("".equals(request.getDestinations().get(0).getAddress())) request.getDestinations().get(0).setAddress(null); - if ("".equals(request.getPaymentId())) request.setPaymentId(null); - if ("".equals(request.getRecipientName())) request.setRecipientName(null); - if ("".equals(request.getNote())) request.setNote(null); - return request; - } - - @SuppressWarnings("unchecked") - @Override - public String getAttribute(String key) { - Map params = new HashMap(); - params.put("key", key); - Map resp = rpc.sendJsonRequest("get_attribute", params); - Map result = (Map) resp.get("result"); - String value = (String) result.get("value"); - return value.isEmpty() ? null : value; - } - - @Override - public void setAttribute(String key, String val) { - Map params = new HashMap(); - params.put("key", key); - params.put("value", val); - rpc.sendJsonRequest("set_attribute", params); - } - - @Override - public void startMining(Long numThreads, Boolean backgroundMining, Boolean ignoreBattery) { - Map params = new HashMap(); - params.put("threads_count", numThreads); - params.put("backgroundMining", backgroundMining); - params.put("ignoreBattery", ignoreBattery); - rpc.sendJsonRequest("start_mining", params); - } - - @Override - public void stopMining() { - rpc.sendJsonRequest("stop_mining"); - } - - @SuppressWarnings("unchecked") - @Override - public boolean isMultisigImportNeeded() { - Map resp = rpc.sendJsonRequest("get_balance"); - Map result = (Map) resp.get("result"); - return Boolean.TRUE.equals((Boolean) result.get("multisig_import_needed")); - } - - @Override - @SuppressWarnings("unchecked") - public MoneroMultisigInfo getMultisigInfo() { - Map resp = rpc.sendJsonRequest("is_multisig"); - Map result = (Map) resp.get("result"); - MoneroMultisigInfo info = new MoneroMultisigInfo(); - info.setIsMultisig((boolean) result.get("multisig")); - info.setIsReady((boolean) result.get("ready")); - info.setThreshold(((BigInteger) result.get("threshold")).intValue()); - info.setNumParticipants(((BigInteger) result.get("total")).intValue()); - return info; - } - - @Override - @SuppressWarnings("unchecked") - public String prepareMultisig() { - Map resp = rpc.sendJsonRequest("prepare_multisig"); - Map result = (Map) resp.get("result"); - return (String) result.get("multisig_info"); - } - - @Override - @SuppressWarnings("unchecked") - public MoneroMultisigInitResult makeMultisig(List multisigHexes, int threshold, String password) { - Map params = new HashMap(); - params.put("multisig_info", multisigHexes); - params.put("threshold", threshold); - params.put("password", password); - Map resp = rpc.sendJsonRequest("make_multisig", params); - Map result = (Map) resp.get("result"); - MoneroMultisigInitResult msResult = new MoneroMultisigInitResult(); - msResult.setAddress((String) result.get("address")); - msResult.setMultisigHex((String) result.get("multisig_info")); - if (msResult.getAddress().isEmpty()) msResult.setAddress(null); - if (msResult.getMultisigHex().isEmpty()) msResult.setMultisigHex(null); - return msResult; - } - - @Override - @SuppressWarnings("unchecked") - public MoneroMultisigInitResult exchangeMultisigKeys(List multisigHexes, String password) { - Map params = new HashMap(); - params.put("multisig_info", multisigHexes); - params.put("password", password); - Map resp = rpc.sendJsonRequest("exchange_multisig_keys", params); - Map result = (Map) resp.get("result"); - MoneroMultisigInitResult msResult = new MoneroMultisigInitResult(); - msResult.setAddress((String) result.get("address")); - msResult.setMultisigHex((String) result.get("multisig_info")); - if (msResult.getAddress().isEmpty()) msResult.setAddress(null); - if (msResult.getMultisigHex().isEmpty()) msResult.setMultisigHex(null); - return msResult; - } - - @Override - @SuppressWarnings("unchecked") - public String getMultisigHex() { - Map resp = rpc.sendJsonRequest("export_multisig_info"); - Map result = (Map) resp.get("result"); - return (String) result.get("info"); - } - - @Override - @SuppressWarnings("unchecked") - public int importMultisigHex(List multisigHexes) { - Map params = new HashMap(); - params.put("info", multisigHexes); - Map resp = rpc.sendJsonRequest("import_multisig_info", params); - Map result = (Map) resp.get("result"); - return ((BigInteger) result.get("n_outputs")).intValue(); - } - - @Override - @SuppressWarnings("unchecked") - public MoneroMultisigSignResult signMultisigTxHex(String multisigTxHex) { - Map params = new HashMap(); - params.put("tx_data_hex", multisigTxHex); - Map resp = rpc.sendJsonRequest("sign_multisig", params); - Map result = (Map) resp.get("result"); - MoneroMultisigSignResult signResult = new MoneroMultisigSignResult(); - signResult.setSignedMultisigTxHex((String) result.get("tx_data_hex")); - signResult.setTxIds((List) result.get("tx_hash_list")); - return signResult; - } - - @Override - @SuppressWarnings("unchecked") - public List submitMultisigTxHex(String signedMultisigTxHex) { - Map params = new HashMap(); - params.put("tx_data_hex", signedMultisigTxHex); - Map resp = rpc.sendJsonRequest("submit_multisig", params); - Map result = (Map) resp.get("result"); - return (List) result.get("tx_hash_list"); - } - - @Override - public void save() { - rpc.sendJsonRequest("store"); - } - - @Override - public void close(boolean save) { - addressCache.clear(); - path = null; - Map params = new HashMap(); - params.put("autosave_current", save); - rpc.sendJsonRequest("close_wallet", params); - } - - // ------------------------------ PRIVATE ----------------------------------- - - private Map> getAccountIndices(boolean getSubaddressIndices) { - Map> indices = new HashMap>(); - for (MoneroAccount account : getAccounts()) { - indices.put(account.getIndex(), getSubaddressIndices ? getSubaddressIndices(account.getIndex()) : null); - } - return indices; - } - - @SuppressWarnings("unchecked") - private List getSubaddressIndices(int accountIdx) { - List subaddressIndices = new ArrayList(); - Map params = new HashMap(); - params.put("account_index", accountIdx); - Map resp = rpc.sendJsonRequest("get_address", params); - Map result = (Map) resp.get("result"); - for (Map address : (List>) result.get("addresses")) { - subaddressIndices.add(((BigInteger) address.get("address_index")).intValue()); - } - return subaddressIndices; - } - - /** - * Common method to get key images. - * - * @param all specifies to get all xor only new images from last import - * @return {MoneroKeyImage[]} are the key images - */ - @SuppressWarnings("unchecked") - private List rpcExportKeyImages(boolean all) { - Map params = new HashMap(); - params.put("all", all); - Map resp = rpc.sendJsonRequest("export_key_images", params); - Map result = (Map) resp.get("result"); - List images = new ArrayList(); - if (!result.containsKey("signed_key_images")) return images; - for (Map rpcImage : (List>) result.get("signed_key_images")) { - images.add(new MoneroKeyImage((String) rpcImage.get("key_image"), (String) rpcImage.get("signature"))); - } - return images; - } - - @SuppressWarnings("unchecked") - private BigInteger[] getBalances(Integer accountIdx, Integer subaddressIdx) { - if (accountIdx == null) { - assertNull("Must provide account index with subaddress index", subaddressIdx); - BigInteger balance = BigInteger.valueOf(0); - BigInteger unlockedBalance = BigInteger.valueOf(0); - for (MoneroAccount account : getAccounts()) { - balance = balance.add(account.getBalance()); - unlockedBalance = unlockedBalance.add(account.getUnlockedBalance()); - } - return new BigInteger[] { balance, unlockedBalance }; - } else { - Map params = new HashMap(); - params.put("account_index", accountIdx); - params.put("address_indices", subaddressIdx == null ? null : new Integer[] { subaddressIdx }); - Map resp = rpc.sendJsonRequest("get_balance", params); - Map result = (Map) resp.get("result"); - if (subaddressIdx == null) return new BigInteger[] { (BigInteger) result.get("balance"), (BigInteger) result.get("unlocked_balance") }; - else { - List> rpcBalancesPerSubaddress = (List>) result.get("per_subaddress"); - return new BigInteger[] { (BigInteger) rpcBalancesPerSubaddress.get(0).get("balance"), (BigInteger) rpcBalancesPerSubaddress.get(0).get("unlocked_balance") }; - } - } - } - - @SuppressWarnings("unchecked") - private MoneroTxSet rpcSweepAccount(MoneroSendRequest request) { - - // validate request - if (request == null) throw new MoneroException("Sweep request cannot be null"); - if (request.getAccountIndex() == null) throw new MoneroException("Must specify an account index to sweep from"); - if (request.getDestinations() == null || request.getDestinations().size() != 1) throw new MoneroException("Must specify exactly one destination to sweep to"); - if (request.getDestinations().get(0).getAddress() == null) throw new MoneroException("Must specify destination address to sweep to"); - if (request.getDestinations().get(0).getAmount() != null) throw new MoneroException("Cannot specify amount in sweep request"); - if (request.getKeyImage() != null) throw new MoneroException("Key image defined; use sweepOutput() to sweep an output by its key image"); - if (request.getSubaddressIndices() != null && request.getSubaddressIndices().isEmpty()) request.setSubaddressIndices((List) null); - if (Boolean.TRUE.equals(request.getSweepEachSubaddress())) throw new MoneroException("Cannot sweep each subaddress with RPC `sweep_all`"); - - // sweep from all subaddresses if not otherwise defined - if (request.getSubaddressIndices() == null) { - request.setSubaddressIndices(new ArrayList()); - for (MoneroSubaddress subaddress : getSubaddresses(request.getAccountIndex())) { - request.getSubaddressIndices().add(subaddress.getIndex()); - } - } - if (request.getSubaddressIndices().size() == 0) throw new MoneroException("No subaddresses to sweep from"); - - // common request params - boolean doNotRelay = request.getDoNotRelay() != null && request.getDoNotRelay(); - Map params = new HashMap(); - params.put("account_index", request.getAccountIndex()); - params.put("subaddr_indices", request.getSubaddressIndices()); - params.put("address", request.getDestinations().get(0).getAddress()); - params.put("priority", request.getPriority() == null ? null : request.getPriority().ordinal()); - params.put("mixin", request.getMixin()); - params.put("ring_size", request.getRingSize()); - params.put("unlock_time", request.getUnlockTime()); - params.put("payment_id", request.getPaymentId()); - params.put("do_not_relay", doNotRelay); - params.put("below_amount", request.getBelowAmount()); - params.put("get_tx_keys", true); - params.put("get_tx_hex", true); - params.put("get_tx_metadata", true); - - // invoke wallet rpc `sweep_all` - Map resp = rpc.sendJsonRequest("sweep_all", params); - Map result = (Map) resp.get("result"); - - // initialize txs from response - MoneroTxSet txSet = convertRpcSentTxsToTxSet(result, null); - - // initialize remaining known fields - for (MoneroTxWallet tx : txSet.getTxs()) { - tx.setIsConfirmed(false); - tx.setNumConfirmations(0l); - tx.setDoNotRelay(doNotRelay); - tx.setInTxPool(!doNotRelay); - tx.setIsRelayed(!doNotRelay); - tx.setIsMinerTx(false); - tx.setIsFailed(false); - tx.setMixin(request.getMixin()); - MoneroOutgoingTransfer transfer = tx.getOutgoingTransfer(); - transfer.setAccountIndex(request.getAccountIndex()); - if (request.getSubaddressIndices().size() == 1) transfer.setSubaddressIndices(new ArrayList(request.getSubaddressIndices())); - MoneroDestination destination = new MoneroDestination(request.getDestinations().get(0).getAddress(), transfer.getAmount()); - transfer.setDestinations(Arrays.asList(destination)); - tx.setOutgoingTransfer(transfer); - tx.setPaymentId(request.getPaymentId()); - if (tx.getUnlockTime() == null) tx.setUnlockTime(request.getUnlockTime() == null ? 0l : request.getUnlockTime()); - if (!tx.getDoNotRelay()) { - if (tx.getLastRelayedTimestamp() == null) tx.setLastRelayedTimestamp(System.currentTimeMillis()); // TODO (monero-wallet-rpc): provide timestamp on response; unconfirmed timestamps vary - if (tx.isDoubleSpendSeen() == null) tx.setIsDoubleSpendSeen(false); - } - } - return txSet; - } - - // ---------------------------- PRIVATE STATIC ------------------------------ - - private static MoneroAccount convertRpcAccount(Map rpcAccount) { - MoneroAccount account = new MoneroAccount(); - for (String key : rpcAccount.keySet()) { - Object val = rpcAccount.get(key); - if (key.equals("account_index")) account.setIndex(((BigInteger) val).intValue()); - else if (key.equals("balance")) account.setBalance((BigInteger) val); - else if (key.equals("unlocked_balance")) account.setUnlockedBalance((BigInteger) val); - else if (key.equals("base_address")) account.setPrimaryAddress((String) val); - else if (key.equals("tag")) account.setTag((String) val); - else if (key.equals("label")) { } // label belongs to first subaddress - else LOGGER.warn("WARNING: ignoring unexpected account field: " + key + ": " + val); - } - if ("".equals(account.getTag())) account.setTag(null); - return account; - } - - private static MoneroSubaddress convertRpcSubaddress(Map rpcSubaddress) { - MoneroSubaddress subaddress = new MoneroSubaddress(); - for (String key : rpcSubaddress.keySet()) { - Object val = rpcSubaddress.get(key); - if (key.equals("account_index")) subaddress.setAccountIndex(((BigInteger) val).intValue()); - else if (key.equals("address_index")) subaddress.setIndex(((BigInteger) val).intValue()); - else if (key.equals("address")) subaddress.setAddress((String) val); - else if (key.equals("balance")) subaddress.setBalance((BigInteger) val); - else if (key.equals("unlocked_balance")) subaddress.setUnlockedBalance((BigInteger) val); - else if (key.equals("num_unspent_outputs")) subaddress.setNumUnspentOutputs(((BigInteger) val).longValue()); - else if (key.equals("label")) { if (!"".equals(val)) subaddress.setLabel((String) val); } - else if (key.equals("used")) subaddress.setIsUsed((Boolean) val); - else if (key.equals("blocks_to_unlock")) subaddress.setNumBlocksToUnlock(((BigInteger) val).longValue()); - else LOGGER.warn("WARNING: ignoring unexpected subaddress field: " + key + ": " + val); - } - return subaddress; - } - - /** - * Initializes a sent transaction. - * - * @param request is the send configuration - * @param tx is an existing transaction to initialize (optional) - * @return tx is the initialized send tx - */ - private static MoneroTxWallet initSentTxWallet(MoneroSendRequest request, MoneroTxWallet tx) { - if (tx == null) tx = new MoneroTxWallet(); - tx.setIsConfirmed(false); - tx.setNumConfirmations(0l); - tx.setInTxPool(Boolean.TRUE.equals(request.getDoNotRelay()) ? false : true); - tx.setDoNotRelay(Boolean.TRUE.equals(request.getDoNotRelay()) ? true : false); - tx.setIsRelayed(!Boolean.TRUE.equals(tx.getDoNotRelay())); - tx.setIsMinerTx(false); - tx.setIsFailed(false); - tx.setMixin(request.getMixin()); - MoneroOutgoingTransfer transfer = new MoneroOutgoingTransfer().setTx(tx); - if (request.getSubaddressIndices() != null && request.getSubaddressIndices().size() == 1) transfer.setSubaddressIndices(new ArrayList(request.getSubaddressIndices())); // we know src subaddress indices iff request specifies 1 - List destCopies = new ArrayList(); - for (MoneroDestination dest : request.getDestinations()) destCopies.add(dest.copy()); - transfer.setDestinations(destCopies); - tx.setOutgoingTransfer(transfer); - tx.setPaymentId(request.getPaymentId()); - if (tx.getUnlockTime() == null) tx.setUnlockTime(request.getUnlockTime() == null ? 0l : request.getUnlockTime()); - if (!Boolean.TRUE.equals(tx.getDoNotRelay())) { - if (tx.getLastRelayedTimestamp() == null) tx.setLastRelayedTimestamp(System.currentTimeMillis()); // TODO (monero-wallet-rpc): provide timestamp on response; unconfirmed timestamps vary - if (tx.isDoubleSpendSeen() == null) tx.setIsDoubleSpendSeen(false); - } - return tx; - } - - /** - * Initializes a tx set from a RPC map excluding txs. - * - * @param rpcMap is the map to initialize the tx set from - * @return MoneroTxSet is the initialized tx set - */ - private static MoneroTxSet convertRpcMapToTxSet(Map rpcMap) { - MoneroTxSet txSet = new MoneroTxSet(); - txSet.setMultisigTxHex((String) rpcMap.get("multisig_txset")); - txSet.setUnsignedTxHex((String) rpcMap.get("unsigned_txset")); - txSet.setSignedTxHex((String) rpcMap.get("signed_txset")); - if (txSet.getMultisigTxHex() != null && txSet.getMultisigTxHex().isEmpty()) txSet.setMultisigTxHex(null); - if (txSet.getUnsignedTxHex() != null && txSet.getUnsignedTxHex().isEmpty()) txSet.setUnsignedTxHex(null); - if (txSet.getSignedTxHex() != null && txSet.getSignedTxHex().isEmpty()) txSet.setSignedTxHex(null); - return txSet; - } - - /** - * Initializes a MoneroTxSet from from a list of rpc txs. - * - * @param rpcTxs are sent rpc txs to initialize the set from - * @param txs are existing txs to further initialize (optional) - * @return the converted tx set - */ - @SuppressWarnings("unchecked") - private static MoneroTxSet convertRpcSentTxsToTxSet(Map rpcTxs, List txs) { - - // build shared tx set - MoneroTxSet txSet = convertRpcMapToTxSet(rpcTxs); - - // done if rpc contains no txs - if (!rpcTxs.containsKey("fee_list")) { - assertNull(txs); - return txSet; - } - - // get lists - List ids = (List) rpcTxs.get("tx_hash_list"); - List keys = (List) rpcTxs.get("tx_key_list"); - List blobs = (List) rpcTxs.get("tx_blob_list"); - List metadatas = (List) rpcTxs.get("tx_metadata_list"); - List fees = (List) rpcTxs.get("fee_list"); - List amounts = (List) rpcTxs.get("amount_list"); - - // ensure all lists are the same size - Set sizes = new HashSet(); - if (amounts != null) sizes.add(amounts.size()); - if (ids != null) sizes.add(ids.size()); - if (keys != null) sizes.add(keys.size()); - if (blobs != null) sizes.add(blobs.size()); - if (metadatas != null) sizes.add(metadatas.size()); - if (fees != null) sizes.add(fees.size()); - if (amounts != null) sizes.add(amounts.size()); - assertEquals("RPC lists are different sizes", 1, sizes.size()); - - // pre-initialize txs if none given - if (txs != null) txSet.setTxs(txs); - else { - txs = new ArrayList(); - for (int i = 0; i < fees.size(); i++) txs.add(new MoneroTxWallet()); - txSet.setTxs(txs); - } - - // build transactions - for (int i = 0; i < fees.size(); i++) { - MoneroTxWallet tx = txs.get(i); - if (ids != null) tx.setId(ids.get(i)); - if (keys != null) tx.setKey(keys.get(i)); - if (blobs != null) tx.setFullHex(blobs.get(i)); - if (metadatas != null) tx.setMetadata(metadatas.get(i)); - tx.setFee((BigInteger) fees.get(i)); - if (tx.getOutgoingTransfer() != null) tx.getOutgoingTransfer().setAmount((BigInteger) amounts.get(i)); - else tx.setOutgoingTransfer(new MoneroOutgoingTransfer().setTx(tx).setAmount((BigInteger) amounts.get(i))); - tx.setTxSet(txSet); // link tx to parent set - } - - return txSet; - } - - /** - * Converts a rpc tx with a transfer to a tx set with a tx and transfer. - * - * @param rpcTx is the rpc tx to build from - * @param tx is an existing tx to continue initializing (optional) - * @param isOutgoing specifies if the tx is outgoing if true, incoming if false, or decodes from type if undefined - * @returns the initialized tx set with a tx - */ - private static MoneroTxSet convertRpcTxToTxSet(Map rpcTx, MoneroTxWallet tx, Boolean isOutgoing) { - MoneroTxSet txSet = convertRpcMapToTxSet(rpcTx); - txSet.setTxs(Arrays.asList(convertRpcTxWithTransfer(rpcTx, tx, isOutgoing).setTxSet(txSet))); - return txSet; - } - - /** - * Builds a MoneroTxWallet from a RPC tx. - * - * @param rpcTx is the rpc tx to build from - * @param tx is an existing tx to continue initializing (optional) - * @param isOutgoing specifies if the tx is outgoing if true, incoming if false, or decodes from type if undefined - * @returns the initialized tx with a transfer - */ - @SuppressWarnings("unchecked") - private static MoneroTxWallet convertRpcTxWithTransfer(Map rpcTx, MoneroTxWallet tx, Boolean isOutgoing) { // TODO: change everything to safe set - - // initialize tx to return - if (tx == null) tx = new MoneroTxWallet(); - - // initialize tx state from rpc type - if (rpcTx.containsKey("type")) isOutgoing = decodeRpcType((String) rpcTx.get("type"), tx); - else { - assertNotNull("Must indicate if tx is outgoing (true) xor incoming (false) since unknown", isOutgoing); - assertNotNull(tx.isConfirmed()); - assertNotNull(tx.inTxPool()); - assertNotNull(tx.isMinerTx()); - assertNotNull(tx.isFailed()); - assertNotNull(tx.getDoNotRelay()); - } - - // TODO: safe set - // initialize remaining fields TODO: seems this should be part of common function with DaemonRpc._convertRpcTx - MoneroBlockHeader header = null; - MoneroTransfer transfer = null; - for (String key : rpcTx.keySet()) { - Object val = rpcTx.get(key); - if (key.equals("txid")) tx.setId((String) val); - else if (key.equals("tx_hash")) tx.setId((String) val); - else if (key.equals("fee")) tx.setFee((BigInteger) val); - else if (key.equals("note")) { if (!"".equals(val)) tx.setNote((String) val); } - else if (key.equals("tx_key")) tx.setKey((String) val); - else if (key.equals("type")) { } // type already handled - else if (key.equals("tx_size")) tx.setSize(((BigInteger) val).longValue()); - else if (key.equals("unlock_time")) tx.setUnlockTime(((BigInteger) val).longValue()); - else if (key.equals("tx_blob")) tx.setFullHex((String) val); - else if (key.equals("tx_metadata")) tx.setMetadata((String) val); - else if (key.equals("double_spend_seen")) tx.setIsDoubleSpendSeen((Boolean) val); - else if (key.equals("block_height") || key.equals("height")) { - if (tx.isConfirmed()) { - if (header == null) header = new MoneroBlockHeader(); - header.setHeight(((BigInteger) val).longValue()); - } - } - else if (key.equals("timestamp")) { - if (tx.isConfirmed()) { - if (header == null) header = new MoneroBlockHeader(); - header.setTimestamp(((BigInteger) val).longValue()); - } else { - // timestamp of unconfirmed tx is current request time - } - } - else if (key.equals("confirmations")) { - if (!tx.isConfirmed()) tx.setNumConfirmations(0l); - else tx.setNumConfirmations(((BigInteger) val).longValue()); - } - else if (key.equals("suggested_confirmations_threshold")) { - if (transfer == null) transfer = (isOutgoing ? new MoneroOutgoingTransfer() : new MoneroIncomingTransfer()).setTx(tx); - transfer.setNumSuggestedConfirmations(((BigInteger) val).longValue()); - } - else if (key.equals("amount")) { - if (transfer == null) transfer = (isOutgoing ? new MoneroOutgoingTransfer() : new MoneroIncomingTransfer()).setTx(tx); - transfer.setAmount((BigInteger) val); - } - else if (key.equals("address")) { - if (!isOutgoing) { - if (transfer == null) transfer = new MoneroIncomingTransfer().setTx(tx); - ((MoneroIncomingTransfer) transfer).setAddress((String) val); - } - } - else if (key.equals("payment_id")) { - if (!MoneroTxWallet.DEFAULT_PAYMENT_ID.equals(val)) tx.setPaymentId((String) val); // default is undefined - } - else if (key.equals("subaddr_index")) assertTrue(rpcTx.containsKey("subaddr_indices")); // handled by subaddr_indices - else if (key.equals("subaddr_indices")) { - if (transfer == null) transfer = (isOutgoing ? new MoneroOutgoingTransfer() : new MoneroIncomingTransfer()).setTx(tx); - List> rpcIndices = (List>) val; - transfer.setAccountIndex(rpcIndices.get(0).get("major").intValue()); - if (isOutgoing) { - List subaddressIndices = new ArrayList(); - for (Map rpcIndex : rpcIndices) subaddressIndices.add(rpcIndex.get("minor").intValue()); - ((MoneroOutgoingTransfer) transfer).setSubaddressIndices(subaddressIndices); - } else { - assertEquals(1, rpcIndices.size()); - ((MoneroIncomingTransfer) transfer).setSubaddressIndex(rpcIndices.get(0).get("minor").intValue()); - } - } - else if (key.equals("destinations")) { - assertTrue(isOutgoing); - List destinations = new ArrayList(); - for (Map rpcDestination : (List>) val) { - MoneroDestination destination = new MoneroDestination(); - destinations.add(destination); - for (String destinationKey : rpcDestination.keySet()) { - if (destinationKey.equals("address")) destination.setAddress((String) rpcDestination.get(destinationKey)); - else if (destinationKey.equals("amount")) destination.setAmount((BigInteger) rpcDestination.get(destinationKey)); - else throw new MoneroException("Unrecognized transaction destination field: " + destinationKey); - } - } - if (transfer == null) transfer = new MoneroOutgoingTransfer().setTx(tx); - ((MoneroOutgoingTransfer) transfer).setDestinations(destinations); - } - else if (key.equals("multisig_txset") && val != null) {} // handled elsewhere; this method only builds a tx wallet - else if (key.equals("unsigned_txset") && val != null) {} // handled elsewhere; this method only builds a tx wallet - else LOGGER.warn("WARNING: ignoring unexpected transaction field: " + key + ": " + val); - } - - // link block and tx - if (header != null) tx.setBlock(new MoneroBlock(header).setTxs(tx)); - - // initialize final fields - if (transfer != null) { - if (isOutgoing) { - if (tx.getOutgoingTransfer() != null) tx.getOutgoingTransfer().merge(transfer); - else tx.setOutgoingTransfer((MoneroOutgoingTransfer) transfer); - } else { - tx.setIncomingTransfers(new ArrayList(Arrays.asList((MoneroIncomingTransfer) transfer))); - } - } - - // return initialized transaction - return tx; - } - - @SuppressWarnings("unchecked") - private static MoneroTxWallet convertRpcTxWithVout(Map rpcVout) { - - // initialize tx - MoneroTxWallet tx = new MoneroTxWallet(); - tx.setIsConfirmed(true); - tx.setIsRelayed(true); - tx.setIsFailed(false); - - // initialize vout - MoneroOutputWallet vout = new MoneroOutputWallet().setTx(tx); - for (String key : rpcVout.keySet()) { - Object val = rpcVout.get(key); - if (key.equals("amount")) vout.setAmount((BigInteger) val); - else if (key.equals("spent")) vout.setIsSpent((Boolean) val); - else if (key.equals("key_image")) vout.setKeyImage(new MoneroKeyImage((String) val)); - else if (key.equals("global_index")) vout.setIndex(((BigInteger) val).intValue()); - else if (key.equals("tx_hash")) tx.setId((String) val); - else if (key.equals("unlocked")) vout.setIsUnlocked((Boolean) val); - else if (key.equals("frozen")) vout.setIsFrozen((Boolean) val); - else if (key.equals("subaddr_index")) { - Map rpcIndices = (Map) val; - vout.setAccountIndex(rpcIndices.get("major").intValue()); - vout.setSubaddressIndex(rpcIndices.get("minor").intValue()); - } - else if (key.equals("block_height")) { - long height = ((BigInteger) val).longValue(); - tx.setBlock(new MoneroBlock().setHeight(height).setTxs(tx)); - } - else LOGGER.warn("WARNING: ignoring unexpected transaction field with vout: " + key + ": " + val); - } - - // initialize tx with vout - List vouts = new ArrayList(); - vouts.add((MoneroOutput) vout); // have to cast to extended type because Java paramaterized types do not recognize inheritance - tx.setVouts(vouts); - return tx; - } - - /** - * Decodes a "type" from monero-wallet-rpc to initialize type and state - * fields in the given transaction. - * - * TODO: these should be safe set - * - * @param rpcType is the type to decode - * @param tx is the transaction to decode known fields to - * @return {boolean} true if the rpc type indicates outgoing xor incoming - */ - private static boolean decodeRpcType(String rpcType, MoneroTxWallet tx) { - boolean isOutgoing; - if (rpcType.equals("in")) { - isOutgoing = false; - tx.setIsConfirmed(true); - tx.setInTxPool(false); - tx.setIsRelayed(true); - tx.setDoNotRelay(false); - tx.setIsFailed(false); - tx.setIsMinerTx(false); - } else if (rpcType.equals("out")) { - isOutgoing = true; - tx.setIsConfirmed(true); - tx.setInTxPool(false); - tx.setIsRelayed(true); - tx.setDoNotRelay(false); - tx.setIsFailed(false); - tx.setIsMinerTx(false); - } else if (rpcType.equals("pool")) { - isOutgoing = false; - tx.setIsConfirmed(false); - tx.setInTxPool(true); - tx.setIsRelayed(true); - tx.setDoNotRelay(false); - tx.setIsFailed(false); - tx.setIsMinerTx(false); // TODO: but could it be? - } else if (rpcType.equals("pending")) { - isOutgoing = true; - tx.setIsConfirmed(false); - tx.setInTxPool(true); - tx.setIsRelayed(true); - tx.setDoNotRelay(false); - tx.setIsFailed(false); - tx.setIsMinerTx(false); - } else if (rpcType.equals("block")) { - isOutgoing = false; - tx.setIsConfirmed(true); - tx.setInTxPool(false); - tx.setIsRelayed(true); - tx.setDoNotRelay(false); - tx.setIsFailed(false); - tx.setIsMinerTx(true); - } else if (rpcType.equals("failed")) { - isOutgoing = true; - tx.setIsConfirmed(false); - tx.setInTxPool(false); - tx.setIsRelayed(true); - tx.setDoNotRelay(false); - tx.setIsFailed(true); - tx.setIsMinerTx(false); - } else { - throw new MoneroException("Unrecognized transfer type: " + rpcType); - } - return isOutgoing; - } - - /** - * Merges a transaction into a unique set of transactions. - * - * TODO monero-core: skipIfAbsent only necessary because incoming payments not returned - * when sent from/to same account #4500 - * - * @param tx is the transaction to merge into the existing txs - * @param txMap maps tx ids to txs - * @param blockMap maps block heights to blocks - * @param skipIfAbsent specifies if the tx should not be added if it doesn't already exist - */ - private static void mergeTx(MoneroTxWallet tx, Map txMap, Map blockMap, boolean skipIfAbsent) { - assertNotNull(tx.getId()); - - // if tx doesn't exist, add it (unless skipped) - MoneroTxWallet aTx = txMap.get(tx.getId()); - if (aTx == null) { - if (!skipIfAbsent) { - txMap.put(tx.getId(), tx); - } else { - LOGGER.warn("WARNING: tx does not already exist"); - } - } - - // otherwise merge with existing tx - else { - if (aTx.isFailed() != null & tx.isFailed() != null && !aTx.isFailed().equals(tx.isFailed())) { - System.out.println("ERROR: Merging these transactions will throw an error because their isFailed state is different"); - System.out.println(aTx); - System.out.println(tx); - } - aTx.merge(tx); - } - - // if confirmed, merge tx's block - if (tx.getHeight() != null) { - MoneroBlock aBlock = blockMap.get(tx.getHeight()); - if (aBlock == null) { - blockMap.put(tx.getHeight(), tx.getBlock()); - } else { - aBlock.merge(tx.getBlock()); - } - } - } - - /** - * Compares two transactions by their height. - */ - private static class TxHeightComparator implements Comparator { - @Override - public int compare(MoneroTx tx1, MoneroTx tx2) { - if (tx1.getHeight() == null && tx2.getHeight() == null) return 0; // both unconfirmed - else if (tx1.getHeight() == null) return 1; // tx1 is unconfirmed - else if (tx2.getHeight() == null) return -1; // tx2 is unconfirmed - int diff = tx1.getHeight().compareTo(tx2.getHeight()); - if (diff != 0) return diff; - return tx1.getBlock().getTxs().indexOf(tx1) - tx2.getBlock().getTxs().indexOf(tx2); // txs are in the same block so retain their original order - } - } - - /** - * Compares two transfers by ascending account and subaddress indices. - */ - public static class IncomingTransferComparator implements Comparator { - @Override - public int compare(MoneroIncomingTransfer t1, MoneroIncomingTransfer t2) { - - // compare by height - int heightComparison = TX_HEIGHT_COMPARATOR.compare(t1.getTx(), t2.getTx()); - if (heightComparison != 0) return heightComparison; - - // compare by account and subaddress index - if (t1.getAccountIndex() < t2.getAccountIndex()) return -1; - else if (t1.getAccountIndex() == t2.getAccountIndex()) return t1.getSubaddressIndex().compareTo(t2.getSubaddressIndex()); - return 1; - } - } - - /** - * Compares two vouts by ascending account and subaddress indices. - */ - public static class VoutComparator implements Comparator { - - @Override - public int compare(MoneroOutput o1, MoneroOutput o2) { - MoneroOutputWallet ow1 = (MoneroOutputWallet) o1; - MoneroOutputWallet ow2 = (MoneroOutputWallet) o2; - - // compare by height - int heightComparison = TX_HEIGHT_COMPARATOR.compare(ow1.getTx(), ow2.getTx()); - if (heightComparison != 0) return heightComparison; - - // compare by account index, subaddress index, and output - if (ow1.getAccountIndex() < ow2.getAccountIndex()) return -1; - else if (ow1.getAccountIndex() == ow2.getAccountIndex()) { - int compare = ow1.getSubaddressIndex().compareTo(ow2.getSubaddressIndex()); - if (compare != 0) return compare; - return ow1.getIndex().compareTo(ow2.getIndex()); - } - return 1; - } - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroAccount.java b/core/src/main/java/monero/wallet/model/MoneroAccount.java deleted file mode 100644 index 9477677346b..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroAccount.java +++ /dev/null @@ -1,147 +0,0 @@ -package monero.wallet.model; - -import java.math.BigInteger; -import java.util.List; - -import monero.utils.MoneroUtils; - -/** - * Monero account model. - */ -public class MoneroAccount { - - private Integer index; - private String primaryAddress; - private BigInteger balance; - private BigInteger unlockedBalance; - private String tag; - private List subaddresses; - - public MoneroAccount() { - super(); - } - - public MoneroAccount(int index, String primaryAddress, BigInteger balance, BigInteger unlockedBalance, List subaddresses) { - super(); - this.index = index; - this.primaryAddress = primaryAddress; - this.balance = balance; - this.unlockedBalance = unlockedBalance; - this.subaddresses = subaddresses; - } - - public Integer getIndex() { - return index; - } - - public void setIndex(Integer index) { - this.index = index; - } - - public String getPrimaryAddress() { - return primaryAddress; - } - - public void setPrimaryAddress(String primaryAddress) { - this.primaryAddress = primaryAddress; - } - - public BigInteger getBalance() { - return balance; - } - - public void setBalance(BigInteger balance) { - this.balance = balance; - } - - public BigInteger getUnlockedBalance() { - return unlockedBalance; - } - - public void setUnlockedBalance(BigInteger unlockedBalance) { - this.unlockedBalance = unlockedBalance; - } - - public String getTag() { - return tag; - } - - public void setTag(String tag) { - this.tag = tag; - } - - public List getSubaddresses() { - return subaddresses; - } - - public void setSubaddresses(List subaddresses) { - this.subaddresses = subaddresses; - if (subaddresses != null) { - for (MoneroSubaddress subaddress : subaddresses) { - subaddress.setAccountIndex(index); - } - } - } - - public String toString() { - return toString(0); - } - - public String toString(int indent) { - StringBuilder sb = new StringBuilder(); - sb.append(MoneroUtils.kvLine("Index", this.getIndex(), indent)); - sb.append(MoneroUtils.kvLine("Primary address", this.getPrimaryAddress(), indent)); - sb.append(MoneroUtils.kvLine("Balance", this.getBalance(), indent)); - sb.append(MoneroUtils.kvLine("Unlocked balance", this.getUnlockedBalance(), indent)); - sb.append(MoneroUtils.kvLine("Tag", this.getTag(), indent)); - if (this.getSubaddresses() != null) { - sb.append(MoneroUtils.kvLine("Subaddresses", "", indent)); - for (int i = 0; i < this.getSubaddresses().size(); i++) { - sb.append(MoneroUtils.kvLine(i + 1, "", indent + 1)); - sb.append(this.getSubaddresses().get(i).toString(indent + 2) + "\n"); - } - } - String str = sb.toString(); - return str.substring(0, str.length() - 1); // strip last newline - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((balance == null) ? 0 : balance.hashCode()); - result = prime * result + ((index == null) ? 0 : index.hashCode()); - result = prime * result + ((primaryAddress == null) ? 0 : primaryAddress.hashCode()); - result = prime * result + ((subaddresses == null) ? 0 : subaddresses.hashCode()); - result = prime * result + ((tag == null) ? 0 : tag.hashCode()); - result = prime * result + ((unlockedBalance == null) ? 0 : unlockedBalance.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - MoneroAccount other = (MoneroAccount) obj; - if (balance == null) { - if (other.balance != null) return false; - } else if (!balance.equals(other.balance)) return false; - if (index == null) { - if (other.index != null) return false; - } else if (!index.equals(other.index)) return false; - if (primaryAddress == null) { - if (other.primaryAddress != null) return false; - } else if (!primaryAddress.equals(other.primaryAddress)) return false; - if (subaddresses == null) { - if (other.subaddresses != null) return false; - } else if (!subaddresses.equals(other.subaddresses)) return false; - if (tag == null) { - if (other.tag != null) return false; - } else if (!tag.equals(other.tag)) return false; - if (unlockedBalance == null) { - if (other.unlockedBalance != null) return false; - } else if (!unlockedBalance.equals(other.unlockedBalance)) return false; - return true; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroAccountTag.java b/core/src/main/java/monero/wallet/model/MoneroAccountTag.java deleted file mode 100644 index 667a796f141..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroAccountTag.java +++ /dev/null @@ -1,76 +0,0 @@ -package monero.wallet.model; - -import java.util.List; - -/** - * Represents an account tag. - */ -public class MoneroAccountTag { - - private String tag; - private String label; - private List accountIndices; - - public MoneroAccountTag() { - super(); - } - - public MoneroAccountTag(String tag, String label, List accountIndices) { - super(); - this.tag = tag; - this.label = label; - this.accountIndices = accountIndices; - } - - public String getTag() { - return tag; - } - - public void setTag(String tag) { - this.tag = tag; - } - - public String getLabel() { - return label; - } - - public void setLabel(String label) { - this.label = label; - } - - public List getAccountIndices() { - return accountIndices; - } - - public void setAccountIndices(List accountIndices) { - this.accountIndices = accountIndices; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((accountIndices == null) ? 0 : accountIndices.hashCode()); - result = prime * result + ((label == null) ? 0 : label.hashCode()); - result = prime * result + ((tag == null) ? 0 : tag.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - MoneroAccountTag other = (MoneroAccountTag) obj; - if (accountIndices == null) { - if (other.accountIndices != null) return false; - } else if (!accountIndices.equals(other.accountIndices)) return false; - if (label == null) { - if (other.label != null) return false; - } else if (!label.equals(other.label)) return false; - if (tag == null) { - if (other.tag != null) return false; - } else if (!tag.equals(other.tag)) return false; - return true; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroAddressBookEntry.java b/core/src/main/java/monero/wallet/model/MoneroAddressBookEntry.java deleted file mode 100644 index 45e5c69d423..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroAddressBookEntry.java +++ /dev/null @@ -1,52 +0,0 @@ -package monero.wallet.model; - -/** - * Monero address book entry model. - */ -public class MoneroAddressBookEntry { - - private int index; - private String address; - private String paymentId; - private String description; - - public MoneroAddressBookEntry(int index, String address, String paymentId, String description) { - super(); - this.index = index; - this.address = address; - this.paymentId = paymentId; - this.description = description; - } - - public int getIndex() { - return index; - } - - public void setIndex(int index) { - this.index = index; - } - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public String getPaymentId() { - return paymentId; - } - - public void setPaymentId(String paymentId) { - this.paymentId = paymentId; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroCheck.java b/core/src/main/java/monero/wallet/model/MoneroCheck.java deleted file mode 100644 index 26a179b2869..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroCheck.java +++ /dev/null @@ -1,20 +0,0 @@ -package monero.wallet.model; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Base class for results from checking a transaction or reserve proof. - */ -public class MoneroCheck { - - public Boolean isGood; - - @JsonProperty("isGood") - public Boolean isGood() { - return isGood; - } - - public void setIsGood(Boolean isGood) { - this.isGood = isGood; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroCheckReserve.java b/core/src/main/java/monero/wallet/model/MoneroCheckReserve.java deleted file mode 100644 index f158060b126..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroCheckReserve.java +++ /dev/null @@ -1,28 +0,0 @@ -package monero.wallet.model; - -import java.math.BigInteger; - -/** - * Results from checking a reserve proof. - */ -public class MoneroCheckReserve extends MoneroCheck { - - private BigInteger totalAmount; - private BigInteger unconfirmedSpentAmount; - - public BigInteger getTotalAmount() { - return totalAmount; - } - - public void setTotalAmount(BigInteger totalAmount) { - this.totalAmount = totalAmount; - } - - public BigInteger getUnconfirmedSpentAmount() { - return unconfirmedSpentAmount; - } - - public void setUnconfirmedSpentAmount(BigInteger unconfirmedSpentAmount) { - this.unconfirmedSpentAmount = unconfirmedSpentAmount; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroCheckTx.java b/core/src/main/java/monero/wallet/model/MoneroCheckTx.java deleted file mode 100644 index 3366c651642..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroCheckTx.java +++ /dev/null @@ -1,37 +0,0 @@ -package monero.wallet.model; - -import java.math.BigInteger; - -/** - * Results from checking a transaction key. - */ -public class MoneroCheckTx extends MoneroCheck { - - public Boolean inTxPool; - public Long numConfirmations; - public BigInteger receivedAmount; - - public Boolean getInTxPool() { - return inTxPool; - } - - public void setInTxPool(Boolean inTxPool) { - this.inTxPool = inTxPool; - } - - public Long getNumConfirmations() { - return numConfirmations; - } - - public void setNumConfirmations(Long numConfirmations) { - this.numConfirmations = numConfirmations; - } - - public BigInteger getReceivedAmount() { - return receivedAmount; - } - - public void setReceivedAmount(BigInteger receivedAmount) { - this.receivedAmount = receivedAmount; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroIncomingTransfer.java b/core/src/main/java/monero/wallet/model/MoneroIncomingTransfer.java deleted file mode 100644 index c797ce44b46..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroIncomingTransfer.java +++ /dev/null @@ -1,143 +0,0 @@ -package monero.wallet.model; - -import static org.junit.Assert.assertTrue; - -import java.math.BigInteger; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import monero.utils.MoneroUtils; - -/** - * Models an incoming transfer of funds to the wallet. - */ -public class MoneroIncomingTransfer extends MoneroTransfer { - - private Integer subaddressIndex; - private String address; - - public MoneroIncomingTransfer() { - // nothing to initialize - } - - public MoneroIncomingTransfer(final MoneroIncomingTransfer transfer) { - super(transfer); - this.subaddressIndex = transfer.subaddressIndex; - this.address = transfer.address; - } - - @Override - public MoneroIncomingTransfer copy() { - return new MoneroIncomingTransfer(this); - } - - @JsonProperty("isIncoming") - public Boolean isIncoming() { - return true; - } - - public Integer getSubaddressIndex() { - return subaddressIndex; - } - - public MoneroIncomingTransfer setSubaddressIndex(Integer subaddressIndex) { - this.subaddressIndex = subaddressIndex; - return this; - } - - public String getAddress() { - return address; - } - - public MoneroIncomingTransfer setAddress(String address) { - this.address = address; - return this; - } - - public MoneroIncomingTransfer merge(MoneroTransfer transfer) { - assertTrue(transfer instanceof MoneroIncomingTransfer); - return merge((MoneroIncomingTransfer) transfer); - } - - /** - * Updates this transaction by merging the latest information from the given - * transaction. - * - * Merging can modify or build references to the transfer given so it - * should not be re-used or it should be copied before calling this method. - * - * @param transfer is the transfer to merge into this one - * @return this transfer for chaining - */ - public MoneroIncomingTransfer merge(MoneroIncomingTransfer transfer) { - super.merge(transfer); - assert(transfer instanceof MoneroIncomingTransfer); - if (this == transfer) return this; - this.setSubaddressIndex(MoneroUtils.reconcile(this.getSubaddressIndex(), transfer.getSubaddressIndex())); - this.setAddress(MoneroUtils.reconcile(this.getAddress(), transfer.getAddress())); - return this; - } - - public String toString() { - return toString(0); - } - - public String toString(int indent) { - StringBuilder sb = new StringBuilder(); - sb.append(super.toString(indent) + "\n"); - sb.append(MoneroUtils.kvLine("Subaddress index", this.getSubaddressIndex(), indent)); - sb.append(MoneroUtils.kvLine("Address", this.getAddress(), indent)); - String str = sb.toString(); - return str.substring(0, str.length() - 1); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((address == null) ? 0 : address.hashCode()); - result = prime * result + ((subaddressIndex == null) ? 0 : subaddressIndex.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!super.equals(obj)) return false; - if (getClass() != obj.getClass()) return false; - MoneroIncomingTransfer other = (MoneroIncomingTransfer) obj; - if (address == null) { - if (other.address != null) return false; - } else if (!address.equals(other.address)) return false; - if (subaddressIndex == null) { - if (other.subaddressIndex != null) return false; - } else if (!subaddressIndex.equals(other.subaddressIndex)) return false; - return true; - } - - //------------------- OVERRIDE CO-VARIANT RETURN TYPES --------------------- - - @Override - public MoneroIncomingTransfer setTx(MoneroTxWallet tx) { - super.setTx(tx); - return this; - } - - @Override - public MoneroIncomingTransfer setAmount(BigInteger amount) { - super.setAmount(amount); - return this; - } - - @Override - public MoneroIncomingTransfer setAccountIndex(Integer accountIndex) { - super.setAccountIndex(accountIndex); - return this; - } - - @Override - public MoneroIncomingTransfer setNumSuggestedConfirmations(Long numSuggestedConfirmations) { - super.setNumSuggestedConfirmations(numSuggestedConfirmations); - return this; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroIntegratedAddress.java b/core/src/main/java/monero/wallet/model/MoneroIntegratedAddress.java deleted file mode 100644 index b68eecf74d6..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroIntegratedAddress.java +++ /dev/null @@ -1,78 +0,0 @@ -package monero.wallet.model; - -/** - * Monero integrated address model. - */ -public class MoneroIntegratedAddress { - - private String standardAddress; - private String paymentId; - private String integratedAddress; - - public MoneroIntegratedAddress() { - // necessary for deserialization - } - - public MoneroIntegratedAddress(String standardAddress, String paymentId, String integratedAddress) { - super(); - this.standardAddress = standardAddress; - this.paymentId = paymentId; - this.integratedAddress = integratedAddress; - } - - public String getStandardAddress() { - return standardAddress; - } - - public void setStandardAddress(String standardAddress) { - this.standardAddress = standardAddress; - } - - public String getPaymentId() { - return paymentId; - } - - public void setPaymentId(String paymentId) { - this.paymentId = paymentId; - } - - public String getIntegratedAddress() { - return integratedAddress; - } - - public void setIntegratedAddress(String integratedAddress) { - this.integratedAddress = integratedAddress; - } - - public String toString() { - return integratedAddress; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((standardAddress == null) ? 0 : standardAddress.hashCode()); - result = prime * result + ((integratedAddress == null) ? 0 : integratedAddress.hashCode()); - result = prime * result + ((paymentId == null) ? 0 : paymentId.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - MoneroIntegratedAddress other = (MoneroIntegratedAddress) obj; - if (standardAddress == null) { - if (other.standardAddress != null) return false; - } else if (!standardAddress.equals(other.standardAddress)) return false; - if (integratedAddress == null) { - if (other.integratedAddress != null) return false; - } else if (!integratedAddress.equals(other.integratedAddress)) return false; - if (paymentId == null) { - if (other.paymentId != null) return false; - } else if (!paymentId.equals(other.paymentId)) return false; - return true; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroKeyImageImportResult.java b/core/src/main/java/monero/wallet/model/MoneroKeyImageImportResult.java deleted file mode 100644 index 8f44a6be4ec..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroKeyImageImportResult.java +++ /dev/null @@ -1,37 +0,0 @@ -package monero.wallet.model; - -import java.math.BigInteger; - -/** - * Models results from importing key images. - */ -public class MoneroKeyImageImportResult { - - private Long height; - private BigInteger spentAmount; - private BigInteger unspentAmount; - - public Long getHeight() { - return height; - } - - public void setHeight(Long height) { - this.height = height; - } - - public BigInteger getSpentAmount() { - return spentAmount; - } - - public void setSpentAmount(BigInteger spentAmount) { - this.spentAmount = spentAmount; - } - - public BigInteger getUnspentAmount() { - return unspentAmount; - } - - public void setUnspentAmount(BigInteger unspentAmount) { - this.unspentAmount = unspentAmount; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroMultisigInfo.java b/core/src/main/java/monero/wallet/model/MoneroMultisigInfo.java deleted file mode 100644 index 992a1d05533..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroMultisigInfo.java +++ /dev/null @@ -1,44 +0,0 @@ -package monero.wallet.model; - -/** - * Models information about a multisig wallet. - */ -public class MoneroMultisigInfo { - - private boolean isMultisig; - private Boolean isReady; - private Integer threshold; - private Integer numParticipants; - - public boolean isMultisig() { - return isMultisig; - } - - public void setIsMultisig(boolean isMultisig) { - this.isMultisig = isMultisig; - } - - public Boolean isReady() { - return isReady; - } - - public void setIsReady(Boolean isReady) { - this.isReady = isReady; - } - - public Integer getThreshold() { - return threshold; - } - - public void setThreshold(Integer threshold) { - this.threshold = threshold; - } - - public Integer getNumParticipants() { - return numParticipants; - } - - public void setNumParticipants(Integer numParticipants) { - this.numParticipants = numParticipants; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroMultisigInitResult.java b/core/src/main/java/monero/wallet/model/MoneroMultisigInitResult.java deleted file mode 100644 index ba0154865db..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroMultisigInitResult.java +++ /dev/null @@ -1,28 +0,0 @@ -package monero.wallet.model; - -/** - * Models the result of initializing a multisig wallet which results in the - * multisig wallet's address xor another multisig hex to share with - * participants to create the wallet. - */ -public class MoneroMultisigInitResult { - - private String address; - private String multisigHex; - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public String getMultisigHex() { - return multisigHex; - } - - public void setMultisigHex(String multisigHex) { - this.multisigHex = multisigHex; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroMultisigSignResult.java b/core/src/main/java/monero/wallet/model/MoneroMultisigSignResult.java deleted file mode 100644 index 4a6f815f0b7..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroMultisigSignResult.java +++ /dev/null @@ -1,52 +0,0 @@ -package monero.wallet.model; - -import java.util.List; - -/** - * Models the result of signing multisig tx hex. - */ -public class MoneroMultisigSignResult { - - private String signedMultisigTxHex; - private List txIds; - - public String getSignedMultisigTxHex() { - return signedMultisigTxHex; - } - - public void setSignedMultisigTxHex(String signedTxMultisigHex) { - this.signedMultisigTxHex = signedTxMultisigHex; - } - - public List getTxIds() { - return txIds; - } - - public void setTxIds(List txIds) { - this.txIds = txIds; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((signedMultisigTxHex == null) ? 0 : signedMultisigTxHex.hashCode()); - result = prime * result + ((txIds == null) ? 0 : txIds.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - MoneroMultisigSignResult other = (MoneroMultisigSignResult) obj; - if (signedMultisigTxHex == null) { - if (other.signedMultisigTxHex != null) return false; - } else if (!signedMultisigTxHex.equals(other.signedMultisigTxHex)) return false; - if (txIds == null) { - if (other.txIds != null) return false; - } else if (!txIds.equals(other.txIds)) return false; - return true; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroOutputQuery.java b/core/src/main/java/monero/wallet/model/MoneroOutputQuery.java deleted file mode 100644 index c999ab157ab..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroOutputQuery.java +++ /dev/null @@ -1,164 +0,0 @@ -package monero.wallet.model; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonIgnore; - -import common.types.Filter; -import common.utils.GenUtils; -import monero.daemon.model.MoneroKeyImage; -import monero.daemon.model.MoneroTx; - -/** - * Configures a query to retrieve wallet outputs (i.e. outputs that the wallet has or had the - * ability to spend). - * - * All outputs are returned except those that do not meet the criteria defined in this query. - */ -public class MoneroOutputQuery extends MoneroOutputWallet implements Filter { - - private MoneroTxQuery txQuery; - private List subaddressIndices; - private static MoneroOutputWallet EMPTY_OUTPUT = new MoneroOutputWallet(); - - public MoneroOutputQuery() { - super(); - } - - public MoneroOutputQuery(final MoneroOutputQuery query) { - super(query); - if (query.subaddressIndices != null) this.subaddressIndices = new ArrayList(query.subaddressIndices); - this.txQuery = query.txQuery; // reference original by default, MoneroTxQuery's deep copy will set this to itself - } - - public MoneroOutputQuery copy() { - return new MoneroOutputQuery(this); - } - - @JsonIgnore - public MoneroTxQuery getTxQuery() { - return txQuery; - } - - public MoneroOutputQuery setTxQuery(MoneroTxQuery txQuery) { - this.txQuery = txQuery; - return this; - } - - public List getSubaddressIndices() { - return subaddressIndices; - } - - public MoneroOutputQuery setSubaddressIndices(List subaddressIndices) { - this.subaddressIndices = subaddressIndices; - return this; - } - - public MoneroOutputQuery setSubaddressIndices(Integer... subaddressIndices) { - this.subaddressIndices = GenUtils.arrayToList(subaddressIndices); - return this; - } - - @Override - public boolean meetsCriteria(MoneroOutputWallet output) { - if (!(output instanceof MoneroOutputWallet)) return false; - - // filter on output - if (this.getAccountIndex() != null && !this.getAccountIndex().equals(output.getAccountIndex())) return false; - if (this.getSubaddressIndex() != null && !this.getSubaddressIndex().equals(output.getSubaddressIndex())) return false; - if (this.getAmount() != null && this.getAmount().compareTo(output.getAmount()) != 0) return false; - if (this.isSpent() != null && !this.isSpent().equals(output.isSpent())) return false; - if (this.isUnlocked() != null && !this.isUnlocked().equals(output.isUnlocked())) return false; - - // filter on output key image - if (this.getKeyImage() != null) { - if (output.getKeyImage() == null) return false; - if (this.getKeyImage().getHex() != null && !this.getKeyImage().getHex().equals(output.getKeyImage().getHex())) return false; - if (this.getKeyImage().getSignature() != null && !this.getKeyImage().getSignature().equals(output.getKeyImage().getSignature())) return false; - } - - // filter on extensions - if (this.getSubaddressIndices() != null && !this.getSubaddressIndices().contains(output.getSubaddressIndex())) return false; - - // filter with tx query - if (this.getTxQuery() != null && !this.getTxQuery().meetsCriteria(output.getTx())) return false; - - // output meets query - return true; - } - - public boolean isDefault() { - return meetsCriteria(EMPTY_OUTPUT); - } - - // ------------------- OVERRIDE CO-VARIANT RETURN TYPES --------------------- - - @Override - public MoneroOutputQuery setTx(MoneroTx tx) { - super.setTx(tx); - return this; - } - - @Override - public MoneroOutputQuery setTx(MoneroTxWallet tx) { - super.setTx(tx); - return this; - } - - @Override - public MoneroOutputQuery setAccountIndex(Integer accountIndex) { - super.setAccountIndex(accountIndex); - return this; - } - - @Override - public MoneroOutputQuery setSubaddressIndex(Integer subaddressIndex) { - super.setSubaddressIndex(subaddressIndex); - return this; - } - - @Override - public MoneroOutputQuery setIsSpent(Boolean isSpent) { - super.setIsSpent(isSpent); - return this; - } - - @Override - public MoneroOutputQuery setIsUnlocked(Boolean isUnlocked) { - super.setIsUnlocked(isUnlocked); - return this; - } - - - @Override - public MoneroOutputQuery setKeyImage(MoneroKeyImage keyImage) { - super.setKeyImage(keyImage); - return this; - } - - @Override - public MoneroOutputQuery setAmount(BigInteger amount) { - super.setAmount(amount); - return this; - } - - @Override - public MoneroOutputQuery setIndex(Integer index) { - super.setIndex(index); - return this; - } - - @Override - public MoneroOutputQuery setRingOutputIndices(List ringOutputIndices) { - super.setRingOutputIndices(ringOutputIndices); - return this; - } - - @Override - public MoneroOutputQuery setStealthPublicKey(String stealthPublicKey) { - super.setStealthPublicKey(stealthPublicKey); - return this; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroOutputWallet.java b/core/src/main/java/monero/wallet/model/MoneroOutputWallet.java deleted file mode 100644 index e6f9cd53db6..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroOutputWallet.java +++ /dev/null @@ -1,175 +0,0 @@ -package monero.wallet.model; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; - -import monero.daemon.model.MoneroOutput; -import monero.daemon.model.MoneroTx; -import monero.utils.MoneroException; -import monero.utils.MoneroUtils; - -/** - * Models a Monero output with wallet extensions. - */ -public class MoneroOutputWallet extends MoneroOutput { - - private Integer accountIndex; - private Integer subaddressIndex; - private Boolean isSpent; - private Boolean isUnlocked; - private Boolean isFrozen; - - public MoneroOutputWallet() { - // nothing to construct - } - - /** - * Deep copy constructor. - * - * @param output is the output to initialize from - */ - public MoneroOutputWallet(final MoneroOutputWallet output) { - super(output); - this.accountIndex = output.accountIndex; - this.subaddressIndex = output.subaddressIndex; - this.isSpent = output.isSpent; - this.isUnlocked = output.isUnlocked; - this.isFrozen = output.isFrozen; - } - - public MoneroOutputWallet copy() { - return new MoneroOutputWallet(this); - } - - public MoneroTxWallet getTx() { - return (MoneroTxWallet) super.getTx(); - } - - @JsonIgnore - public MoneroOutputWallet setTx(MoneroTx tx) { - if (tx != null && !(tx instanceof MoneroTxWallet)) throw new MoneroException("Wallet output's transaction must be of type MoneroTxWallet"); - super.setTx(tx); - return this; - } - - @JsonProperty("tx") - public MoneroOutputWallet setTx(MoneroTxWallet tx) { - super.setTx(tx); - return this; - } - - public Integer getAccountIndex() { - return accountIndex; - } - - public MoneroOutputWallet setAccountIndex(Integer accountIndex) { - this.accountIndex = accountIndex; - return this; - } - - public Integer getSubaddressIndex() { - return subaddressIndex; - } - - public MoneroOutputWallet setSubaddressIndex(Integer subaddressIndex) { - this.subaddressIndex = subaddressIndex; - return this; - } - - @JsonProperty("isSpent") - public Boolean isSpent() { - return isSpent; - } - - public MoneroOutputWallet setIsSpent(Boolean isSpent) { - this.isSpent = isSpent; - return this; - } - - @JsonProperty("isUnlocked") - public Boolean isUnlocked() { - return isUnlocked; - } - - public MoneroOutputWallet setIsUnlocked(Boolean isUnlocked) { - this.isUnlocked = isUnlocked; - return this; - } - - /** - * Indicates if this output has been deemed 'malicious' and will therefore - * not be spent by the wallet. - * - * @return Boolean is whether or not this output is frozen - */ - @JsonProperty("isFrozen") - public Boolean isFrozen() { - return isFrozen; - } - - public MoneroOutputWallet setIsFrozen(Boolean isFrozen) { - this.isFrozen = isFrozen; - return this; - } - - public MoneroOutputWallet merge(MoneroOutput output) { - return merge((MoneroOutputWallet) output); - } - - public MoneroOutputWallet merge(MoneroOutputWallet output) { - if (this == output) return this; - super.merge(output); - this.setAccountIndex(MoneroUtils.reconcile(this.getAccountIndex(), output.getAccountIndex())); - this.setSubaddressIndex(MoneroUtils.reconcile(this.getSubaddressIndex(), output.getSubaddressIndex())); - this.setIsSpent(MoneroUtils.reconcile(this.isSpent(), output.isSpent(), null, true, null)); // output can become spent - return this; - } - - public String toString(int indent) { - StringBuilder sb = new StringBuilder(); - sb.append(super.toString(indent) + "\n"); - sb.append(MoneroUtils.kvLine("Account index", this.getAccountIndex(), indent)); - sb.append(MoneroUtils.kvLine("Subaddress index", this.getSubaddressIndex(), indent)); - sb.append(MoneroUtils.kvLine("Is spent", this.isSpent(), indent)); - sb.append(MoneroUtils.kvLine("Is unlocked", this.isUnlocked(), indent)); - sb.append(MoneroUtils.kvLine("Is frozen", this.isFrozen(), indent)); - String str = sb.toString(); - return str.substring(0, str.length() - 1); // strip last newline - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((accountIndex == null) ? 0 : accountIndex.hashCode()); - result = prime * result + ((isFrozen == null) ? 0 : isFrozen.hashCode()); - result = prime * result + ((isSpent == null) ? 0 : isSpent.hashCode()); - result = prime * result + ((isUnlocked == null) ? 0 : isUnlocked.hashCode()); - result = prime * result + ((subaddressIndex == null) ? 0 : subaddressIndex.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!super.equals(obj)) return false; - if (getClass() != obj.getClass()) return false; - MoneroOutputWallet other = (MoneroOutputWallet) obj; - if (accountIndex == null) { - if (other.accountIndex != null) return false; - } else if (!accountIndex.equals(other.accountIndex)) return false; - if (isFrozen == null) { - if (other.isFrozen != null) return false; - } else if (!isFrozen.equals(other.isFrozen)) return false; - if (isSpent == null) { - if (other.isSpent != null) return false; - } else if (!isSpent.equals(other.isSpent)) return false; - if (isUnlocked == null) { - if (other.isUnlocked != null) return false; - } else if (!isUnlocked.equals(other.isUnlocked)) return false; - if (subaddressIndex == null) { - if (other.subaddressIndex != null) return false; - } else if (!subaddressIndex.equals(other.subaddressIndex)) return false; - return true; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroSendPriority.java b/core/src/main/java/monero/wallet/model/MoneroSendPriority.java deleted file mode 100644 index 02d80d750a6..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroSendPriority.java +++ /dev/null @@ -1,11 +0,0 @@ -package monero.wallet.model; - -/** - * Enumerates send priorities. - */ -public enum MoneroSendPriority { - DEFAULT, - UNIMPORTANT, - NORMAL, - ELEVATED -} diff --git a/core/src/main/java/monero/wallet/model/MoneroSendRequest.java b/core/src/main/java/monero/wallet/model/MoneroSendRequest.java deleted file mode 100644 index 13646e17411..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroSendRequest.java +++ /dev/null @@ -1,330 +0,0 @@ -package monero.wallet.model; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import common.utils.GenUtils; - -/** - * Configures a request to send/sweep funds or create a payment URI. - */ -public class MoneroSendRequest { - - private List destinations; - private String paymentId; - private MoneroSendPriority priority; - private Integer mixin; - private Integer ringSize; - private BigInteger fee; - private Integer accountIndex; - private List subaddressIndices; - private Long unlockTime; - private Boolean canSplit; - private Boolean doNotRelay; - private String note; - private String recipientName; - private BigInteger belowAmount; - private Boolean sweepEachSubaddress; - private String keyImage; - - public MoneroSendRequest() { - this((String) null); - } - - public MoneroSendRequest(String address) { - this(address, null); - } - - public MoneroSendRequest(String address, BigInteger amount) { - this(null, address, amount); - } - - public MoneroSendRequest(Integer accountIndex, String address) { - this(accountIndex, address, null); - } - - public MoneroSendRequest(Integer accountIndex, String address, BigInteger amount) { - this(accountIndex, address, amount, null); - } - - public MoneroSendRequest(Integer accountIndex, String address, BigInteger amount, MoneroSendPriority priority) { - this.accountIndex = accountIndex; - if (address != null || amount != null) this.destinations = Arrays.asList(new MoneroDestination(address, amount)); // map address and amount to default destination - this.priority = priority; - } - - MoneroSendRequest(final MoneroSendRequest req) { - if (req.destinations != null) { - this.destinations = new ArrayList(); - for (MoneroDestination destination : req.getDestinations()) this.destinations.add(destination.copy()); - } - this.paymentId = req.paymentId; - this.priority = req.priority; - this.mixin = req.mixin; - this.ringSize = req.ringSize; - this.fee = req.fee; - this.accountIndex = req.accountIndex; - if (req.subaddressIndices != null) this.subaddressIndices = new ArrayList(req.subaddressIndices); - this.unlockTime = req.unlockTime; - this.canSplit = req.canSplit; - this.doNotRelay = req.doNotRelay; - this.note = req.note; - this.recipientName = req.recipientName; - this.belowAmount = req.belowAmount; - this.sweepEachSubaddress = req.sweepEachSubaddress; - this.keyImage = req.keyImage; - } - - public MoneroSendRequest copy() { - return new MoneroSendRequest(this); - } - - public MoneroSendRequest addDestination(MoneroDestination destination) { - if (this.destinations == null) this.destinations = new ArrayList(); - this.destinations.add(destination); - return this; - } - - public List getDestinations() { - return destinations; - } - - @JsonProperty("destinations") - public MoneroSendRequest setDestinations(List destinations) { - this.destinations = destinations; - return this; - } - - public MoneroSendRequest setDestinations(MoneroDestination... destinations) { - this.destinations = GenUtils.arrayToList(destinations); - return this; - } - - public String getPaymentId() { - return paymentId; - } - - public MoneroSendRequest setPaymentId(String paymentId) { - this.paymentId = paymentId; - return this; - } - - public MoneroSendPriority getPriority() { - return priority; - } - - public MoneroSendRequest setPriority(MoneroSendPriority priority) { - this.priority = priority; - return this; - } - - public Integer getMixin() { - return mixin; - } - - public MoneroSendRequest setMixin(Integer mixin) { - this.mixin = mixin; - return this; - } - - public Integer getRingSize() { - return ringSize; - } - - public MoneroSendRequest setRingSize(Integer ringSize) { - this.ringSize = ringSize; - return this; - } - - public BigInteger getFee() { - return fee; - } - - public MoneroSendRequest setFee(BigInteger fee) { - this.fee = fee; - return this; - } - - public Integer getAccountIndex() { - return accountIndex; - } - - public MoneroSendRequest setAccountIndex(Integer accountIndex) { - this.accountIndex = accountIndex; - return this; - } - - public List getSubaddressIndices() { - return subaddressIndices; - } - - public MoneroSendRequest setSubaddressIndex(int subaddressIndex) { - setSubaddressIndices(subaddressIndex); - return this; - } - - @JsonProperty("subaddressIndices") - public MoneroSendRequest setSubaddressIndices(List subaddressIndices) { - this.subaddressIndices = subaddressIndices; - return this; - } - - public MoneroSendRequest setSubaddressIndices(Integer... subaddressIndices) { - this.subaddressIndices = GenUtils.arrayToList(subaddressIndices); - return this; - } - - public Long getUnlockTime() { - return unlockTime; - } - - public MoneroSendRequest setUnlockTime(Long unlockTime) { - this.unlockTime = unlockTime; - return this; - } - - public Boolean getCanSplit() { - return canSplit; - } - - public MoneroSendRequest setCanSplit(Boolean canSplit) { - this.canSplit = canSplit; - return this; - } - - public Boolean getDoNotRelay() { - return doNotRelay; - } - - public MoneroSendRequest setDoNotRelay(Boolean doNotRelay) { - this.doNotRelay = doNotRelay; - return this; - } - - public String getNote() { - return note; - } - - public MoneroSendRequest setNote(String note) { - this.note = note; - return this; - } - - public String getRecipientName() { - return recipientName; - } - - public MoneroSendRequest setRecipientName(String recipientName) { - this.recipientName = recipientName; - return this; - } - - public BigInteger getBelowAmount() { - return belowAmount; - } - - public MoneroSendRequest setBelowAmount(BigInteger belowAmount) { - this.belowAmount = belowAmount; - return this; - } - - public Boolean getSweepEachSubaddress() { - return sweepEachSubaddress; - } - - public MoneroSendRequest setSweepEachSubaddress(Boolean sweepEachSubaddress) { - this.sweepEachSubaddress = sweepEachSubaddress; - return this; - } - - public String getKeyImage() { - return keyImage; - } - - public MoneroSendRequest setKeyImage(String keyImage) { - this.keyImage = keyImage; - return this; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((accountIndex == null) ? 0 : accountIndex.hashCode()); - result = prime * result + ((belowAmount == null) ? 0 : belowAmount.hashCode()); - result = prime * result + ((canSplit == null) ? 0 : canSplit.hashCode()); - result = prime * result + ((destinations == null) ? 0 : destinations.hashCode()); - result = prime * result + ((doNotRelay == null) ? 0 : doNotRelay.hashCode()); - result = prime * result + ((fee == null) ? 0 : fee.hashCode()); - result = prime * result + ((keyImage == null) ? 0 : keyImage.hashCode()); - result = prime * result + ((mixin == null) ? 0 : mixin.hashCode()); - result = prime * result + ((note == null) ? 0 : note.hashCode()); - result = prime * result + ((paymentId == null) ? 0 : paymentId.hashCode()); - result = prime * result + ((priority == null) ? 0 : priority.hashCode()); - result = prime * result + ((recipientName == null) ? 0 : recipientName.hashCode()); - result = prime * result + ((ringSize == null) ? 0 : ringSize.hashCode()); - result = prime * result + ((subaddressIndices == null) ? 0 : subaddressIndices.hashCode()); - result = prime * result + ((sweepEachSubaddress == null) ? 0 : sweepEachSubaddress.hashCode()); - result = prime * result + ((unlockTime == null) ? 0 : unlockTime.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - MoneroSendRequest other = (MoneroSendRequest) obj; - if (accountIndex == null) { - if (other.accountIndex != null) return false; - } else if (!accountIndex.equals(other.accountIndex)) return false; - if (belowAmount == null) { - if (other.belowAmount != null) return false; - } else if (!belowAmount.equals(other.belowAmount)) return false; - if (canSplit == null) { - if (other.canSplit != null) return false; - } else if (!canSplit.equals(other.canSplit)) return false; - if (destinations == null) { - if (other.destinations != null) return false; - } else if (!destinations.equals(other.destinations)) return false; - if (doNotRelay == null) { - if (other.doNotRelay != null) return false; - } else if (!doNotRelay.equals(other.doNotRelay)) return false; - if (fee == null) { - if (other.fee != null) return false; - } else if (!fee.equals(other.fee)) return false; - if (keyImage == null) { - if (other.keyImage != null) return false; - } else if (!keyImage.equals(other.keyImage)) return false; - if (mixin == null) { - if (other.mixin != null) return false; - } else if (!mixin.equals(other.mixin)) return false; - if (note == null) { - if (other.note != null) return false; - } else if (!note.equals(other.note)) return false; - if (paymentId == null) { - if (other.paymentId != null) return false; - } else if (!paymentId.equals(other.paymentId)) return false; - if (priority != other.priority) return false; - if (recipientName == null) { - if (other.recipientName != null) return false; - } else if (!recipientName.equals(other.recipientName)) return false; - if (ringSize == null) { - if (other.ringSize != null) return false; - } else if (!ringSize.equals(other.ringSize)) return false; - if (subaddressIndices == null) { - if (other.subaddressIndices != null) return false; - } else if (!subaddressIndices.equals(other.subaddressIndices)) return false; - if (sweepEachSubaddress == null) { - if (other.sweepEachSubaddress != null) return false; - } else if (!sweepEachSubaddress.equals(other.sweepEachSubaddress)) return false; - if (unlockTime == null) { - if (other.unlockTime != null) return false; - } else if (!unlockTime.equals(other.unlockTime)) return false; - return true; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroSubaddress.java b/core/src/main/java/monero/wallet/model/MoneroSubaddress.java deleted file mode 100644 index c3dd4e95df3..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroSubaddress.java +++ /dev/null @@ -1,184 +0,0 @@ -package monero.wallet.model; - -import java.math.BigInteger; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import monero.utils.MoneroUtils; - -/** - * Monero subaddress model. - */ -public class MoneroSubaddress { - - private Integer accountIndex; - private Integer index; - private String address; - private String label; - private BigInteger balance; - private BigInteger unlockedBalance; - private Long numUnspentOutputs; - private Boolean isUsed; - private Long numBlocksToUnlock; - - public MoneroSubaddress() { - // nothing to construct - } - - public MoneroSubaddress(String address) { - this.address = address; - } - - public Integer getAccountIndex() { - return accountIndex; - } - - public MoneroSubaddress setAccountIndex(Integer accountIndex) { - this.accountIndex = accountIndex; - return this; - } - - public Integer getIndex() { - return index; - } - - public MoneroSubaddress setIndex(Integer index) { - this.index = index; - return this; - } - - public String getAddress() { - return address; - } - - public MoneroSubaddress setAddress(String address) { - this.address = address; - return this; - } - - public String getLabel() { - return label; - } - - public MoneroSubaddress setLabel(String label) { - this.label = label; - return this; - } - - public BigInteger getBalance() { - return balance; - } - - public MoneroSubaddress setBalance(BigInteger balance) { - this.balance = balance; - return this; - } - - public BigInteger getUnlockedBalance() { - return unlockedBalance; - } - - public MoneroSubaddress setUnlockedBalance(BigInteger unlockedBalance) { - this.unlockedBalance = unlockedBalance; - return this; - } - - public Long getNumUnspentOutputs() { - return numUnspentOutputs; - } - - public MoneroSubaddress setNumUnspentOutputs(Long numUnspentOutputs) { - this.numUnspentOutputs = numUnspentOutputs; - return this; - } - - @JsonProperty("isUsed") - public Boolean isUsed() { - return isUsed; - } - - public MoneroSubaddress setIsUsed(Boolean isUsed) { - this.isUsed = isUsed; - return this; - } - - public Long getNumBlocksToUnlock() { - return numBlocksToUnlock; - } - - public MoneroSubaddress setNumBlocksToUnlock(Long numBlocksToUnlock) { - this.numBlocksToUnlock = numBlocksToUnlock; - return this; - } - - public String toString() { - return toString(0); - } - - public String toString(int indent) { - StringBuilder sb = new StringBuilder(); - sb.append(MoneroUtils.kvLine("Account index", this.getAccountIndex(), indent)); - sb.append(MoneroUtils.kvLine("Subaddress index", this.getIndex(), indent)); - sb.append(MoneroUtils.kvLine("Address", this.getAddress(), indent)); - sb.append(MoneroUtils.kvLine("Label", this.getLabel(), indent)); - sb.append(MoneroUtils.kvLine("Balance", this.getBalance(), indent)); - sb.append(MoneroUtils.kvLine("Unlocked balance", this.getUnlockedBalance(), indent)); - sb.append(MoneroUtils.kvLine("Num unspent outputs", this.getNumUnspentOutputs(), indent)); - sb.append(MoneroUtils.kvLine("Is used", this.isUsed(), indent)); - sb.append(MoneroUtils.kvLine("Num blocks to unlock", this.getNumBlocksToUnlock(), indent)); - String str = sb.toString(); - return str.substring(0, str.length() - 1); // strip last newline - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((accountIndex == null) ? 0 : accountIndex.hashCode()); - result = prime * result + ((address == null) ? 0 : address.hashCode()); - result = prime * result + ((balance == null) ? 0 : balance.hashCode()); - result = prime * result + ((index == null) ? 0 : index.hashCode()); - result = prime * result + ((isUsed == null) ? 0 : isUsed.hashCode()); - result = prime * result + ((label == null) ? 0 : label.hashCode()); - result = prime * result + ((numBlocksToUnlock == null) ? 0 : numBlocksToUnlock.hashCode()); - result = prime * result + ((numUnspentOutputs == null) ? 0 : numUnspentOutputs.hashCode()); - result = prime * result + ((unlockedBalance == null) ? 0 : unlockedBalance.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - MoneroSubaddress other = (MoneroSubaddress) obj; - if (accountIndex == null) { - if (other.accountIndex != null) return false; - } else if (!accountIndex.equals(other.accountIndex)) return false; - if (address == null) { - if (other.address != null) return false; - } else if (!address.equals(other.address)) return false; - if (balance == null) { - if (other.balance != null) return false; - } else if (!balance.equals(other.balance)) return false; - if (index == null) { - if (other.index != null) return false; - } else if (!index.equals(other.index)) return false; - if (isUsed == null) { - if (other.isUsed != null) return false; - } else if (!isUsed.equals(other.isUsed)) return false; - if (label == null) { - if (other.label != null) return false; - } else if (!label.equals(other.label)) return false; - if (numBlocksToUnlock == null) { - if (other.numBlocksToUnlock != null) return false; - } else if (!numBlocksToUnlock.equals(other.numBlocksToUnlock)) return false; - if (numUnspentOutputs == null) { - if (other.numUnspentOutputs != null) return false; - } else if (!numUnspentOutputs.equals(other.numUnspentOutputs)) return false; - if (unlockedBalance == null) { - if (other.unlockedBalance != null) return false; - } else if (!unlockedBalance.equals(other.unlockedBalance)) return false; - return true; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroSyncListener.java b/core/src/main/java/monero/wallet/model/MoneroSyncListener.java deleted file mode 100644 index 5d76cf2459b..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroSyncListener.java +++ /dev/null @@ -1,18 +0,0 @@ -package monero.wallet.model; - -/** - * Interface to receive progress notifications as a wallet is synchronized. - */ -public interface MoneroSyncListener { - - /** - * Invoked as the wallet is synchronized. - * - * @param height is the height of the synced block - * @param startHeight is the starting height of the sync request - * @param endHeight is the ending height of the sync request - * @param percentDone is the sync progress as a percentage - * @param message is a human-readable description of the current progress - */ - public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message); -} diff --git a/core/src/main/java/monero/wallet/model/MoneroSyncResult.java b/core/src/main/java/monero/wallet/model/MoneroSyncResult.java deleted file mode 100644 index 1e76867c3b3..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroSyncResult.java +++ /dev/null @@ -1,35 +0,0 @@ -package monero.wallet.model; - -/** - * Result from syncing a Monero wallet. - */ -public class MoneroSyncResult { - - private Long numBlocksFetched; - private Boolean receivedMoney; - - public MoneroSyncResult() { - this(null, null); - } - - public MoneroSyncResult(Long numBlocksFetched, Boolean receivedMoney) { - this.numBlocksFetched = numBlocksFetched; - this.receivedMoney = receivedMoney; - } - - public Long getNumBlocksFetched() { - return numBlocksFetched; - } - - public void setNumBlocksFetched(Long numBlocksFetched) { - this.numBlocksFetched = numBlocksFetched; - } - - public Boolean getReceivedMoney() { - return receivedMoney; - } - - public void setReceivedMoney(Boolean receivedMoney) { - this.receivedMoney = receivedMoney; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroTransfer.java b/core/src/main/java/monero/wallet/model/MoneroTransfer.java deleted file mode 100644 index f0bd3ea258f..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroTransfer.java +++ /dev/null @@ -1,162 +0,0 @@ -package monero.wallet.model; - -import java.math.BigInteger; - -import com.fasterxml.jackson.annotation.JsonBackReference; -import com.fasterxml.jackson.annotation.JsonProperty; - -import monero.utils.MoneroUtils; - -/** - * Models a base transfer of funds to or from the wallet. - * - * Transfers are either of type MoneroIncomingTransfer or MoneroOutgoingTransfer so this class is abstract. - */ -public abstract class MoneroTransfer { - - private MoneroTxWallet tx; - private BigInteger amount; - private Integer accountIndex; - private Long numSuggestedConfirmations; - - public MoneroTransfer() { - // nothing to initialize - } - - public MoneroTransfer(final MoneroTransfer transfer) { - this.amount = transfer.amount; - this.accountIndex = transfer.accountIndex; - this.numSuggestedConfirmations = transfer.numSuggestedConfirmations; - } - - public abstract MoneroTransfer copy(); - - @JsonBackReference - public MoneroTxWallet getTx() { - return tx; - } - - public MoneroTransfer setTx(MoneroTxWallet tx) { - this.tx = tx; - return this; - } - - @JsonProperty("isOutgoing") - public Boolean isOutgoing() { - return !isIncoming(); - } - - @JsonProperty("isIncoming") - public abstract Boolean isIncoming(); - - public BigInteger getAmount() { - return amount; - } - - public MoneroTransfer setAmount(BigInteger amount) { - this.amount = amount; - return this; - } - - public Integer getAccountIndex() { - return accountIndex; - } - - public MoneroTransfer setAccountIndex(Integer accountIndex) { - this.accountIndex = accountIndex; - return this; - } - - /** - * Return how many confirmations till it's not economically worth re-writing the chain. - * That is, the number of confirmations before the transaction is highly unlikely to be - * double spent or overwritten and may be considered settled, e.g. for a merchant to trust - * as finalized. - * - * @return Integer is the number of confirmations before it's not worth rewriting the chain - */ - public Long getNumSuggestedConfirmations() { - return numSuggestedConfirmations; - } - - public MoneroTransfer setNumSuggestedConfirmations(Long numSuggestedConfirmations) { - this.numSuggestedConfirmations = numSuggestedConfirmations; - return this; - } - - /** - * Updates this transaction by merging the latest information from the given - * transaction. - * - * Merging can modify or build references to the transfer given so it - * should not be re-used or it should be copied before calling this method. - * - * @param transfer is the transfer to merge into this one - */ - public MoneroTransfer merge(MoneroTransfer transfer) { - assert(transfer instanceof MoneroTransfer); - if (this == transfer) return this; - - // merge txs if they're different which comes back to merging transfers - if (this.getTx() != transfer.getTx()) { - this.getTx().merge(transfer.getTx()); - return this; - } - - // otherwise merge transfer fields - this.setAccountIndex(MoneroUtils.reconcile(this.getAccountIndex(), transfer.getAccountIndex())); - - // TODO monero core: failed tx in pool (after testUpdateLockedDifferentAccounts()) causes non-originating saved wallets to return duplicate incoming transfers but one has amount/numSuggestedConfirmations of 0 - if (this.getAmount() != null && transfer.getAmount() != null && !this.getAmount().equals(transfer.getAmount()) && (BigInteger.valueOf(0).equals(this.getAmount()) || BigInteger.valueOf(0).equals(transfer.getAmount()))) { - this.setAmount(MoneroUtils.reconcile(this.getAmount(), transfer.getAmount(), null, null, true)); - this.setNumSuggestedConfirmations(MoneroUtils.reconcile(this.getNumSuggestedConfirmations(), transfer.getNumSuggestedConfirmations(), null, null, true)); - System.out.println("WARNING: failed tx in pool causes non-originating wallets to return duplicate incoming transfers but with one amount/numSuggestedConfirmations of 0"); - } else { - this.setAmount(MoneroUtils.reconcile(this.getAmount(), transfer.getAmount())); - this.setNumSuggestedConfirmations(MoneroUtils.reconcile(this.getNumSuggestedConfirmations(), transfer.getNumSuggestedConfirmations(), null, null, false)); // TODO monero-wallet-rpc: outgoing txs become 0 when confirmed - } - - return this; - } - - public String toString() { - return toString(0); - } - - public String toString(int indent) { - StringBuilder sb = new StringBuilder(); - sb.append(MoneroUtils.kvLine("Amount", this.getAmount() != null ? this.getAmount().toString() : null, indent)); - sb.append(MoneroUtils.kvLine("Account index", this.getAccountIndex(), indent)); - sb.append(MoneroUtils.kvLine("Num suggested confirmations", getNumSuggestedConfirmations(), indent)); - String str = sb.toString(); - return str.isEmpty() ? str : str.substring(0, str.length() - 1); // strip last newline - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((accountIndex == null) ? 0 : accountIndex.hashCode()); - result = prime * result + ((amount == null) ? 0 : amount.hashCode()); - result = prime * result + ((numSuggestedConfirmations == null) ? 0 : numSuggestedConfirmations.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - MoneroTransfer other = (MoneroTransfer) obj; - if (accountIndex == null) { - if (other.accountIndex != null) return false; - } else if (!accountIndex.equals(other.accountIndex)) return false; - if (amount == null) { - if (other.amount != null) return false; - } else if (!amount.equals(other.amount)) return false; - if (numSuggestedConfirmations == null) { - if (other.numSuggestedConfirmations != null) return false; - } else if (!numSuggestedConfirmations.equals(other.numSuggestedConfirmations)) return false; - return true; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroTransferQuery.java b/core/src/main/java/monero/wallet/model/MoneroTransferQuery.java deleted file mode 100644 index 4969fae9630..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroTransferQuery.java +++ /dev/null @@ -1,226 +0,0 @@ -package monero.wallet.model; - -import static org.junit.Assert.assertNotNull; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; - -import common.types.Filter; -import common.utils.GenUtils; - -/** - * Configures a query to retrieve transfers. - * - * All transfers are returned except those that do not meet the criteria defined in this query. - */ -public class MoneroTransferQuery extends MoneroTransfer implements Filter { - - private Boolean isIncoming; - private String address; - private List addresses; - private Integer subaddressIndex; - private List subaddressIndices; - private List destinations; - private Boolean hasDestinations; - private MoneroTxQuery txQuery; - - public MoneroTransferQuery() { - - } - - public MoneroTransferQuery(final MoneroTransferQuery query) { - super(query); - this.isIncoming = query.isIncoming; - this.address = query.address; - if (query.addresses != null) this.addresses = new ArrayList(query.addresses); - this.subaddressIndex = query.subaddressIndex; - if (query.subaddressIndices != null) this.subaddressIndices = new ArrayList(query.subaddressIndices); - if (query.destinations != null) { - this.destinations = new ArrayList(); - for (MoneroDestination destination : query.getDestinations()) this.destinations.add(destination.copy()); - } - this.hasDestinations = query.hasDestinations; - this.txQuery = query.txQuery; // reference original by default, MoneroTxQuery's deep copy will set this to itself - } - - @Override - public MoneroTransferQuery copy() { - return new MoneroTransferQuery(this); - } - - public Boolean isIncoming() { - return isIncoming; - } - - public MoneroTransferQuery setIsIncoming(Boolean isIncoming) { - this.isIncoming = isIncoming; - return this; - } - - public Boolean isOutgoing() { - return isIncoming == null ? null : !isIncoming; - } - - public MoneroTransferQuery setIsOutgoing(Boolean isOutgoing) { - isIncoming = isOutgoing == null ? null : !isOutgoing; - return this; - } - - public String getAddress() { - return address; - } - - public MoneroTransferQuery setAddress(String address) { - this.address = address; - return this; - } - - public List getAddresses() { - return addresses; - } - - public MoneroTransferQuery setAddresses(List addresses) { - this.addresses = addresses; - return this; - } - - public MoneroTransferQuery setAddresses(String... addresses) { - this.addresses = GenUtils.arrayToList(addresses); - return this; - } - - public Integer getSubaddressIndex() { - return subaddressIndex; - } - - public MoneroTransferQuery setSubaddressIndex(Integer subaddressIndex) { - this.subaddressIndex = subaddressIndex; - return this; - } - - public List getSubaddressIndices() { - return subaddressIndices; - } - - public MoneroTransferQuery setSubaddressIndices(List subaddressIndices) { - this.subaddressIndices = subaddressIndices; - return this; - } - - public MoneroTransferQuery setSubaddressIndices(Integer... subaddressIndices) { - this.subaddressIndices = GenUtils.arrayToList(subaddressIndices); - return this; - } - - public List getDestinations() { - return destinations; - } - - public MoneroTransferQuery setDestinations(List destinations) { - this.destinations = destinations; - return this; - } - - @JsonProperty("hasDestinations") - public Boolean hasDestinations() { - return hasDestinations; - } - - public MoneroTransferQuery setHasDestinations(Boolean hasDestinations) { - this.hasDestinations = hasDestinations; - return this; - } - - @JsonIgnore - public MoneroTxQuery getTxQuery() { - return txQuery; - } - - public MoneroTransferQuery setTxQuery(MoneroTxQuery txQuery) { - this.txQuery = txQuery; - return this; - } - - @Override - public boolean meetsCriteria(MoneroTransfer transfer) { - assertNotNull("transfer is null", transfer); - if (txQuery != null && txQuery.getTransferQuery() != null) throw new RuntimeException("Transfer query's tx query cannot have a circular transfer query"); // TODO: could auto detect and handle this. port to js - - // filter on common fields - if (this.isIncoming() != null && this.isIncoming() != transfer.isIncoming()) return false; - if (this.isOutgoing() != null && this.isOutgoing() != transfer.isOutgoing()) return false; - if (this.getAmount() != null && this.getAmount().compareTo(transfer.getAmount()) != 0) return false; - if (this.getAccountIndex() != null && !this.getAccountIndex().equals(transfer.getAccountIndex())) return false; - - // filter on incoming fields - if (transfer instanceof MoneroIncomingTransfer) { - if (Boolean.TRUE.equals(this.hasDestinations())) return false; - MoneroIncomingTransfer inTransfer = (MoneroIncomingTransfer) transfer; - if (this.getAddress() != null && !this.getAddress().equals(inTransfer.getAddress())) return false; - if (this.getAddresses() != null && !this.getAddresses().contains(inTransfer.getAddress())) return false; - if (this.getSubaddressIndex() != null && !this.getSubaddressIndex().equals(inTransfer.getSubaddressIndex())) return false; - if (this.getSubaddressIndices() != null && !this.getSubaddressIndices().contains(inTransfer.getSubaddressIndex())) return false; - } - - // filter on outgoing fields - else if (transfer instanceof MoneroOutgoingTransfer) { - MoneroOutgoingTransfer outTransfer = (MoneroOutgoingTransfer) transfer; - - // filter on addresses - if (this.getAddress() != null && (outTransfer.getAddresses() == null || !outTransfer.getAddresses().contains(this.getAddress()))) return false; // TODO: will filter all transfers if they don't contain addresses - if (this.getAddresses() != null) { - List intersections = new ArrayList(this.getAddresses()); - intersections.retainAll(outTransfer.getAddresses()); - if (intersections.isEmpty()) return false; // must have overlapping addresses - } - - // filter on subaddress indices - if (this.getSubaddressIndex() != null && (outTransfer.getSubaddressIndices() == null || !outTransfer.getSubaddressIndices().contains(this.getSubaddressIndex()))) return false; - if (this.getSubaddressIndices() != null) { - List intersections = new ArrayList(this.getSubaddressIndices()); - intersections.retainAll(outTransfer.getSubaddressIndices()); - if (intersections.isEmpty()) return false; // must have overlapping subaddress indices - } - - // filter on having destinations - if (this.hasDestinations() != null) { - if (this.hasDestinations() && outTransfer.getDestinations() == null) return false; - if (!this.hasDestinations() && outTransfer.getDestinations() != null) return false; - } - - // filter on destinations TODO: start with test for this -// if (this.getDestionations() != null && this.getDestionations() != transfer.getDestionations()) return false; - } - - // otherwise invalid type - else throw new RuntimeException("Transfer must be MoneroIncomingTransfer or MoneroOutgoingTransfer"); - - // filter with tx filter - if (this.getTxQuery() != null && !this.getTxQuery().meetsCriteria(transfer.getTx())) return false; - return true; - } - - // ------------------- OVERRIDE CO-VARIANT RETURN TYPES --------------------- - - @Override - public MoneroTransferQuery setTx(MoneroTxWallet tx) { - super.setTx(tx); - return this; - } - - @Override - public MoneroTransferQuery setAmount(BigInteger amount) { - super.setAmount(amount); - return this; - } - - @Override - public MoneroTransferQuery setAccountIndex(Integer accountIndex) { - super.setAccountIndex(accountIndex); - return this; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroTxQuery.java b/core/src/main/java/monero/wallet/model/MoneroTxQuery.java deleted file mode 100644 index dd57d405e5d..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroTxQuery.java +++ /dev/null @@ -1,496 +0,0 @@ -package monero.wallet.model; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import common.types.Filter; -import common.utils.GenUtils; -import monero.daemon.model.MoneroBlock; -import monero.daemon.model.MoneroOutput; - -/** - * Configures a query to retrieve transactions. - * - * All transactions are returned except those that do not meet the criteria defined in this query. - */ -public class MoneroTxQuery extends MoneroTxWallet implements Filter { - - private Boolean isOutgoing; - private Boolean isIncoming; - private List txIds; - private Boolean hasPaymentId; - private List paymentIds; - private Long height; - private Long minHeight; - private Long maxHeight; - private Boolean includeOutputs; - private MoneroTransferQuery transferQuery; - private MoneroOutputQuery outputQuery; - - public MoneroTxQuery() { - - } - - public MoneroTxQuery(final MoneroTxQuery query) { - super(query); - this.isOutgoing = query.isOutgoing; - this.isIncoming = query.isIncoming; - if (query.txIds != null) this.txIds = new ArrayList(query.txIds); - this.hasPaymentId = query.hasPaymentId; - if (query.paymentIds != null) this.paymentIds = new ArrayList(query.paymentIds); - this.height = query.height; - this.minHeight = query.minHeight; - this.maxHeight = query.maxHeight; - this.includeOutputs = query.includeOutputs; - if (query.transferQuery != null) { - this.transferQuery = new MoneroTransferQuery(query.transferQuery); - if (query.transferQuery.getTxQuery() == query) this.transferQuery.setTxQuery(this); - } - if (query.outputQuery != null) { - this.outputQuery = new MoneroOutputQuery(query.outputQuery); - if (query.outputQuery.getTxQuery() == query) this.outputQuery.setTxQuery(this) ; - } - } - - public MoneroTxQuery copy() { - return new MoneroTxQuery(this); - } - - @JsonProperty("isOutgoing") - public Boolean isOutgoing() { - return isOutgoing; - } - - public MoneroTxQuery setIsOutgoing(Boolean isOutgoing) { - this.isOutgoing = isOutgoing; - return this; - } - - @JsonProperty("isIncoming") - public Boolean isIncoming() { - return isIncoming; - } - - public MoneroTxQuery setIsIncoming(Boolean isIncoming) { - this.isIncoming = isIncoming; - return this; - } - - public List getTxIds() { - return txIds; - } - - public MoneroTxQuery setTxIds(List txIds) { - this.txIds = txIds; - return this; - } - - public MoneroTxQuery setTxIds(String... txIds) { - this.txIds = GenUtils.arrayToList(txIds); - return this; - } - - public MoneroTxQuery setTxId(String txId) { - return setTxIds(Arrays.asList(txId)); - } - - @JsonProperty("hasPaymentId") - public Boolean hasPaymentId() { - return hasPaymentId; - } - - public MoneroTxQuery setHasPaymentId(Boolean hasPaymentId) { - this.hasPaymentId = hasPaymentId; - return this; - } - - public List getPaymentIds() { - return paymentIds; - } - - public MoneroTxQuery setPaymentIds(List paymentIds) { - this.paymentIds = paymentIds; - return this; - } - - public MoneroTxQuery setPaymentId(String paymentId) { - return setPaymentIds(Arrays.asList(paymentId)); - } - - public Long getHeight() { - return height; - } - - public MoneroTxQuery setHeight(Long height) { - this.height = height; - return this; - } - - public Long getMinHeight() { - return minHeight; - } - - public MoneroTxQuery setMinHeight(Long minHeight) { - this.minHeight = minHeight; - return this; - } - - public Long getMaxHeight() { - return maxHeight; - } - - public MoneroTxQuery setMaxHeight(Long maxHeight) { - this.maxHeight = maxHeight; - return this; - } - - public Boolean getIncludeOutputs() { - return includeOutputs; - } - - public MoneroTxQuery setIncludeOutputs(Boolean includeOutputs) { - this.includeOutputs = includeOutputs; - return this; - } - - public MoneroTransferQuery getTransferQuery() { - return transferQuery; - } - - public MoneroTxQuery setTransferQuery(MoneroTransferQuery transferQuery) { - this.transferQuery = transferQuery; - return this; - } - - public MoneroOutputQuery getOutputQuery() { - return outputQuery; - } - - public MoneroTxQuery setOutputQuery(MoneroOutputQuery outputQuery) { - this.outputQuery = outputQuery; - return this; - } - - @Override - public boolean meetsCriteria(MoneroTxWallet tx) { - if (tx == null) return false; - - // filter on tx - if (this.getId() != null && !this.getId().equals(tx.getId())) return false; - if (this.getPaymentId() != null && !this.getPaymentId().equals(tx.getPaymentId())) return false; - if (this.isConfirmed() != null && this.isConfirmed() != tx.isConfirmed()) return false; - if (this.inTxPool() != null && this.inTxPool() != tx.inTxPool()) return false; - if (this.getDoNotRelay() != null && this.getDoNotRelay() != tx.getDoNotRelay()) return false; - if (this.isRelayed() != null && this.isRelayed() != tx.isRelayed()) return false; - if (this.isFailed() != null && this.isFailed() != tx.isFailed()) return false; - if (this.isMinerTx() != null && this.isMinerTx() != tx.isMinerTx()) return false; - - // at least one transfer must meet transfer query if defined - if (this.getTransferQuery() != null) { - boolean matchFound = false; - if (tx.getOutgoingTransfer() != null && this.getTransferQuery().meetsCriteria(tx.getOutgoingTransfer())) matchFound = true; - else if (tx.getIncomingTransfers() != null) { - for (MoneroTransfer incomingTransfer : tx.getIncomingTransfers()) { - if (this.getTransferQuery().meetsCriteria(incomingTransfer)) { - matchFound = true; - break; - } - } - } - if (!matchFound) return false; - } - - // at least one output must meet output query if defined - if (this.getOutputQuery() != null && !this.getOutputQuery().isDefault()) { - if (tx.getVouts() == null || tx.getVouts().isEmpty()) return false; - boolean matchFound = false; - for (MoneroOutputWallet vout : tx.getVoutsWallet()) { - if (this.getOutputQuery().meetsCriteria(vout)) { - matchFound = true; - break; - } - } - if (!matchFound) return false; - } - - // filter on having a payment id - if (this.hasPaymentId() != null) { - if (this.hasPaymentId() && tx.getPaymentId() == null) return false; - if (!this.hasPaymentId() && tx.getPaymentId() != null) return false; - } - - // filter on incoming - if (this.isIncoming() != null) { - if (this.isIncoming() && !tx.isIncoming()) return false; - if (!this.isIncoming() && tx.isIncoming()) return false; - } - - // filter on outgoing - if (this.isOutgoing() != null) { - if (this.isOutgoing() && !tx.isOutgoing()) return false; - if (!this.isOutgoing() && tx.isOutgoing()) return false; - } - - // filter on remaining fields - Long txHeight = tx.getBlock() == null ? null : tx.getBlock().getHeight(); - if (this.getTxIds() != null && !this.getTxIds().contains(tx.getId())) return false; - if (this.getPaymentIds() != null && !this.getPaymentIds().contains(tx.getPaymentId())) return false; - if (this.getHeight() != null && !this.getHeight().equals(txHeight)) return false; - if (this.getMinHeight() != null && (txHeight == null || txHeight < this.getMinHeight())) return false; - if (this.getMaxHeight() != null && (txHeight == null || txHeight > this.getMaxHeight())) return false; - - // transaction meets query criteria - return true; - } - - @Override - public String toString() { - throw new RuntimeException("Not implemented"); - } - - // ------------------- OVERRIDE CO-VARIANT RETURN TYPES --------------------- - - @Override - public MoneroTxQuery setIncomingTransfers(List incomingTransfers) { - super.setIncomingTransfers(incomingTransfers); - return this; - } - - @Override - public MoneroTxQuery setOutgoingTransfer(MoneroOutgoingTransfer outgoingTransfer) { - super.setOutgoingTransfer(outgoingTransfer); - return this; - } - - @Override - public MoneroTxQuery setVouts(List vouts) { - super.setVouts(vouts); - return this; - } - - @Override - public MoneroTxQuery setNote(String note) { - super.setNote(note); - return this; - } - - @Override - public MoneroTxQuery setBlock(MoneroBlock block) { - super.setBlock(block); - return this; - } - - @Override - public MoneroTxQuery setId(String id) { - super.setId(id); - return this; - } - - @Override - public MoneroTxQuery setVersion(Integer version) { - super.setVersion(version); - return this; - } - - @Override - public MoneroTxQuery setIsMinerTx(Boolean isMinerTx) { - super.setIsMinerTx(isMinerTx); - return this; - } - - @Override - public MoneroTxQuery setFee(BigInteger fee) { - super.setFee(fee); - return this; - } - - @Override - public MoneroTxQuery setMixin(Integer mixin) { - super.setMixin(mixin); - return this; - } - - @Override - public MoneroTxQuery setDoNotRelay(Boolean doNotRelay) { - super.setDoNotRelay(doNotRelay); - return this; - } - - @Override - public MoneroTxQuery setIsRelayed(Boolean isRelayed) { - super.setIsRelayed(isRelayed); - return this; - } - - @Override - public MoneroTxQuery setIsConfirmed(Boolean isConfirmed) { - super.setIsConfirmed(isConfirmed); - return this; - } - - @Override - public MoneroTxQuery setInTxPool(Boolean inTxPool) { - super.setInTxPool(inTxPool); - return this; - } - - @Override - public MoneroTxQuery setNumConfirmations(Long numConfirmations) { - super.setNumConfirmations(numConfirmations); - return this; - } - - @Override - public MoneroTxQuery setUnlockTime(Long unlockTime) { - super.setUnlockTime(unlockTime); - return this; - } - - @Override - public MoneroTxQuery setLastRelayedTimestamp(Long lastRelayedTimestamp) { - super.setLastRelayedTimestamp(lastRelayedTimestamp); - return this; - } - - @Override - public MoneroTxQuery setReceivedTimestamp(Long receivedTimestamp) { - super.setReceivedTimestamp(receivedTimestamp); - return this; - } - - @Override - public MoneroTxQuery setIsDoubleSpendSeen(Boolean isDoubleSpend) { - super.setIsDoubleSpendSeen(isDoubleSpend); - return this; - } - - @Override - public MoneroTxQuery setKey(String key) { - super.setKey(key); - return this; - } - - @Override - public MoneroTxQuery setFullHex(String hex) { - super.setFullHex(hex); - return this; - } - - @Override - public MoneroTxQuery setPrunedHex(String prunedHex) { - super.setPrunedHex(prunedHex); - return this; - } - - @Override - public MoneroTxQuery setPrunableHex(String prunableHex) { - super.setPrunableHex(prunableHex); - return this; - } - - @Override - public MoneroTxQuery setPrunableHash(String prunableHash) { - super.setPrunableHash(prunableHash); - return this; - } - - @Override - public MoneroTxQuery setSize(Long size) { - super.setSize(size); - return this; - } - - @Override - public MoneroTxQuery setWeight(Long weight) { - super.setWeight(weight); - return this; - } - - @Override - public MoneroTxQuery setVins(List vins) { - super.setVins(vins); - return this; - } - - @Override - public MoneroTxQuery setOutputIndices(List outputIndices) { - super.setOutputIndices(outputIndices); - return this; - } - - @Override - public MoneroTxQuery setMetadata(String metadata) { - super.setMetadata(metadata); - return this; - } - - @Override - public MoneroTxQuery setTxSet(MoneroTxSet commonTxSets) { - super.setTxSet(commonTxSets); - return this; - } - - @Override - public MoneroTxQuery setExtra(int[] extra) { - super.setExtra(extra); - return this; - } - - @Override - public MoneroTxQuery setRctSignatures(Object rctSignatures) { - super.setRctSignatures(rctSignatures); - return this; - } - - @Override - public MoneroTxQuery setRctSigPrunable(Object rctSigPrunable) { - super.setRctSigPrunable(rctSigPrunable); - return this; - } - - @Override - public MoneroTxQuery setIsKeptByBlock(Boolean isKeptByBlock) { - super.setIsKeptByBlock(isKeptByBlock); - return this; - } - - @Override - public MoneroTxQuery setIsFailed(Boolean isFailed) { - super.setIsFailed(isFailed); - return this; - } - - @Override - public MoneroTxQuery setLastFailedHeight(Long lastFailedHeight) { - super.setLastFailedHeight(lastFailedHeight); - return this; - } - - @Override - public MoneroTxQuery setLastFailedId(String lastFailedId) { - super.setLastFailedId(lastFailedId); - return this; - } - - @Override - public MoneroTxQuery setMaxUsedBlockHeight(Long maxUsedBlockHeight) { - super.setMaxUsedBlockHeight(maxUsedBlockHeight); - return this; - } - - @Override - public MoneroTxQuery setMaxUsedBlockId(String maxUsedBlockId) { - super.setMaxUsedBlockId(maxUsedBlockId); - return this; - } - - @Override - public MoneroTxQuery setSignatures(List signatures) { - super.setSignatures(signatures); - return this; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroTxSet.java b/core/src/main/java/monero/wallet/model/MoneroTxSet.java deleted file mode 100644 index 2e69077394a..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroTxSet.java +++ /dev/null @@ -1,140 +0,0 @@ -package monero.wallet.model; - -import static org.junit.Assert.assertNotNull; - -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonManagedReference; -import com.fasterxml.jackson.annotation.JsonProperty; - -import common.utils.GenUtils; -import monero.daemon.model.MoneroTx; -import monero.utils.MoneroUtils; - -/** - * Groups transactions who share common hex data which is needed in order to - * sign and submit the transactions. - * - * For example, multisig transactions created from sendSplit() share a common - * hex string which is needed in order to sign and submit the multisig - * transactions. - */ -public class MoneroTxSet { - - private List txs; - private String multisigTxHex; - private String unsignedTxHex; - private String signedTxHex; - - @JsonManagedReference("tx_set") - public List getTxs() { - return txs; - } - - @JsonProperty("txs") - public MoneroTxSet setTxs(List txs) { - this.txs = txs; - return this; - } - - @JsonIgnore - public MoneroTxSet setTxs(MoneroTxWallet... txs) { - this.txs = GenUtils.arrayToList(txs); - return this; - } - - public String getMultisigTxHex() { - return multisigTxHex; - } - - public MoneroTxSet setMultisigTxHex(String multisigTxHex) { - this.multisigTxHex = multisigTxHex; - return this; - } - - public String getUnsignedTxHex() { - return unsignedTxHex; - } - - public MoneroTxSet setUnsignedTxHex(String unsignedTxHex) { - this.unsignedTxHex = unsignedTxHex; - return this; - } - - public String getSignedTxHex() { - return signedTxHex; - } - - public MoneroTxSet setSignedTxHex(String signedTxHex) { - this.signedTxHex = signedTxHex; - return this; - } - - public MoneroTxSet merge(MoneroTxSet txSet) { - assertNotNull(txSet); - if (this == txSet) return this; - - // merge sets - this.setMultisigTxHex(MoneroUtils.reconcile(this.getMultisigTxHex(), txSet.getMultisigTxHex())); - this.setUnsignedTxHex(MoneroUtils.reconcile(this.getUnsignedTxHex(), txSet.getUnsignedTxHex())); - this.setSignedTxHex(MoneroUtils.reconcile(this.getSignedTxHex(), txSet.getSignedTxHex())); - - // merge txs - if (txSet.getTxs() != null) { - for (MoneroTxWallet tx : txSet.getTxs()) { - tx.setTxSet(this); - MoneroUtils.mergeTx(txs, tx); - } - } - - return this; - } - - @Override - public String toString() { - return toString(0); - } - - public String toString(int indent) { - StringBuilder sb = new StringBuilder(); - sb.append(MoneroUtils.kvLine("Multisig tx hex: ", getMultisigTxHex(), indent)); - sb.append(MoneroUtils.kvLine("Unsigned tx hex: ", getUnsignedTxHex(), indent)); - sb.append(MoneroUtils.kvLine("Signed tx hex: ", getSignedTxHex(), indent)); - if (getTxs() != null) { - sb.append(MoneroUtils.kvLine("Txs", "", indent)); - for (MoneroTx tx : getTxs()) { - sb.append(tx.toString(indent + 1) + "\n"); - } - } - return sb.toString(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((multisigTxHex == null) ? 0 : multisigTxHex.hashCode()); - result = prime * result + ((signedTxHex == null) ? 0 : signedTxHex.hashCode()); - result = prime * result + ((unsignedTxHex == null) ? 0 : unsignedTxHex.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - MoneroTxSet other = (MoneroTxSet) obj; - if (multisigTxHex == null) { - if (other.multisigTxHex != null) return false; - } else if (!multisigTxHex.equals(other.multisigTxHex)) return false; - if (signedTxHex == null) { - if (other.signedTxHex != null) return false; - } else if (!signedTxHex.equals(other.signedTxHex)) return false; - if (unsignedTxHex == null) { - if (other.unsignedTxHex != null) return false; - } else if (!unsignedTxHex.equals(other.unsignedTxHex)) return false; - return true; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroTxWallet.java b/core/src/main/java/monero/wallet/model/MoneroTxWallet.java deleted file mode 100644 index 935ac208822..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroTxWallet.java +++ /dev/null @@ -1,514 +0,0 @@ -package monero.wallet.model; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonBackReference; -import com.fasterxml.jackson.annotation.JsonManagedReference; -import com.fasterxml.jackson.annotation.JsonProperty; - -import monero.daemon.model.MoneroBlock; -import monero.daemon.model.MoneroOutput; -import monero.daemon.model.MoneroTx; -import monero.utils.MoneroException; -import monero.utils.MoneroUtils; - -/** - * Models a Monero transaction with wallet extensions. - */ -public class MoneroTxWallet extends MoneroTx { - - private MoneroTxSet txSet; - private List incomingTransfers; - private MoneroOutgoingTransfer outgoingTransfer; - private String note; - - public MoneroTxWallet() { - // nothing to initialize - } - - public MoneroTxWallet(final MoneroTxWallet tx) { - super(tx); - this.txSet = tx.txSet; - if (tx.incomingTransfers != null) { - this.incomingTransfers = new ArrayList(); - for (MoneroIncomingTransfer transfer : tx.incomingTransfers) { - this.incomingTransfers.add(transfer.copy().setTx(this)); - } - } - if (tx.outgoingTransfer != null) this.outgoingTransfer = tx.outgoingTransfer.copy().setTx(this); - this.note = tx.note; - } - - public MoneroTxWallet copy() { - return new MoneroTxWallet(this); - } - - @JsonBackReference("tx_set") - public MoneroTxSet getTxSet() { - return txSet; - } - - public MoneroTxWallet setTxSet(MoneroTxSet txSet) { - this.txSet = txSet; - return this; - } - - @JsonProperty("isOutgoing") - public Boolean isOutgoing() { - return getOutgoingTransfer() != null; - } - - @JsonProperty("isIncoming") - public Boolean isIncoming() { - return getIncomingTransfers() != null && !getIncomingTransfers().isEmpty(); - } - - public BigInteger getIncomingAmount() { - if (getIncomingTransfers() == null) return null; - BigInteger incomingAmt = BigInteger.valueOf(0); - for (MoneroTransfer transfer : this.getIncomingTransfers()) incomingAmt = incomingAmt.add(transfer.getAmount()); - return incomingAmt; - } - - public BigInteger getOutgoingAmount() { - return getOutgoingTransfer() != null ? getOutgoingTransfer().getAmount() : null; - } - - @JsonManagedReference - public List getIncomingTransfers() { - return incomingTransfers; - } - - public MoneroTxWallet setIncomingTransfers(List incomingTransfers) { - this.incomingTransfers = incomingTransfers; - return this; - } - - @JsonManagedReference - public MoneroOutgoingTransfer getOutgoingTransfer() { - return outgoingTransfer; - } - - public MoneroTxWallet setOutgoingTransfer(MoneroOutgoingTransfer outgoingTransfer) { - this.outgoingTransfer = outgoingTransfer; - return this; - } - - /** - * Returns a copy of this model's vouts as a list of type MoneroOutputWallet. - * - * @return vouts of type MoneroOutputWallet - */ - public List getVoutsWallet() { - List vouts = getVouts(); - if (vouts == null) return null; - List voutsWallet = new ArrayList(); - for (MoneroOutput vout : getVouts()) { - voutsWallet.add((MoneroOutputWallet) vout); - } - return voutsWallet; - } - - /** - * Set the tx's vouts (MoneroOutputWallet) which contain information relative - * to a wallet. - * - * Callers must cast to extended type (MoneroOutput) because Java - * paramaterized types do not recognize inheritance. - * - * @param vouts are MoneroOutputWallets to set for the wallet tx - * @return MoneroTxWallet is a reference to this tx for chaining - */ - public MoneroTxWallet setVouts(List vouts) { - - // validate that all vouts are wallet outputs - if (vouts != null) { - for (MoneroOutput vout : vouts) { - if (!(vout instanceof MoneroOutputWallet)) throw new MoneroException("Wallet transaction vouts must be of type MoneroOutputWallet"); - } - } - super.setVouts(vouts); - return this; - } - - /** - * Set vouts with compile-time binding to MoneroOutputWallet for deserialization. - * - * @param outputs are the tx's vouts - * @return MoneroTxWallet is a reference to this tx for chaining - */ - @JsonProperty("vouts") - public MoneroTxWallet setVoutsWallet(List outputs) { - return setVouts(new ArrayList(outputs)); - } - - public String getNote() { - return note; - } - - public MoneroTxWallet setNote(String note) { - this.note = note; - return this; - } - - public MoneroTxWallet merge(MoneroTx tx) { - if (tx != null && !(tx instanceof MoneroTxWallet)) throw new MoneroException("Wallet transaction must be merged with type MoneroTxWallet"); - return merge((MoneroTxWallet) tx); - } - - /** - * Updates this transaction by merging the latest information from the given - * transaction. - * - * Merging can modify or build references to the transaction given so it - * should not be re-used or it should be copied before calling this method. - * - * @param tx is the transaction to merge into this transaction - * @return this tx for chaining - */ - public MoneroTxWallet merge(MoneroTxWallet tx) { - if (!(tx instanceof MoneroTxWallet)) throw new MoneroException("Wallet transaction must be merged with type MoneroTxWallet"); - if (this == tx) return this; - - // merge base classes - super.merge(tx); - - // merge tx set if they're different which comes back to merging txs - if (txSet != tx.getTxSet()) { - if (txSet == null) { - txSet = new MoneroTxSet(); - txSet.setTxs(this); - } - if (tx.getTxSet() == null) { - tx.setTxSet(new MoneroTxSet()); - tx.getTxSet().setTxs(tx); - } - txSet.merge(tx.getTxSet()); - return this; - } - - // merge incoming transfers - if (tx.getIncomingTransfers() != null) { - if (this.getIncomingTransfers() == null) this.setIncomingTransfers(new ArrayList()); - for (MoneroIncomingTransfer transfer : tx.getIncomingTransfers()) { - transfer.setTx(this); - mergeIncomingTransfer(this.getIncomingTransfers(), transfer); - } - } - - // merge outgoing transfer - if (tx.getOutgoingTransfer() != null) { - tx.getOutgoingTransfer().setTx(this); - if (this.getOutgoingTransfer() == null) this.setOutgoingTransfer(tx.getOutgoingTransfer()); - else this.getOutgoingTransfer().merge(tx.getOutgoingTransfer()); - } - - // merge simple extensions - this.setNote(MoneroUtils.reconcile(this.getNote(), tx.getNote())); - - return this; // for chaining - } - - public String toString() { - return toString(0, false); - } - - public String toString(int indent) { - return toString(indent, false); - } - - public String toString(int indent, boolean oneLine) { - StringBuilder sb = new StringBuilder(); - - // represent tx with one line string - // TODO: proper csv export - if (oneLine) { - sb.append(this.getId() + ", "); - sb.append((this.isConfirmed() ? this.getBlock().getTimestamp() : this.getReceivedTimestamp()) + ", "); - sb.append(this.isConfirmed() + ", "); - sb.append((this.getOutgoingAmount() != null? this.getOutgoingAmount().toString() : "") + ", "); - sb.append(this.getIncomingAmount() != null ? this.getIncomingAmount().toString() : ""); - return sb.toString(); - } - - // otherwise stringify all fields - sb.append(super.toString(indent) + "\n"); - sb.append(MoneroUtils.kvLine("Is incoming", this.isIncoming(), indent)); - sb.append(MoneroUtils.kvLine("Incoming amount", this.getIncomingAmount(), indent)); - if (this.getIncomingTransfers() != null) { - sb.append(MoneroUtils.kvLine("Incoming transfers", "", indent)); - for (int i = 0; i < this.getIncomingTransfers().size(); i++) { - sb.append(MoneroUtils.kvLine(i + 1, "", indent + 1)); - sb.append(this.getIncomingTransfers().get(i).toString(indent + 2) + "\n"); - } - } - sb.append(MoneroUtils.kvLine("Is outgoing", this.isOutgoing(), indent)); - sb.append(MoneroUtils.kvLine("Outgoing amount", this.getOutgoingAmount(), indent)); - if (this.getOutgoingTransfer() != null) { - sb.append(MoneroUtils.kvLine("Outgoing transfer", "", indent)); - sb.append(this.getOutgoingTransfer().toString(indent + 1) + "\n"); - } - sb.append(MoneroUtils.kvLine("Note: ", this.getNote(), indent)); - String str = sb.toString(); - return str.substring(0, str.length() - 1); // strip last newline - } - - // private helper to merge transfers - private static void mergeIncomingTransfer(List transfers, MoneroIncomingTransfer transfer) { - for (MoneroIncomingTransfer aTransfer : transfers) { - if (aTransfer.getAccountIndex() == transfer.getAccountIndex() && aTransfer.getSubaddressIndex() == transfer.getSubaddressIndex()) { - aTransfer.merge(transfer); - return; - } - } - transfers.add(transfer); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((incomingTransfers == null) ? 0 : incomingTransfers.hashCode()); - result = prime * result + ((note == null) ? 0 : note.hashCode()); - result = prime * result + ((outgoingTransfer == null) ? 0 : outgoingTransfer.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!super.equals(obj)) return false; - if (getClass() != obj.getClass()) return false; - MoneroTxWallet other = (MoneroTxWallet) obj; - if (incomingTransfers == null) { - if (other.incomingTransfers != null) return false; - } else if (!incomingTransfers.equals(other.incomingTransfers)) return false; - if (note == null) { - if (other.note != null) return false; - } else if (!note.equals(other.note)) return false; - if (outgoingTransfer == null) { - if (other.outgoingTransfer != null) return false; - } else if (!outgoingTransfer.equals(other.outgoingTransfer)) return false; - return true; - } - - // ------------------- OVERRIDE CO-VARIANT RETURN TYPES --------------------- - - @Override - public MoneroTxWallet setBlock(MoneroBlock block) { - super.setBlock(block); - return this; - } - - @Override - public MoneroTxWallet setId(String id) { - super.setId(id); - return this; - } - - @Override - public MoneroTxWallet setVersion(Integer version) { - super.setVersion(version); - return this; - } - - @Override - public MoneroTxWallet setIsMinerTx(Boolean isMinerTx) { - super.setIsMinerTx(isMinerTx); - return this; - } - - @Override - public MoneroTxWallet setPaymentId(String paymentId) { - super.setPaymentId(paymentId); - return this; - } - - @Override - public MoneroTxWallet setFee(BigInteger fee) { - super.setFee(fee); - return this; - } - - @Override - public MoneroTxWallet setMixin(Integer mixin) { - super.setMixin(mixin); - return this; - } - - @Override - public MoneroTxWallet setDoNotRelay(Boolean doNotRelay) { - super.setDoNotRelay(doNotRelay); - return this; - } - - @Override - public MoneroTxWallet setIsRelayed(Boolean isRelayed) { - super.setIsRelayed(isRelayed); - return this; - } - - @Override - public MoneroTxWallet setIsConfirmed(Boolean isConfirmed) { - super.setIsConfirmed(isConfirmed); - return this; - } - - @Override - public MoneroTxWallet setInTxPool(Boolean inTxPool) { - super.setInTxPool(inTxPool); - return this; - } - - @Override - public MoneroTxWallet setNumConfirmations(Long numConfirmations) { - super.setNumConfirmations(numConfirmations); - return this; - } - - @Override - public MoneroTxWallet setUnlockTime(Long unlockTime) { - super.setUnlockTime(unlockTime); - return this; - } - - @Override - public MoneroTxWallet setLastRelayedTimestamp(Long lastRelayedTimestamp) { - super.setLastRelayedTimestamp(lastRelayedTimestamp); - return this; - } - - @Override - public MoneroTxWallet setReceivedTimestamp(Long receivedTimestamp) { - super.setReceivedTimestamp(receivedTimestamp); - return this; - } - - @Override - public MoneroTxWallet setIsDoubleSpendSeen(Boolean isDoubleSpend) { - super.setIsDoubleSpendSeen(isDoubleSpend); - return this; - } - - @Override - public MoneroTxWallet setKey(String key) { - super.setKey(key); - return this; - } - - @Override - public MoneroTxWallet setFullHex(String hex) { - super.setFullHex(hex); - return this; - } - - @Override - public MoneroTxWallet setPrunedHex(String prunedHex) { - super.setPrunedHex(prunedHex); - return this; - } - - @Override - public MoneroTxWallet setPrunableHex(String prunableHex) { - super.setPrunableHex(prunableHex); - return this; - } - - @Override - public MoneroTxWallet setPrunableHash(String prunableHash) { - super.setPrunableHash(prunableHash); - return this; - } - - @Override - public MoneroTxWallet setSize(Long size) { - super.setSize(size); - return this; - } - - @Override - public MoneroTxWallet setWeight(Long weight) { - super.setWeight(weight); - return this; - } - - @Override - public MoneroTxWallet setVins(List vins) { - super.setVins(vins); - return this; - } - - @Override - public MoneroTxWallet setOutputIndices(List outputIndices) { - super.setOutputIndices(outputIndices); - return this; - } - - @Override - public MoneroTxWallet setMetadata(String metadata) { - super.setMetadata(metadata); - return this; - } - - @Override - public MoneroTxWallet setExtra(int[] extra) { - super.setExtra(extra); - return this; - } - - @Override - public MoneroTxWallet setRctSignatures(Object rctSignatures) { - super.setRctSignatures(rctSignatures); - return this; - } - - @Override - public MoneroTxWallet setRctSigPrunable(Object rctSigPrunable) { - super.setRctSigPrunable(rctSigPrunable); - return this; - } - - @Override - public MoneroTxWallet setIsKeptByBlock(Boolean isKeptByBlock) { - super.setIsKeptByBlock(isKeptByBlock); - return this; - } - - @Override - public MoneroTxWallet setIsFailed(Boolean isFailed) { - super.setIsFailed(isFailed); - return this; - } - - @Override - public MoneroTxWallet setLastFailedHeight(Long lastFailedHeight) { - super.setLastFailedHeight(lastFailedHeight); - return this; - } - - @Override - public MoneroTxWallet setLastFailedId(String lastFailedId) { - super.setLastFailedId(lastFailedId); - return this; - } - - @Override - public MoneroTxWallet setMaxUsedBlockHeight(Long maxUsedBlockHeight) { - super.setMaxUsedBlockHeight(maxUsedBlockHeight); - return this; - } - - @Override - public MoneroTxWallet setMaxUsedBlockId(String maxUsedBlockId) { - super.setMaxUsedBlockId(maxUsedBlockId); - return this; - } - - @Override - public MoneroTxWallet setSignatures(List signatures) { - super.setSignatures(signatures); - return this; - } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroWalletListener.java b/core/src/main/java/monero/wallet/model/MoneroWalletListener.java deleted file mode 100644 index 3935f5fe818..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroWalletListener.java +++ /dev/null @@ -1,19 +0,0 @@ -package monero.wallet.model; - -/** - * Provides default handling for wallet notifications. - */ -public class MoneroWalletListener implements MoneroWalletListenerI { - - @Override - public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { } - - @Override - public void onNewBlock(long height) { } - - @Override - public void onOutputReceived(MoneroOutputWallet output) { } - - @Override - public void onOutputSpent(MoneroOutputWallet output) { } -} diff --git a/core/src/main/java/monero/wallet/model/MoneroWalletListenerI.java b/core/src/main/java/monero/wallet/model/MoneroWalletListenerI.java deleted file mode 100644 index bb8470f36e6..00000000000 --- a/core/src/main/java/monero/wallet/model/MoneroWalletListenerI.java +++ /dev/null @@ -1,28 +0,0 @@ -package monero.wallet.model; - -/** - * Interface to receive wallet notifications. - */ -public interface MoneroWalletListenerI extends MoneroSyncListener { - - /** - * Invoked when a new block is added to the chain. - * - * @param height is the height of the block added to the chain - */ - public void onNewBlock(long height); - - /** - * Invoked when the wallet receives an output. - * - * @param output is the incoming output to the wallet - */ - public void onOutputReceived(MoneroOutputWallet output); - - /** - * Invoked when the wallet spends an output. - * - * @param output the outgoing transfer from the wallet - */ - public void onOutputSpent(MoneroOutputWallet output); -} \ No newline at end of file diff --git a/core/src/test/java/bisq/core/xmr/jsonrpc/MoneroRpcConnectionTest.java b/core/src/test/java/bisq/core/xmr/jsonrpc/MoneroRpcConnectionTest.java new file mode 100644 index 00000000000..dee69441642 --- /dev/null +++ b/core/src/test/java/bisq/core/xmr/jsonrpc/MoneroRpcConnectionTest.java @@ -0,0 +1,63 @@ +package bisq.core.xmr.jsonrpc; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; + +import bisq.core.xmr.jsonrpc.result.MoneroTransfer; +import bisq.core.xmr.jsonrpc.result.MoneroTx; + +public class MoneroRpcConnectionTest { + private Logger log = LoggerFactory.getLogger(MoneroRpcConnectionTest.class); + public static final Gson GSON = new Gson(); + + @Test + public void testExecute() { + MoneroRpcConnection connection = new MoneroRpcConnection("http://localhost:29088", "rpc_user", "rpcpassword123"); + MoneroWalletRpc walletRpc = new MoneroWalletRpc(connection); + + log.info("walletRpc.getPrimaryAddress => {}", walletRpc.getPrimaryAddress()); + + log.info("walletRpc.getBalance => {}", walletRpc.getBalance()); + + log.info("walletRpc.getUnlockedBalance => {}", walletRpc.getUnlockedBalance()); + + Map destination = new HashMap<>(); + destination.put("amount", new BigInteger("1300000000000")); + destination.put("address", "A19Nu2WbrA2f8aJrqJAjLh45mw7Nuft3BCnNBv2a4u3qigMR1ytdgGJLoJLzF6PkQe1Cs36CxagmoKbSTCPMgQ7eCFhFTiy"); + List> destinations = new ArrayList>(); + destinations.add(destination); + Map request = new HashMap<>(); + request.put("destinations", destinations); + request.put("priority", MoneroSendPriority.NORMAL.ordinal()); + request.put("payment_id", MoneroWalletRpc.generatePaymentId()); + request.put("get_tx_key", true); + request.put("get_tx_hex", false); + request.put("do_not_relay", true); + request.put("get_tx_metadata", true); + + MoneroTx moneroTx = walletRpc.send(request); + log.info("walletRpc.send => {}", moneroTx); + + log.info("walletRpc.txHash => {}", walletRpc.relayTx(moneroTx.getTxMetadata())); + + List transfers = walletRpc.getTxs(null); + log.info("walletRpc.transfers => {}", transfers.size()); + + String txId = "fb43267b69c165f8143a599b58254ed5b695dac3fa778266b78f75e2c611ed1e"; + String message = "One of the incoming transactions"; + String signature = walletRpc.getSpendProof(txId, message); + log.info("walletRpc.spendProof => {}", signature); + + boolean good = walletRpc.checkSpendProof(txId, message, signature); + log.info("walletRpc.checkSpendProof => {}", good); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java index 6e1119e5194..225aa8e0129 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java @@ -35,6 +35,7 @@ import bisq.common.util.Tuple4; import bisq.core.locale.Res; import bisq.core.xmr.XmrFormatter; +import bisq.core.xmr.jsonrpc.MoneroSendPriority; import bisq.core.xmr.wallet.XmrWalletRpcWrapper; import bisq.core.xmr.wallet.listeners.WalletUiListener; import bisq.desktop.Navigation; @@ -60,8 +61,6 @@ import javafx.scene.control.Label; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; -import monero.wallet.model.MoneroSendPriority; -import monero.wallet.model.MoneroTxWallet; @FxmlView public class XmrSendView extends ActivatableView implements WalletUiListener { @@ -152,7 +151,7 @@ public void onUpdateBalances(HashMap walletRpcData) { } else { showPublishTxPopup(amountToSend, receiversAddressInputTextField.getText(), fee, size, sizeKbs, amountToSend, xmrFormatter, () -> { - MoneroTxWallet txToRelay = (MoneroTxWallet) walletRpcData.get("txToRelay"); + String txToRelay = (String) walletRpcData.get("txToRelay"); HashMap dataToRelay = new HashMap<>(); dataToRelay.put("txToRelay", txToRelay); walletWrapper.relayTx(XmrSendView.this, dataToRelay); diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java index 58228a68815..8f99344a631 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java @@ -55,8 +55,6 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.util.Callback; -import monero.wallet.MoneroWalletRpc; -import monero.wallet.model.MoneroTxWallet; @FxmlView public class XmrTxView extends ActivatableView implements WalletUiListener { @@ -161,8 +159,8 @@ public void onUpdateBalances(HashMap walletRpcData) { } private void openTxInBlockExplorer(XmrTxListItem item) { - if (item.getTxId() != null) - GUIUtil.openWebPage("https://testnet.xmrchain.com/search?value=" + item.getTxId(), false);//TODO(niyid) Change from hardcoded URL + if (item.getId() != null) + GUIUtil.openWebPage("https://testnet.xmrchain.com/search?value=" + item.getId(), false);//TODO(niyid) Change from hardcoded URL } private void addDateColumn() { @@ -218,7 +216,7 @@ public void updateItem(final XmrTxListItem item, boolean empty) { super.updateItem(item, empty); if (item != null && !empty) { - String transactionId = item.getTxId(); + String transactionId = item.getId(); hyperlinkWithIcon = new HyperlinkWithIcon(transactionId, MaterialDesignIcon.LINK); hyperlinkWithIcon.setOnAction(event -> openTxInBlockExplorer(item)); hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForTx", transactionId))); @@ -321,35 +319,6 @@ public void updateItem(final XmrTxListItem item, boolean empty) { }); tableView.getColumns().add(column); } - - private void addMixinColumn() { - TableColumn column = new AutoTooltipTableColumn<>(Res.get("shared.account.wallet.tx.item.mixin")); - - column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue())); - column.setMinWidth(60); - column.setCellFactory( - new Callback<>() { - - @Override - public TableCell call(TableColumn column) { - return new TableCell<>() { - @Override - public void updateItem(final XmrTxListItem item, boolean empty) { - super.updateItem(item, empty); - - if (item != null && !empty && item.getMixin() != null) { - String mixin = Integer.toString(item.getMixin()); - setText(mixin); - } else { - setText(""); - } - } - }; - } - }); - tableView.getColumns().add(column); - } private void addConfirmedColumn() { TableColumn column = new AutoTooltipTableColumn<>(Res.get("shared.account.wallet.tx.item.confirmed")); @@ -372,8 +341,8 @@ public void updateItem(final XmrTxListItem item, boolean empty) { if (item != null && !empty) { String confirmed = item.isConfirmed() ? Res.get("shared.yes") : Res.get("shared.no"); hyperlinkWithIcon = new HyperlinkWithIcon(confirmed, MaterialDesignIcon.TICKET_CONFIRMATION); - hyperlinkWithIcon.setOnAction(e -> showTxProof(item.getTxId(), "Transaction at " + xmrFormatter.formatDateTime(item.getDate()))); - hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openTxProof", item.getTxId()))); + hyperlinkWithIcon.setOnAction(e -> showTxProof(item.getId(), "Transaction at " + xmrFormatter.formatDateTime(item.getDate()))); + hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openTxProof", item.getId()))); setGraphic(hyperlinkWithIcon); } else { setGraphic(null); From 1235780245afd280a70b19c680648c44e2515240 Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Fri, 20 Sep 2019 02:15:27 +0100 Subject: [PATCH 19/22] Replacement of monero-java with compact version complete. --- .../bisq/core/xmr/wallet/XmrTxListItem.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java b/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java index 357d2bfc196..e19588c55b4 100644 --- a/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java +++ b/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java @@ -34,7 +34,7 @@ public class XmrTxListItem { @Getter - private final String txId; + private final String id; @Getter private final Date date; @Getter @@ -52,15 +52,15 @@ public class XmrTxListItem { @Getter private String destinationAddress; - public XmrTxListItem(MoneroTransfer txWallet) { - txId = txWallet.getId(); - paymentId = txWallet.getPaymentId(); - Long timestamp = txWallet.getTimestamp(); + public XmrTxListItem(MoneroTransfer transfer) { + id = transfer.getId(); + paymentId = transfer.getPaymentId(); + Long timestamp = transfer.getTimestamp(); date = timestamp != null && timestamp != 0 ? Date.from(Instant.ofEpochSecond(timestamp)) : null; - confirmed = txWallet.getConfirmations() >= txWallet.getSuggestedConfirmationsThreshold(); - confirmations = txWallet.getConfirmations(); - unlockTime = txWallet.getUnlockTime(); - amount = txWallet.getAmount(); - direction = "in".equals(txWallet.getType()) ? Res.get("shared.account.wallet.tx.item.in") : Res.get("shared.account.wallet.tx.item.out"); + confirmed = transfer.getConfirmations() >= transfer.getSuggestedConfirmationsThreshold(); + confirmations = transfer.getConfirmations(); + unlockTime = transfer.getUnlockTime(); + amount = transfer.getAmount(); + direction = "in".equals(transfer.getType()) ? Res.get("shared.account.wallet.tx.item.in") : Res.get("shared.account.wallet.tx.item.out"); } } From 63cff665d3370d8d5fcbed15fde7b65945fa7369 Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Fri, 20 Sep 2019 02:25:19 +0100 Subject: [PATCH 20/22] Test ignore for now. --- .../java/bisq/core/xmr/jsonrpc/MoneroRpcConnectionTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/test/java/bisq/core/xmr/jsonrpc/MoneroRpcConnectionTest.java b/core/src/test/java/bisq/core/xmr/jsonrpc/MoneroRpcConnectionTest.java index dee69441642..fb8db8cf883 100644 --- a/core/src/test/java/bisq/core/xmr/jsonrpc/MoneroRpcConnectionTest.java +++ b/core/src/test/java/bisq/core/xmr/jsonrpc/MoneroRpcConnectionTest.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; +import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,6 +21,7 @@ public class MoneroRpcConnectionTest { public static final Gson GSON = new Gson(); @Test + @Ignore public void testExecute() { MoneroRpcConnection connection = new MoneroRpcConnection("http://localhost:29088", "rpc_user", "rpcpassword123"); MoneroWalletRpc walletRpc = new MoneroWalletRpc(connection); From 4596d29a67dfa1198394e65c1e2486175aa40e43 Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Fri, 20 Sep 2019 03:23:02 +0100 Subject: [PATCH 21/22] Display destination address; filter out transactions older than 90 days. --- .../bisq/core/xmr/wallet/XmrTxListItem.java | 1 + .../core/xmr/wallet/XmrWalletRpcWrapper.java | 2 +- .../xmr/jsonrpc/MoneroRpcConnectionTest.java | 39 +++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java b/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java index e19588c55b4..53946c60e72 100644 --- a/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java +++ b/core/src/main/java/bisq/core/xmr/wallet/XmrTxListItem.java @@ -62,5 +62,6 @@ public XmrTxListItem(MoneroTransfer transfer) { unlockTime = transfer.getUnlockTime(); amount = transfer.getAmount(); direction = "in".equals(transfer.getType()) ? Res.get("shared.account.wallet.tx.item.in") : Res.get("shared.account.wallet.tx.item.out"); + destinationAddress = transfer.getAddress(); } } diff --git a/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java b/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java index 0c779e24e8d..98d1d4b3440 100644 --- a/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java +++ b/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java @@ -138,7 +138,7 @@ private List transformTxWallet(List txList) { @Override public boolean test(MoneroTransfer t) { //Check if transaction occurred less than 90 days ago - return (new Date().getTime() - Date.from(Instant.ofEpochSecond(t.getTimestamp())).getTime()) <= 90 * 24 * 3600 * 1000; + return (new Date().getTime() - Date.from(Instant.ofEpochSecond(t.getTimestamp())).getTime()) <= 90 * 24 * 3600 * 1000l; } }; List list = new ArrayList<>(); diff --git a/core/src/test/java/bisq/core/xmr/jsonrpc/MoneroRpcConnectionTest.java b/core/src/test/java/bisq/core/xmr/jsonrpc/MoneroRpcConnectionTest.java index fb8db8cf883..924377e05be 100644 --- a/core/src/test/java/bisq/core/xmr/jsonrpc/MoneroRpcConnectionTest.java +++ b/core/src/test/java/bisq/core/xmr/jsonrpc/MoneroRpcConnectionTest.java @@ -1,7 +1,9 @@ package bisq.core.xmr.jsonrpc; import java.math.BigInteger; +import java.time.Instant; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -32,27 +34,24 @@ public void testExecute() { log.info("walletRpc.getUnlockedBalance => {}", walletRpc.getUnlockedBalance()); - Map destination = new HashMap<>(); - destination.put("amount", new BigInteger("1300000000000")); - destination.put("address", "A19Nu2WbrA2f8aJrqJAjLh45mw7Nuft3BCnNBv2a4u3qigMR1ytdgGJLoJLzF6PkQe1Cs36CxagmoKbSTCPMgQ7eCFhFTiy"); - List> destinations = new ArrayList>(); - destinations.add(destination); - Map request = new HashMap<>(); - request.put("destinations", destinations); - request.put("priority", MoneroSendPriority.NORMAL.ordinal()); - request.put("payment_id", MoneroWalletRpc.generatePaymentId()); - request.put("get_tx_key", true); - request.put("get_tx_hex", false); - request.put("do_not_relay", true); - request.put("get_tx_metadata", true); +// Map destination = new HashMap<>(); +// destination.put("amount", new BigInteger("1300000000000")); +// destination.put("address", "A19Nu2WbrA2f8aJrqJAjLh45mw7Nuft3BCnNBv2a4u3qigMR1ytdgGJLoJLzF6PkQe1Cs36CxagmoKbSTCPMgQ7eCFhFTiy"); +// List> destinations = new ArrayList>(); +// destinations.add(destination); +// Map request = new HashMap<>(); +// request.put("destinations", destinations); +// request.put("priority", MoneroSendPriority.NORMAL.ordinal()); +// request.put("payment_id", MoneroWalletRpc.generatePaymentId()); +// request.put("get_tx_key", true); +// request.put("get_tx_hex", false); +// request.put("do_not_relay", true); +// request.put("get_tx_metadata", true); - MoneroTx moneroTx = walletRpc.send(request); - log.info("walletRpc.send => {}", moneroTx); - - log.info("walletRpc.txHash => {}", walletRpc.relayTx(moneroTx.getTxMetadata())); - - List transfers = walletRpc.getTxs(null); - log.info("walletRpc.transfers => {}", transfers.size()); +// MoneroTx moneroTx = walletRpc.send(request); +// log.info("walletRpc.send => {}", moneroTx); +// +// log.info("walletRpc.txHash => {}", walletRpc.relayTx(moneroTx.getTxMetadata())); String txId = "fb43267b69c165f8143a599b58254ed5b695dac3fa778266b78f75e2c611ed1e"; String message = "One of the incoming transactions"; From b8e84aec497f4efd780828b9aebfd3cc88cb11c9 Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Fri, 20 Sep 2019 19:06:03 +0100 Subject: [PATCH 22/22] References to Log4J and JUnit in non test-case classes removed. --- build.gradle | 7 - .../core/xmr/jsonrpc/MoneroException.java | 4 +- .../core/xmr/jsonrpc/MoneroRpcConnection.java | 6 +- .../bisq/core/xmr/jsonrpc/MoneroUtils.java | 559 ++++++++++-------- .../core/xmr/wallet/XmrWalletRpcWrapper.java | 3 + .../wallet/monero/send/XmrSendView.java | 20 +- .../content/wallet/monero/tx/XmrTxView.java | 7 +- 7 files changed, 319 insertions(+), 287 deletions(-) diff --git a/build.gradle b/build.gradle index c3734878b71..ea1c0ea83dc 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,6 @@ configure(subprojects) { apply plugin: 'kotlin' apply plugin: 'java' apply plugin: 'com.google.osdetector' - apply plugin: 'maven' sourceCompatibility = 1.10 @@ -159,8 +158,6 @@ configure(project(':assets')) { compile "com.google.guava:guava:$guavaVersion" compile "org.slf4j:slf4j-api:$slf4jVersion" compile "org.apache.commons:commons-lang3:$langVersion" - compile "junit:junit:$junitVersion" - compile "log4j:log4j:1.2.17" } } @@ -178,8 +175,6 @@ configure(project(':common')) { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" - compile "junit:junit:$junitVersion" - compile "log4j:log4j:1.2.17" compile "org.openjfx:javafx-base:11:$os" compile "org.openjfx:javafx-graphics:11:$os" compile "com.google.protobuf:protobuf-java:$protobufVersion" @@ -236,8 +231,6 @@ configure(project(':core')) { dependencies { compile project(':assets') compile project(':p2p') - compile "junit:junit:$junitVersion" - compile "log4j:log4j:1.2.17" compile "net.sf.jopt-simple:jopt-simple:$joptVersion" compile("network.bisq.btcd-cli4j:btcd-cli4j-core:$btcdCli4jVersion") { exclude(module: 'slf4j-api') diff --git a/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroException.java b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroException.java index 1eb5c952a24..e85365a1073 100644 --- a/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroException.java +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroException.java @@ -1,6 +1,6 @@ package bisq.core.xmr.jsonrpc; -import static org.junit.Assert.assertNotNull; +import org.springframework.util.Assert; /** * Exception when interacting with a Monero wallet or daemon. @@ -37,7 +37,7 @@ public MoneroException(String message) { */ public MoneroException(String message, Integer code) { super(message); - assertNotNull("Exeption message cannot be null", message); + Assert.notNull(message, "Exeption message cannot be null"); this.code = code; } diff --git a/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroRpcConnection.java b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroRpcConnection.java index 72165815a21..592207c4ea3 100644 --- a/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroRpcConnection.java +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroRpcConnection.java @@ -16,12 +16,12 @@ import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.Gson; /** * Maintains a connection and sends requests to a Monero RPC API. @@ -29,7 +29,7 @@ public class MoneroRpcConnection { // logger - private static final Logger LOGGER = Logger.getLogger(MoneroRpcConnection.class); + private static final Logger LOGGER = LoggerFactory.getLogger(MoneroRpcConnection.class); // custom mapper to deserialize integers to BigIntegers public static ObjectMapper MAPPER; diff --git a/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroUtils.java b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroUtils.java index 46b7dc1982d..265808f8b56 100644 --- a/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroUtils.java +++ b/core/src/main/java/bisq/core/xmr/jsonrpc/MoneroUtils.java @@ -1,274 +1,311 @@ package bisq.core.xmr.jsonrpc; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - import java.math.BigInteger; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.jetbrains.annotations.NotNull; +import org.springframework.util.Assert; + /** * Collection of Monero utilities. */ public class MoneroUtils { - - // core wallet2 syncs on a fixed intervals - public static final long WALLET2_REFRESH_INTERVAL = 10000; - - private static final int NUM_MNEMONIC_WORDS = 25; - private static final int VIEW_KEY_LENGTH = 64; - private static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); - private static final List CHARS = new ArrayList(); - static { - for (char c : ALPHABET) { - CHARS.add(c); - } - } - - /** - * Validates a wallet seed. - * - * TODO: beef this up - * - * @param seed is the seed to validate - */ - public static void validateSeed(String seed) { - assertNotNull(seed); - assertEquals(64, seed.length()); - } - - /** - * Validates the given mnemonic phrase. - * - * @param mnemonic is the mnemonic to validate - * @throws MoneroException if the given mnemonic is invalid - */ - public static void validateMnemonic(String mnemonic) { - assertNotNull("Mnemonic phrase is not initialized", mnemonic); - assertFalse("Mnemonic phrase is empty", mnemonic.isEmpty()); - String[] words = mnemonic.split(" "); - if (words.length != MoneroUtils.NUM_MNEMONIC_WORDS) throw new Error("Mnemonic phrase is " + words.length + " words but must be " + MoneroUtils.NUM_MNEMONIC_WORDS); - } - - // TODO: improve validation - public static void validatePrivateViewKey(String privateViewKey) { - assertNotNull(privateViewKey); - assertEquals(64, privateViewKey.length()); - } - - // TODO: improve validation - public static void validatePrivateSpendKey(String privateSpendKey) { - assertNotNull(privateSpendKey); - assertEquals(64, privateSpendKey.length()); - } - - // TODO: improve validation - public static void validatePublicViewKey(String publicViewKey) { - assertNotNull(publicViewKey); - assertEquals(64, publicViewKey.length()); - } - - // TODO: improve validation - public static void validatePublicSpendKey(String publicSpendKey) { - assertNotNull(publicSpendKey); - assertEquals(64, publicSpendKey.length()); - } - - // TODO: improve validation - public static void validateAddress(String address) { - assertNotNull(address); - assertFalse(address.isEmpty()); - } - - // TODO: improve validation - public static void validatePaymentId(String paymentId) { - assertTrue(paymentId.length() == 16 || paymentId.length() == 64); - } - - /** - * Validates the given view key. - * - * @param viewKey is the view key to validate - * @throws MoneroException if the given view key is invalid - */ - public static void validateViewKey(String viewKey) { - if (viewKey == null) throw new MoneroException("View key is null"); - if (viewKey.length() != VIEW_KEY_LENGTH) throw new MoneroException("View key is " + viewKey.length() + " characters but must be " + VIEW_KEY_LENGTH); - } - - /** - * Converts the string to a URI. Throws MoneroException if exception. - * - * @param endpoint is the string to convert to a URI - * @return URI is the initialized object from the string endpoint - */ - public static URI parseUri(String endpoint) { - try { - return new URI(endpoint); - } catch (Exception e) { - throw new MoneroException(e); - } - } - - public static void validateHex(String str) { - if (!str.matches("^([0-9A-Fa-f]{2})+$")) throw new MoneroException("Invalid hex: " + str); - } - - public static void validateBase58(String standardAddress) { - for (char c : standardAddress.toCharArray()) { - if (!CHARS.contains((Character) c)) throw new MoneroException("Invalid Base58 " + standardAddress); - } - } - - /** - * Determines if two payment ids are functionally equal. - * - * For example, 03284e41c342f032 and 03284e41c342f032000000000000000000000000000000000000000000000000 are considered equal. - * - * @param paymentId1 is a payment id to compare - * @param paymentId2 is a payment id to compare - * @return true if the payment ids are equal, false otherwise - */ - public static boolean paymentIdsEqual(String paymentId1, String paymentId2) { - int maxLength = Math.max(paymentId1.length(), paymentId2.length()); - for (int i = 0; i < maxLength; i++) { - if (i < paymentId1.length() && i < paymentId2.length() && paymentId1.charAt(i) != paymentId2.charAt(i)) return false; - if (i >= paymentId1.length() && paymentId2.charAt(i) != '0') return false; - if (i >= paymentId2.length() && paymentId1.charAt(i) != '0') return false; - } - return true; - } - - /** - * Convenience method to reconcile two values with default configuration by - * calling reconcile(val1, val2, null, null, null). - * - * @param val1 is a value to reconcile - * @param val2 is a value to reconcile - * @return the reconciled value if reconcilable - * @throws Exception if the values cannot be reconciled - */ - public static T reconcile(T val1, T val2) { - return reconcile(val1, val2, null, null, null); - } - - /** - * Reconciles two values. - * - * @param val1 is a value to reconcile - * @param val2 is a value to reconcile - * @param resolveDefined uses defined value if true or null, null if false - * @param resolveTrue uses true over false if true, false over true if false, must be equal if null - * @param resolveMax uses max over min if true, min over max if false, must be equal if null - * @returns the reconciled value if reconcilable - * @throws Exception if the values cannot be reconciled - */ - @SuppressWarnings("unchecked") - public static T reconcile(T val1, T val2, Boolean resolveDefined, Boolean resolveTrue, Boolean resolveMax) { - - // check for same reference - if (val1 == val2) return val1; - - // check for BigInteger equality - Integer comparison = null; // save comparison for later if applicable - if (val1 instanceof BigInteger && val2 instanceof BigInteger) { - comparison = ((BigInteger) val1).compareTo((BigInteger) val2); - if (comparison == 0) return val1; - } - - // resolve one value null - if (val1 == null || val2 == null) { - if (Boolean.FALSE.equals(resolveDefined)) return null; // use null - else return val1 == null ? val2 : val1; // use defined value - } - - // resolve different booleans - if (resolveTrue != null && Boolean.class.isInstance(val1) && Boolean.class.isInstance(val2)) { - return (T) resolveTrue; - } - - // resolve different numbers - if (resolveMax != null) { - - // resolve BigIntegers - if (val1 instanceof BigInteger && val2 instanceof BigInteger) { - return resolveMax ? (comparison < 0 ? val2 : val1) : (comparison < 0 ? val1 : val2); - } - - // resolve integers - if (val1 instanceof Integer && val2 instanceof Integer) { - return (T) (Integer) (resolveMax ? Math.max((Integer) val1, (Integer) val2) : Math.min((Integer) val1, (Integer) val2)); - } - - // resolve longs - if (val1 instanceof Long && val2 instanceof Long) { - return (T) (Long) (resolveMax ? Math.max((Long) val1, (Long) val2) : Math.min((Long) val1, (Long) val2)); - } - - throw new RuntimeException("Need to resolve primitives and object versions"); -// // resolve js numbers -// if (typeof val1 === "number" && typeof val2 === "number") { -// return config.resolveMax ? Math.max(val1, val2) : Math.min(val1, val2); -// } - } - - // assert deep equality - assertEquals("Cannot reconcile values " + val1 + " and " + val2 + " with config: [" + resolveDefined + ", " + resolveTrue + ", " + resolveMax + "]", val1, val2); - return val1; - } - - /** - * Reconciles two int arrays. The arrays must be identical or an - * exception is thrown. - * - * @param val1 - * @param val2 - * @return - */ - public static int[] reconcileIntArrays(int[] arr1, int[] arr2) { - - // check for same reference or null - if (arr1 == arr2) return arr1; - - // resolve one value defined - if (arr1 == null || arr2 == null) { - return arr1 == null ? arr2 : arr1; - } - - // assert deep equality - assertTrue("Cannot reconcile arrays", Arrays.equals(arr1, arr2)); - return arr1; - } - - - /** - * Returns a human-friendly key value line. - * - * @param key is the key - * @param value is the value - * @param indent indents the line - * @return the human-friendly key value line - */ - public static String kvLine(Object key, Object value, int indent) { - return kvLine(key, value, indent, true, true); - } - - /** - * Returns a human-friendly key value line. - * - * @param key is the key - * @param value is the value - * @param indent indents the line - * @param newline specifies if the string should be terminated with a newline or not - * @param ignoreUndefined specifies if undefined values should return an empty string - * @return the human-friendly key value line - */ - public static String kvLine(Object key, Object value, int indent, boolean newline, boolean ignoreUndefined) { - if (value == null && ignoreUndefined) return ""; - return GenUtils.getIndent(indent) + key + ": " + value + (newline ? '\n' : ""); - } + + // core wallet2 syncs on a fixed intervals + public static final long WALLET2_REFRESH_INTERVAL = 10000; + + private static final int NUM_MNEMONIC_WORDS = 25; + private static final int VIEW_KEY_LENGTH = 64; + private static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + private static final List CHARS = new ArrayList(); + static { + for (char c : ALPHABET) { + CHARS.add(c); + } + } + + /** + * Validates a wallet seed. + * + * TODO: beef this up + * + * @param seed + * is the seed to validate + */ + public static void validateSeed(@NotNull String seed) { + Assert.isTrue(seed.length() == 64); + } + + /** + * Validates the given mnemonic phrase. + * + * @param mnemonic + * is the mnemonic to validate + * @throws MoneroException + * if the given mnemonic is invalid + */ + public static void validateMnemonic(@NotNull String mnemonic) { + Assert.hasLength(mnemonic, "Mnemonic phrase is empty"); + String[] words = mnemonic.split(" "); + if (words.length != MoneroUtils.NUM_MNEMONIC_WORDS) + throw new Error( + "Mnemonic phrase is " + words.length + " words but must be " + MoneroUtils.NUM_MNEMONIC_WORDS); + } + + // TODO: improve validation + public static void validatePrivateViewKey(@NotNull String privateViewKey) { + Assert.isTrue(privateViewKey.length() == 64); + } + + // TODO: improve validation + public static void validatePrivateSpendKey(@NotNull String privateSpendKey) { + Assert.isTrue(privateSpendKey.length() == 64); + } + + // TODO: improve validation + public static void validatePublicViewKey(@NotNull String publicViewKey) { + Assert.isTrue(publicViewKey.length() == 64); + } + + // TODO: improve validation + public static void validatePublicSpendKey(@NotNull String publicSpendKey) { + Assert.isTrue(publicSpendKey.length() == 64); + } + + // TODO: improve validation + public static void validateAddress(@NotNull String address) { + Assert.hasLength(address); + } + + // TODO: improve validation + public static void validatePaymentId(String paymentId) { + Assert.isTrue(paymentId.length() == 16 || paymentId.length() == 64); + } + + /** + * Validates the given view key. + * + * @param viewKey + * is the view key to validate + * @throws MoneroException + * if the given view key is invalid + */ + public static void validateViewKey(String viewKey) { + if (viewKey == null) + throw new MoneroException("View key is null"); + if (viewKey.length() != VIEW_KEY_LENGTH) + throw new MoneroException("View key is " + viewKey.length() + " characters but must be " + VIEW_KEY_LENGTH); + } + + /** + * Converts the string to a URI. Throws MoneroException if exception. + * + * @param endpoint + * is the string to convert to a URI + * @return URI is the initialized object from the string endpoint + */ + public static URI parseUri(String endpoint) { + try { + return new URI(endpoint); + } catch (Exception e) { + throw new MoneroException(e); + } + } + + public static void validateHex(String str) { + if (!str.matches("^([0-9A-Fa-f]{2})+$")) + throw new MoneroException("Invalid hex: " + str); + } + + public static void validateBase58(String standardAddress) { + for (char c : standardAddress.toCharArray()) { + if (!CHARS.contains((Character) c)) + throw new MoneroException("Invalid Base58 " + standardAddress); + } + } + + /** + * Determines if two payment ids are functionally equal. + * + * For example, 03284e41c342f032 and + * 03284e41c342f032000000000000000000000000000000000000000000000000 are + * considered equal. + * + * @param paymentId1 + * is a payment id to compare + * @param paymentId2 + * is a payment id to compare + * @return true if the payment ids are equal, false otherwise + */ + public static boolean paymentIdsEqual(String paymentId1, String paymentId2) { + int maxLength = Math.max(paymentId1.length(), paymentId2.length()); + for (int i = 0; i < maxLength; i++) { + if (i < paymentId1.length() && i < paymentId2.length() && paymentId1.charAt(i) != paymentId2.charAt(i)) + return false; + if (i >= paymentId1.length() && paymentId2.charAt(i) != '0') + return false; + if (i >= paymentId2.length() && paymentId1.charAt(i) != '0') + return false; + } + return true; + } + + /** + * Convenience method to reconcile two values with default configuration by + * calling reconcile(val1, val2, null, null, null). + * + * @param val1 + * is a value to reconcile + * @param val2 + * is a value to reconcile + * @return the reconciled value if reconcilable + * @throws Exception + * if the values cannot be reconciled + */ + public static T reconcile(T val1, T val2) { + return reconcile(val1, val2, null, null, null); + } + + /** + * Reconciles two values. + * + * @param val1 + * is a value to reconcile + * @param val2 + * is a value to reconcile + * @param resolveDefined + * uses defined value if true or null, null if false + * @param resolveTrue + * uses true over false if true, false over true if false, must be + * equal if null + * @param resolveMax + * uses max over min if true, min over max if false, must be equal if + * null + * @returns the reconciled value if reconcilable + * @throws Exception + * if the values cannot be reconciled + */ + @SuppressWarnings("unchecked") + public static T reconcile(T val1, T val2, Boolean resolveDefined, Boolean resolveTrue, Boolean resolveMax) { + + // check for same reference + if (val1 == val2) + return val1; + + // check for BigInteger equality + Integer comparison = null; // save comparison for later if applicable + if (val1 instanceof BigInteger && val2 instanceof BigInteger) { + comparison = ((BigInteger) val1).compareTo((BigInteger) val2); + if (comparison == 0) + return val1; + } + + // resolve one value null + if (val1 == null || val2 == null) { + if (Boolean.FALSE.equals(resolveDefined)) + return null; // use null + else + return val1 == null ? val2 : val1; // use defined value + } + + // resolve different booleans + if (resolveTrue != null && Boolean.class.isInstance(val1) && Boolean.class.isInstance(val2)) { + return (T) resolveTrue; + } + + // resolve different numbers + if (resolveMax != null) { + + // resolve BigIntegers + if (val1 instanceof BigInteger && val2 instanceof BigInteger) { + return resolveMax ? (comparison < 0 ? val2 : val1) : (comparison < 0 ? val1 : val2); + } + + // resolve integers + if (val1 instanceof Integer && val2 instanceof Integer) { + return (T) (Integer) (resolveMax ? Math.max((Integer) val1, (Integer) val2) + : Math.min((Integer) val1, (Integer) val2)); + } + + // resolve longs + if (val1 instanceof Long && val2 instanceof Long) { + return (T) (Long) (resolveMax ? Math.max((Long) val1, (Long) val2) + : Math.min((Long) val1, (Long) val2)); + } + + throw new RuntimeException("Need to resolve primitives and object versions"); + // // resolve js numbers + // if (typeof val1 === "number" && typeof val2 === "number") { + // return config.resolveMax ? Math.max(val1, val2) : Math.min(val1, val2); + // } + } + + // assert deep equality + Assert.isTrue(val1.equals(val2), "Cannot reconcile values " + val1 + " and " + val2 + " with config: [" + resolveDefined + ", " + + resolveTrue + ", " + resolveMax + "]"); + return val1; + } + + /** + * Reconciles two int arrays. The arrays must be identical or an exception is + * thrown. + * + * @param val1 + * @param val2 + * @return + */ + public static int[] reconcileIntArrays(int[] arr1, int[] arr2) { + + // check for same reference or null + if (arr1 == arr2) + return arr1; + + // resolve one value defined + if (arr1 == null || arr2 == null) { + return arr1 == null ? arr2 : arr1; + } + + // assert deep equality + Assert.isTrue(Arrays.equals(arr1, arr2), "Cannot reconcile arrays"); + return arr1; + } + + /** + * Returns a human-friendly key value line. + * + * @param key + * is the key + * @param value + * is the value + * @param indent + * indents the line + * @return the human-friendly key value line + */ + public static String kvLine(Object key, Object value, int indent) { + return kvLine(key, value, indent, true, true); + } + + /** + * Returns a human-friendly key value line. + * + * @param key + * is the key + * @param value + * is the value + * @param indent + * indents the line + * @param newline + * specifies if the string should be terminated with a newline or not + * @param ignoreUndefined + * specifies if undefined values should return an empty string + * @return the human-friendly key value line + */ + public static String kvLine(Object key, Object value, int indent, boolean newline, boolean ignoreUndefined) { + if (value == null && ignoreUndefined) + return ""; + return GenUtils.getIndent(indent) + key + ": " + value + (newline ? '\n' : ""); + } } diff --git a/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java b/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java index 98d1d4b3440..fb5e02371aa 100644 --- a/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java +++ b/core/src/main/java/bisq/core/xmr/wallet/XmrWalletRpcWrapper.java @@ -203,6 +203,9 @@ public void run() { walletRpcData.put("getBalance", walletRpc.getBalance()); walletRpcData.put("getUnlockedBalance", walletRpc.getUnlockedBalance()); walletRpcData.put("getFee", tx.getFee()); + walletRpcData.put("getAmount", tx.getAmount()); + walletRpcData.put("getAddress", address); + walletRpcData.put("getSize", tx.getSize()); if(doNotRelay) { walletRpcData.put("txToRelay", tx.getTxMetadata()); } diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java index 225aa8e0129..46ca6861c93 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/send/XmrSendView.java @@ -140,11 +140,11 @@ public void onUpdateBalances(HashMap walletRpcData) { log.debug("onUpdateBalances => {}", walletRpcData); BigInteger fee = walletRpcData.get("getFee") != null ? (BigInteger) walletRpcData.get("getFee") : BigInteger.ZERO; BigInteger unlockedBalance = walletRpcData.get("getUnlockedBalance") != null ? (BigInteger) walletRpcData.get("getUnlockedBalance") : BigInteger.ZERO; - Integer size = walletRpcData.get("getSize") != null ? (Integer) walletRpcData.get("getSize") : 0; + Long size = walletRpcData.get("getSize") != null ? (Long) walletRpcData.get("getSize") : 0; Double sizeKbs = size != null ? size.doubleValue() / 1024.0 : 0.0; feeInputTextField.setText(xmrFormatter.formatBigInteger(fee)); - BigInteger amountToSend = walletRpcData.get("getOutgoingAmount") != null - ? (BigInteger) walletRpcData.get("getOutgoingAmount") + BigInteger amountToSend = walletRpcData.get("getAmount") != null + ? (BigInteger) walletRpcData.get("getAmount") : BigInteger.ZERO; if(unlockedBalance.subtract(amountToSend).subtract(fee).compareTo(BigInteger.ZERO) < 0) { handleError(new Exception("Balance too low.")); @@ -216,18 +216,12 @@ private void addSendXmrGroup() { data.put("getBalance", null); data.put("getUnlockedBalance", null); data.put("getFee", null); - data.put("getMixin", null); - data.put("getOutgoingAmount", null); data.put("getNumConfirmations", null); - data.put("getDoNotRelay", null); data.put("getId", null); data.put("getTimestamp", null); data.put("getPaymentId", null); - data.put("getReceivedTimestamp", null); data.put("getUnlockTime", null); - data.put("getVersion", null); - data.put("getOutgoingTransfer", null); - data.put("getExtra", null); + data.put("getAmount", null); try { walletWrapper.createTx(XmrSendView.this, accountIndex, address, amount, priority != null ? priority : MoneroSendPriority.NORMAL, true, data); @@ -252,13 +246,13 @@ private void handleError(Throwable t) { } private void showPublishTxPopup(BigInteger outgoingAmount, String address, - BigInteger fee, Integer size, Double sizeKbs, - BigInteger amountReceived, XmrFormatter amountFormatter, + BigInteger fee, Long size, Double sizeKbs, + BigInteger amountToReceive, XmrFormatter amountFormatter, ResultHandler resultHandler) { new Popup<>().headLine(Res.get("shared.account.wallet.send.sendFunds.headline")) .confirmation(Res.get("shared.account.wallet.send.sendFunds.details", xmrFormatter.formatBigInteger(outgoingAmount), - address, xmrFormatter.formatBigInteger(fee), size != 0 ? (fee.doubleValue() / size) : 0, sizeKbs, xmrFormatter.formatBigInteger(amountReceived))) + address, xmrFormatter.formatBigInteger(fee), size != null ? (new BigDecimal(fee, 12).doubleValue() / size.doubleValue()) : 0, sizeKbs, xmrFormatter.formatBigInteger(amountToReceive))) .actionButtonText(Res.get("shared.yes")) .onAction(() -> { resultHandler.handleResult(); diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java index 8f99344a631..b9c86d4d845 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/wallet/monero/tx/XmrTxView.java @@ -192,7 +192,12 @@ public void updateItem(final XmrTxListItem item, boolean empty) { } }); tableView.getColumns().add(column); - column.setComparator(Comparator.comparing(XmrTxListItem::getDate)); + column.setComparator(new Comparator() { + @Override + public int compare(XmrTxListItem o1, XmrTxListItem o2) { + return o2.getDate().compareTo(o1.getDate()); + } + }); column.setSortType(TableColumn.SortType.DESCENDING); tableView.getSortOrder().add(column); }