From 92d3780a8211c4f8e61f0f6f51089e7b99febdaf Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 12 Feb 2020 16:51:48 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../data/network/BlockchainInfoApi.java | 2 +- .../java/com/tangem/data/network/Server.java | 4 +- .../data/network/ServerApiBlockchainInfo.java | 18 ++- .../network/model/BlockchainInfoResponse.kt | 15 ++- .../data/network/model/BlockcypherResponse.kt | 16 ++- .../main/java/com/tangem/wallet/CoinData.java | 28 ++-- .../java/com/tangem/wallet/btc/BtcEngine.java | 122 +++++++++++++----- 7 files changed, 150 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/com/tangem/data/network/BlockchainInfoApi.java b/app/src/main/java/com/tangem/data/network/BlockchainInfoApi.java index 70f4aae91d..7584a12d64 100644 --- a/app/src/main/java/com/tangem/data/network/BlockchainInfoApi.java +++ b/app/src/main/java/com/tangem/data/network/BlockchainInfoApi.java @@ -14,7 +14,7 @@ import retrofit2.http.Query; public interface BlockchainInfoApi { @GET(Server.ApiBlockchainInfo.Method.ADDRESS) - Single blockchainInfoAddress(@Path("address") String address); + Single blockchainInfoAddress(@Path("address") String address, @Query("offset") Integer offset); @GET(Server.ApiBlockchainInfo.Method.UTXO) Single blockchainInfoUnspents(@Query("active") String address); diff --git a/app/src/main/java/com/tangem/data/network/Server.java b/app/src/main/java/com/tangem/data/network/Server.java index 14117ed83b..29f69aba32 100644 --- a/app/src/main/java/com/tangem/data/network/Server.java +++ b/app/src/main/java/com/tangem/data/network/Server.java @@ -113,7 +113,7 @@ public class Server { public static class Method { static final String MAIN = URL_BLOCKCYPHER + V1_MAIN; - static final String ADDRESS = MAIN + "/addrs/{address}?unspentOnly=true&includeScript=true"; + static final String ADDRESS = MAIN + "/addrs/{address}?includeScript=true&limit=2000"; static final String TXS = MAIN + "/txs/{txHash}?includeHex=true"; static final String PUSH = MAIN + "/txs/push"; } @@ -123,7 +123,7 @@ public class Server { public static final String URL_BLOCKCHAININFO = ServerURL.API_BLOCKCHAIN_INFO; public static class Method { - static final String ADDRESS = URL_BLOCKCHAININFO + "rawaddr/{address}?limit=5"; + static final String ADDRESS = URL_BLOCKCHAININFO + "rawaddr/{address}"; static final String UTXO = URL_BLOCKCHAININFO + "unspent"; // static final String TX = URL_BLOCKCHAININFO + "rawtx/{txHash}"; static final String PUSH = URL_BLOCKCHAININFO + "pushtx"; diff --git a/app/src/main/java/com/tangem/data/network/ServerApiBlockchainInfo.java b/app/src/main/java/com/tangem/data/network/ServerApiBlockchainInfo.java index 3697332b26..a16f25ddfc 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiBlockchainInfo.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiBlockchainInfo.java @@ -5,9 +5,11 @@ import android.util.Log; import com.tangem.App; import com.tangem.data.network.model.BlockchainInfoAddress; import com.tangem.data.network.model.BlockchainInfoAddressAndUnspents; +import com.tangem.data.network.model.BlockchainInfoTransaction; import com.tangem.data.network.model.BlockchainInfoUnspents; import java.util.ArrayList; +import java.util.List; import io.reactivex.Single; import io.reactivex.SingleObserver; @@ -17,12 +19,13 @@ import okhttp3.ResponseBody; public class ServerApiBlockchainInfo { private static String TAG = ServerApiBlockchainInfo.class.getSimpleName(); + private int page = 1; public void getAddressAndUnspents(String wallet, SingleObserver addressAndUnspentsObserver) { Log.i(TAG, "new getAddressAndUnspents request"); BlockchainInfoApi api = App.Companion.getNetworkComponent().getRetrofitBlockchainInfo().create(BlockchainInfoApi.class); - Single addressObservable = api.blockchainInfoAddress(wallet); + Single addressObservable = api.blockchainInfoAddress(wallet, null); Single unspentsObservable = api.blockchainInfoUnspents(wallet) .onErrorReturnItem(new BlockchainInfoUnspents(new ArrayList<>())); @@ -43,4 +46,17 @@ public class ServerApiBlockchainInfo { sendObservable.subscribe(sendObserver); } + + public Single> getMoreAddressTxs(String wallet) { + Log.i(TAG, "new getAddress request"); + BlockchainInfoApi api = App.Companion.getNetworkComponent().getRetrofitBlockchainInfo().create(BlockchainInfoApi.class); + + Single> addressObservable = api.blockchainInfoAddress(wallet, page * 50) + .map(BlockchainInfoAddress::getTxs) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + + page++; + return addressObservable; + } } diff --git a/app/src/main/java/com/tangem/data/network/model/BlockchainInfoResponse.kt b/app/src/main/java/com/tangem/data/network/model/BlockchainInfoResponse.kt index fab1d0c88f..8133dbf180 100644 --- a/app/src/main/java/com/tangem/data/network/model/BlockchainInfoResponse.kt +++ b/app/src/main/java/com/tangem/data/network/model/BlockchainInfoResponse.kt @@ -15,7 +15,10 @@ data class BlockchainInfoTransaction( var hash: String? = null, @SerializedName("block_height") - var block_height: Long? = null + var block_height: Long? = null, + + @SerializedName("inputs") + var inputs: List ) data class BlockchainInfoUnspents( @@ -37,6 +40,16 @@ data class BlockchainInfoUtxo( var script: String? = null ) +data class BlockchainInfoInput( + @SerializedName("prev_out") + var prev_out: BlockchainInfoOutput +) + +data class BlockchainInfoOutput( + @SerializedName("addr") + var addr: String? = null +) + data class BlockchainInfoAddressAndUnspents( var address: BlockchainInfoAddress, var unspents: BlockchainInfoUnspents diff --git a/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt b/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt index 484526bc4d..f3c2e951cd 100644 --- a/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt +++ b/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt @@ -13,13 +13,22 @@ data class BlockcypherResponse( var unconfirmed_balance: Long? = null, @SerializedName("txrefs") - var txrefs: List? = null + var txrefs: List? = null, + + @SerializedName("unconfirmed_txrefs") + var unconfirmed_txrefs: List? = null, + + @SerializedName("hasMore") + var hasMore: Boolean? = null ) data class BlockcypherTxref( @SerializedName("tx_hash") var tx_hash: String? = null, + @SerializedName("tx_input_n") + var tx_input_n: Int? = null, + @SerializedName("tx_output_n") var tx_output_n: Int? = null, @@ -30,7 +39,10 @@ data class BlockcypherTxref( var confirmations: Long? = null, @SerializedName("script") - var script: String? = null + var script: String? = null, + + @SerializedName("spent") + var spent: Boolean? = null ) data class BlockcypherTx( diff --git a/app/src/main/java/com/tangem/wallet/CoinData.java b/app/src/main/java/com/tangem/wallet/CoinData.java index 311624d58b..cac10cdc60 100644 --- a/app/src/main/java/com/tangem/wallet/CoinData.java +++ b/app/src/main/java/com/tangem/wallet/CoinData.java @@ -75,9 +75,7 @@ public abstract class CoinData { B.putBoolean("balanceReceived", balanceReceived); B.putString("validationNodeDescription", validationNodeDescription); - if (sentTransactionsCount != null) { - B.putInt("sentTransactionsCount", sentTransactionsCount); - } + B.putInt("sentTransactionsCount", sentTransactionsCount); } catch (Exception e) { Log.e("Can't save to bundle ", e.getMessage()); } @@ -91,8 +89,8 @@ public abstract class CoinData { } public static CoinData fromBundle(Blockchain blockchain, Bundle bundle) { - CoinEngine engine= CoinEngineFactory.INSTANCE.create(blockchain); - if( engine==null ) return null; + CoinEngine engine = CoinEngineFactory.INSTANCE.create(blockchain); + if (engine == null) return null; CoinData result = engine.createCoinData(); result.loadFromBundle(bundle); return result; @@ -151,12 +149,12 @@ public abstract class CoinData { setIsBalanceEqual(false); setBalanceReceived(false); setValidationNodeDescription(""); - minFee=null; - maxFee=null; - normalFee=null; - rate=0f; - rateAlter=0f; - sentTransactionsCount = null; + minFee = null; + maxFee = null; + normalFee = null; + rate = 0f; + rateAlter = 0f; + sentTransactionsCount = 0; } // private AtomicInteger failedBalanceRequestCounter; @@ -201,13 +199,13 @@ public abstract class CoinData { public CoinEngine.Amount normalFee = null; public CoinEngine.Amount maxFee = null; - private Integer sentTransactionsCount = null; + private int sentTransactionsCount = 0; - public Integer getSentTransactionsCount() { + public int getSentTransactionsCount() { return sentTransactionsCount; } - public void setSentTransactionsCount(Integer sentTransactionsCount) { - this.sentTransactionsCount = sentTransactionsCount; + public void incSentTransactionsCount() { + sentTransactionsCount++; } } diff --git a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java index 49da3cd0a5..4159d44d5b 100644 --- a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java @@ -13,6 +13,7 @@ import com.tangem.data.network.ServerApiBlockcypher; import com.tangem.data.network.ServerApiCommon; import com.tangem.data.network.model.BlockchainInfoAddress; import com.tangem.data.network.model.BlockchainInfoAddressAndUnspents; +import com.tangem.data.network.model.BlockchainInfoInput; import com.tangem.data.network.model.BlockchainInfoTransaction; import com.tangem.data.network.model.BlockchainInfoUnspents; import com.tangem.data.network.model.BlockchainInfoUtxo; @@ -53,6 +54,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import io.reactivex.Single; import io.reactivex.SingleObserver; import io.reactivex.observers.DisposableSingleObserver; import okhttp3.ResponseBody; @@ -576,30 +578,6 @@ public class BtcEngine extends CoinEngine { BlockchainInfoAddress blockchainInfoAddress = blockchainInfoAddressAndUnspents.getAddress(); BlockchainInfoUnspents blockchainInfoUnspents = blockchainInfoAddressAndUnspents.getUnspents(); - if (blockchainInfoAddress.getFinal_balance() != null) { - coinData.setBalanceReceived(true); - coinData.setBalanceConfirmed(blockchainInfoAddress.getFinal_balance()); - coinData.setBalanceUnconfirmed(0L); - coinData.setValidationNodeDescription(Server.ApiBlockchainInfo.URL_BLOCKCHAININFO); - - for (BlockchainInfoTransaction tx : blockchainInfoAddress.getTxs()) { - if (tx.getBlock_height() == null) { - coinData.setHasUnconfirmed(true); - } - } - - if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) { - for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) { - String pendingTxId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx())))); - for (BlockchainInfoTransaction responseTx : blockchainInfoAddress.getTxs()) { - if (responseTx.getHash().equals(pendingTxId)) { - App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), pendingTx.getTx()); - } - } - } - } - } - for (BlockchainInfoUtxo utxo : blockchainInfoUnspents.getUnspent_outputs()) { BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); trUnspent.txID = utxo.getTx_hash_big_endian(); @@ -609,7 +587,18 @@ public class BtcEngine extends CoinEngine { coinData.getUnspentTransactions().add(trUnspent); } - blockchainRequestsCallbacks.onComplete(true); + if (blockchainInfoAddress.getFinal_balance() != null) { + coinData.setBalanceReceived(true); + coinData.setBalanceConfirmed(blockchainInfoAddress.getFinal_balance()); + coinData.setBalanceUnconfirmed(0L); + coinData.setValidationNodeDescription(Server.ApiBlockchainInfo.URL_BLOCKCHAININFO); + } + + if (blockchainInfoAddress.getTxs() != null) { + checkTransactionsBlockchainInfo(Single.just(blockchainInfoAddress.getTxs()), serverApiBlockchainInfo, blockchainRequestsCallbacks); + } else { + blockchainRequestsCallbacks.onComplete(true); + } } @Override @@ -648,14 +637,29 @@ public class BtcEngine extends CoinEngine { coinData.setValidationNodeDescription(Server.ApiBlockcypher.URL_BLOCKCYPHER); coinData.getUnspentTransactions().clear(); + //TODO: change request logic, 2000 tx max if (blockcypherResponse.getTxrefs() != null) { for (BlockcypherTxref txref : blockcypherResponse.getTxrefs()) { - BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); - trUnspent.txID = txref.getTx_hash(); - trUnspent.amount = txref.getValue(); - trUnspent.outputN = txref.getTx_output_n(); - trUnspent.script = txref.getScript(); - coinData.getUnspentTransactions().add(trUnspent); + if (txref.getTx_input_n() == -1) { //recieved only + if (!txref.getSpent()) { + BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); + trUnspent.txID = txref.getTx_hash(); + trUnspent.amount = txref.getValue(); + trUnspent.outputN = txref.getTx_output_n(); + trUnspent.script = txref.getScript(); + coinData.getUnspentTransactions().add(trUnspent); + } + } else { //sent only + coinData.incSentTransactionsCount(); + } + } + } + + if (blockcypherResponse.getUnconfirmed_txrefs() != null) { + for (BlockcypherTxref unconfirmedTxref : blockcypherResponse.getUnconfirmed_txrefs()) { + if (unconfirmedTxref.getTx_input_n() != -1) { + coinData.incSentTransactionsCount(); + } } } } catch (Exception e) { @@ -663,7 +667,7 @@ public class BtcEngine extends CoinEngine { Log.e(TAG, "FAIL BLOCKCYPHER_ADDRESS Exception"); } - checkPending(blockchainRequestsCallbacks); + checkPendingBlockcypher(blockchainRequestsCallbacks); } public void onSuccess(String method, BlockcypherFee blockcypherFee) { @@ -686,7 +690,59 @@ public class BtcEngine extends CoinEngine { } } - private void checkPending(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { + private void checkTransactionsBlockchainInfo(Single> txsSingle, ServerApiBlockchainInfo serverApiBlockchainInfo, BlockchainRequestsCallbacks blockchainRequestsCallbacks) { + SingleObserver> txsObserver = new DisposableSingleObserver>() { + @Override + public void onSuccess(List txs) { + if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) { + for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) { + String pendingTxId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx())))); + for (BlockchainInfoTransaction responseTx : txs) { + if (pendingTxId.equals(responseTx.getHash())) { + App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), pendingTx.getTx()); + } + } + } + } + + for (BlockchainInfoTransaction tx : txs) { + if (tx.getBlock_height() == null) { + coinData.setHasUnconfirmed(true); + } + + for (BlockchainInfoInput input : tx.getInputs()) { + String inputAddress = input.getPrev_out().getAddr(); + if (coinData.getWallet().equals(inputAddress)) { + coinData.incSentTransactionsCount(); + } + } + } + + if (txs.size() == 50) { + final ServerApiBlockchainInfo serverApiBlockchainInfo = new ServerApiBlockchainInfo(); + checkTransactionsBlockchainInfo(serverApiBlockchainInfo.getMoreAddressTxs(coinData.getWallet()), serverApiBlockchainInfo, blockchainRequestsCallbacks); + } else { + blockchainRequestsCallbacks.onComplete(true); + } + } + + @Override + public void onError(Throwable e) { + Log.i(TAG, "onError: getMoreAddressTxs" + e.getMessage()); + coinData.setUseBlockcypher(true); + try { + requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks); + } catch (Exception ex) { + ctx.setError(ex.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } + } + }; + + txsSingle.subscribe(txsObserver); + } + + private void checkPendingBlockcypher(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) { ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher();