From 161042066d63e096175a965b81adee7055a8801e Mon Sep 17 00:00:00 2001 From: Roman Volosovskyi Date: Thu, 2 Jan 2020 12:01:58 +0200 Subject: [PATCH] [#9203] Transfers fetching with less requests - all messages are not shown right away, in order to paginate history a user has to press "load more" button - added link to etherscan before transfers list - there is a new "fetch more" button at the end of the list - rest of changes can be found here status-im/status-go#1775 --- src/status_im/constants.cljs | 58 ++++--- src/status_im/ethereum/json_rpc.cljs | 4 + src/status_im/ethereum/subscriptions.cljs | 61 +++++-- src/status_im/ethereum/transactions/core.cljs | 162 +++++++++++++++--- src/status_im/multiaccounts/login/core.cljs | 5 +- src/status_im/node/core.cljs | 3 +- src/status_im/subs.cljs | 27 +++ .../ui/screens/wallet/account/views.cljs | 2 +- .../ui/screens/wallet/transactions/views.cljs | 76 ++++++-- status-go-version.json | 6 +- translations/en.json | 4 +- 11 files changed, 332 insertions(+), 76 deletions(-) diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 06c618383f..997f07bac7 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -44,12 +44,13 @@ (def system "system") (def mainnet-networks - [{:id "mainnet_rpc", - :name "Mainnet with upstream RPC", - :config {:NetworkId (ethereum/chain-keyword->chain-id :mainnet) - :DataDir "/ethereum/mainnet_rpc" - :UpstreamConfig {:Enabled true - :URL "https://mainnet.infura.io/v3/f315575765b14720b32382a61a89341a"}}}]) + [{:id "mainnet_rpc", + :etherscan-link "https://etherscan.io/address/", + :name "Mainnet with upstream RPC", + :config {:NetworkId (ethereum/chain-keyword->chain-id :mainnet) + :DataDir "/ethereum/mainnet_rpc" + :UpstreamConfig {:Enabled true + :URL "https://mainnet.infura.io/v3/f315575765b14720b32382a61a89341a"}}}]) (def sidechain-networks [{:id "xdai_rpc", @@ -66,28 +67,37 @@ :URL "https://core.poa.network"}}}]) (def testnet-networks - [{:id "testnet_rpc", - :name "Ropsten with upstream RPC", - :config {:NetworkId (ethereum/chain-keyword->chain-id :testnet) - :DataDir "/ethereum/testnet_rpc" - :UpstreamConfig {:Enabled true - :URL "https://ropsten.infura.io/v3/f315575765b14720b32382a61a89341a"}}} - {:id "rinkeby_rpc", - :name "Rinkeby with upstream RPC", - :config {:NetworkId (ethereum/chain-keyword->chain-id :rinkeby) - :DataDir "/ethereum/rinkeby_rpc" - :UpstreamConfig {:Enabled true - :URL "https://rinkeby.infura.io/v3/f315575765b14720b32382a61a89341a"}}} - {:id "goerli_rpc", - :name "Goerli with upstream RPC", - :config {:NetworkId (ethereum/chain-keyword->chain-id :goerli) - :DataDir "/ethereum/goerli_rpc" - :UpstreamConfig {:Enabled true - :URL "https://goerli.blockscout.com/"}}}]) + [{:id "testnet_rpc", + :etherscan-link "https://ropsten.etherscan.io/address/", + :name "Ropsten with upstream RPC", + :config {:NetworkId (ethereum/chain-keyword->chain-id :testnet) + :DataDir "/ethereum/testnet_rpc" + :UpstreamConfig {:Enabled true + :URL "https://ropsten.infura.io/v3/f315575765b14720b32382a61a89341a"}}} + {:id "rinkeby_rpc", + :etherscan-link "https://rinkeby.etherscan.io/address/", + :name "Rinkeby with upstream RPC", + :config {:NetworkId (ethereum/chain-keyword->chain-id :rinkeby) + :DataDir "/ethereum/rinkeby_rpc" + :UpstreamConfig {:Enabled true + :URL "https://rinkeby.infura.io/v3/f315575765b14720b32382a61a89341a"}}} + {:id "goerli_rpc", + :etherscan-link "https://goerli.etherscan.io/address/", + :name "Goerli with upstream RPC", + :config {:NetworkId (ethereum/chain-keyword->chain-id :goerli) + :DataDir "/ethereum/goerli_rpc" + :UpstreamConfig {:Enabled true + :URL "https://goerli.blockscout.com/"}}}]) (def default-networks (concat testnet-networks mainnet-networks sidechain-networks)) +(def default-networks-by-id + (into {} + (map (fn [{:keys [id] :as network}] + [id network]) + default-networks))) + (def default-multiaccount {:preview-privacy? config/blank-preview? :wallet/visible-tokens {:mainnet #{:SNT}} diff --git a/src/status_im/ethereum/json_rpc.cljs b/src/status_im/ethereum/json_rpc.cljs index 05682a12dc..cbd04d5d33 100644 --- a/src/status_im/ethereum/json_rpc.cljs +++ b/src/status_im/ethereum/json_rpc.cljs @@ -21,6 +21,7 @@ (update :timestamp decode/uint))} "eth_getTransactionByHash" {} "eth_getTransactionReceipt" {} + "eth_getBlockByNumber" {} "eth_newBlockFilter" {:subscription? true} "eth_newFilter" {:subscription? true} "eth_syncing" {} @@ -80,6 +81,9 @@ "status_chats" {} "wallet_getTransfers" {} "wallet_getTokensBalances" {} + "wallet_getBlocksByAddress" {} + "wallet_getTransfersFromBlock" {} + "wallet_getTransfersByAddress" {} "wallet_getCustomTokens" {} "wallet_addCustomToken" {} "wallet_deleteCustomToken" {} diff --git a/src/status_im/ethereum/subscriptions.cljs b/src/status_im/ethereum/subscriptions.cljs index 7d627901dd..40f32a6d08 100644 --- a/src/status_im/ethereum/subscriptions.cljs +++ b/src/status_im/ethereum/subscriptions.cljs @@ -1,6 +1,7 @@ (ns status-im.ethereum.subscriptions (:require [re-frame.core :as re-frame] [status-im.constants :as constants] + [status-im.ethereum.eip55 :as eip55] [status-im.ethereum.core :as ethereum] [status-im.ethereum.json-rpc :as json-rpc] [status-im.ethereum.tokens :as tokens] @@ -29,6 +30,9 @@ chain (ethereum/chain-keyword db) chain-tokens (into {} (map (juxt :address identity) (tokens/tokens-for all-tokens chain)))] + (log/debug "[wallet-subs] new-block" + "accounts" accounts + "block" block-number) (fx/merge cofx (cond-> {} (not historical?) @@ -37,26 +41,63 @@ ;;NOTE only get transfers if the new block contains some ;; from/to one of the multiaccount accounts (not-empty accounts) - (assoc ::transactions/get-transfers {:chain-tokens chain-tokens - :from-block block-number - :historical? historical?})) + (assoc :transactions/get-transfers-from-block + {:chain-tokens chain-tokens + :addresses accounts + :block block-number + :historical? historical?})) (transactions/check-watched-transactions)))) (fx/defn reorg - [{:keys [db] :as cofx} block-number accounts] + [{:keys [db] :as cofx} {:keys [blockNumber accounts]}] + (log/debug "[wallet-subs] reorg" + "accounts" accounts + "block-number" blockNumber) + {:db (update-in db [:wallet :transactions] + wallet/remove-transactions-since-block blockNumber)}) + +(fx/defn recent-history-fetching-started + [{:keys [db]} accounts] + (log/debug "[wallet-subs] recent-history-fetching-started" + "accounts" accounts) + {:db (transactions/update-fetching-status db accounts :recent? true)}) + +(fx/defn recent-history-fetching-ended + [{:keys [db] :as cofx} {:keys [accounts blockNumber]}] + (log/debug "[wallet-subs] recent-history-fetching-ended" + "accounts" accounts + "block" blockNumber) (let [{:keys [:wallet/all-tokens]} db chain (ethereum/chain-keyword db) chain-tokens (into {} (map (juxt :address identity) (tokens/tokens-for all-tokens chain)))] - {:db (update-in db [:wallet :accounts] - wallet/remove-transactions-since-block block-number) - ::transactions/get-transfers {:chain-tokens chain-tokens - :from-block block-number}})) + {:db (-> db + (update-in [:wallet :accounts] + wallet/remove-transactions-since-block blockNumber) + (transactions/update-fetching-status accounts :recent? false)) + :transactions/get-transfers + {:chain-tokens chain-tokens + :addresses (reduce + (fn [v address] + (let [normalized-address + (eip55/address->checksum address)] + (if (contains? v normalized-address) + v + (conj v address)))) + [] + accounts) + :before-block blockNumber + :page-size 20 + :historical? true}})) (fx/defn new-wallet-event - [{:keys [db] :as cofx} {:keys [type blockNumber accounts] :as event}] + [cofx {:keys [type blockNumber accounts] :as event}] + (log/debug "[wallet-subs] new-wallet-event" + "event-type" type) (case type "newblock" (new-block cofx false blockNumber accounts) "history" (new-block cofx true blockNumber accounts) - "reorg" (reorg cofx blockNumber accounts) + "reorg" (reorg cofx event) + "recent-history-fetching" (recent-history-fetching-started cofx accounts) + "recent-history-ready" (recent-history-fetching-ended cofx event) (log/warn ::unknown-wallet-event :type type :event event))) diff --git a/src/status_im/ethereum/transactions/core.cljs b/src/status_im/ethereum/transactions/core.cljs index 99ba7484fc..dccacaab20 100644 --- a/src/status_im/ethereum/transactions/core.cljs +++ b/src/status_im/ethereum/transactions/core.cljs @@ -6,11 +6,11 @@ [status-im.ethereum.encode :as encode] [status-im.ethereum.json-rpc :as json-rpc] [status-im.ethereum.core :as ethereum] - [status-im.ethereum.eip55 :as eip55] [status-im.ethereum.tokens :as tokens] [status-im.utils.fx :as fx] [status-im.utils.money :as money] - [status-im.wallet.core :as wallet])) + [status-im.wallet.core :as wallet] + [taoensso.timbre :as log])) (def confirmations-count-threshold 12) @@ -130,10 +130,8 @@ transaction can contain multiple transfers and they would overwrite each other in the transfer map if identified by hash" [{:keys [db] :as cofx} {:keys [hash id address] :as transfer}] - (let [transfer-by-hash (get-in db [:wallet :accounts address :transactions hash]) - transfer-by-id (get-in db [:wallet :accounts address :transactions id])] - (when-let [unique-id (when-not (or transfer-by-id - (= transfer transfer-by-hash)) + (let [transfer-by-hash (get-in db [:wallet :accounts address :transactions hash])] + (when-let [unique-id (when-not (= transfer transfer-by-hash) (if (and transfer-by-hash (not (= :pending (:type transfer-by-hash)))) @@ -144,32 +142,146 @@ (assoc transfer :hash unique-id))} (check-transaction transfer))))) +(defn get-min-known-block [db address] + (get-in db [:wallet :accounts (eip55/address->checksum address) :min-block])) + +(fx/defn set-lowest-fetched-block + [{:keys [db]} address transfers] + (let [min-block (reduce + (fn [min-block {:keys [block]}] + (min (or min-block block) block)) + (get-min-known-block db address) + transfers)] + {:db (assoc-in + db + [:wallet :accounts (eip55/address->checksum address) :min-block] + min-block)})) + +(defn update-fetching-status + [db addresses fetching-type state] + (update-in + db [:wallet :fetching] + (fn [accounts] + (reduce + (fn [accounts address] + (assoc-in accounts + [(eip55/address->checksum address) fetching-type] + state)) + accounts + addresses)))) + +(fx/defn tx-fetching-in-progress + [{:keys [db]} addresses] + {:db (update-fetching-status db addresses :history? true)}) + +(fx/defn tx-fetching-ended + [{:keys [db]} addresses] + {:db (update-fetching-status db addresses :history? false)}) + +(fx/defn tx-history-end-reached + [{:keys [db]} address] + {:db (assoc-in db [:wallet :fetching address :all-fetched?] true)}) + (fx/defn new-transfers {:events [::new-transfers]} - [{:keys [db] :as cofx} transfers historical?] - (let [effects (cond-> (map add-transfer transfers) - ;;NOTE: we only update the balance for new transfers and not historical ones - (not historical?) (conj (wallet/update-balances (into [] (reduce (fn [acc {:keys [address]}] - (conj acc address)) - #{} - transfers)))))] - (apply fx/merge cofx effects))) + [{:keys [db] :as cofx} transfers {:keys [address historical? before-block]}] + (let [min-block (get-in db [:wallet :accounts address :min-block]) + effects (cond-> [(when (seq transfers) + (set-lowest-fetched-block address transfers))] + + (seq transfers) + (concat (mapv add-transfer transfers)) + ;;NOTE: we only update the balance for new transfers and not + ;; historical ones + (not historical?) + (conj (wallet/update-balances + (into [] (reduce (fn [acc {:keys [address]}] + (conj acc address)) + #{} + transfers)))) + + (and (= min-block before-block) + (<= (count transfers) 1)) + (conj (tx-history-end-reached address)))] + (apply fx/merge cofx (tx-fetching-ended [address]) effects))) + +(fx/defn tx-fetching-failed + {:events [::tx-fetching-failed]} + [cofx error address] + (log/debug "[transactions] tx-fetching-failed" + "address" address + "error" error) + (tx-fetching-ended cofx [address])) (re-frame/reg-fx - ::get-transfers - (fn [{:keys [chain-tokens from-block to-block historical?] - :or {from-block "0" - to-block nil}}] - (json-rpc/call - {:method "wallet_getTransfers" - :params [(encode/uint from-block) (encode/uint to-block)] - :on-success #(re-frame/dispatch [::new-transfers (enrich-transfers chain-tokens %) historical?])}))) + :transactions/get-transfers-from-block + (fn [{:keys [chain-tokens addresses block] :as params}] + (log/debug "[transactions] get-transfers-from-block" + "addresses" addresses + "block" block) + (doseq [address addresses] + (json-rpc/call + {:method "wallet_getTransfersFromBlock" + :params [address (encode/uint block)] + :on-success #(re-frame/dispatch + [::new-transfers + (enrich-transfers chain-tokens %) + (assoc params :address address)]) + :on-error #(re-frame/dispatch [::tx-fetching-failed % address])})))) + +(re-frame/reg-fx + :transactions/get-transfers + (fn [{:keys [chain-tokens addresses before-block page-size] + :as params + :or {page-size 0}}] + {:pre [(cljs.spec.alpha/valid? + (cljs.spec.alpha/coll-of string?) + addresses)]} + (log/debug "[transactions] get-transfers" + "addresses" addresses + "block" before-block + "page-size" page-size) + (when before-block + (doseq [address addresses] + (json-rpc/call + {:method "wallet_getTransfersByAddress" + :params [address (encode/uint before-block) (encode/uint page-size)] + :on-success #(re-frame/dispatch + [::new-transfers + (enrich-transfers chain-tokens %) + (assoc params :address address)]) + :on-error #(re-frame/dispatch [::tx-fetching-failed address])}))))) (fx/defn initialize - [{:keys [db] :as cofx}] + [{:keys [db]} addresses] (let [{:keys [:wallet/all-tokens]} db chain (ethereum/chain-keyword db) chain-tokens (into {} (map (juxt :address identity) (tokens/tokens-for all-tokens chain)))] - {::get-transfers {:chain-tokens chain-tokens - :historical? true}})) + {:transactions/get-transfers + {:chain-tokens chain-tokens + :addresses (map eip55/address->checksum addresses) + :page-size 20 + :historical? true}})) + +(fx/defn fetch-more-tx + {:events [:transactions/fetch-more]} + [{:keys [db] :as cofx} address] + (let [all-tokens (:wallet/all-tokens db) + chain (ethereum/chain-keyword db) + chain-tokens (into + {} + (map (juxt :address identity) + (tokens/tokens-for + all-tokens chain))) + min-known-block (or (get-min-known-block db address) + (:ethereum/current-block db))] + (fx/merge + cofx + {:transactions/get-transfers + {:chain-tokens chain-tokens + :addresses [address] + :before-block min-known-block + :page-size 20 + :historical? true}} + (tx-fetching-in-progress [address])))) diff --git a/src/status_im/multiaccounts/login/core.cljs b/src/status_im/multiaccounts/login/core.cljs index 50204ae453..77f8cc46be 100644 --- a/src/status_im/multiaccounts/login/core.cljs +++ b/src/status_im/multiaccounts/login/core.cljs @@ -89,7 +89,10 @@ (wallet/initialize-tokens custom-tokens) (wallet/update-balances nil) (wallet/update-prices) - (transactions/initialize))) + (transactions/initialize + (->> accounts + (filter :wallet) + (map :address))))) (fx/defn login {:events [:multiaccounts.login.ui/password-input-submitted]} diff --git a/src/status_im/node/core.cljs b/src/status_im/node/core.cljs index f7e10a2a2e..a641962265 100644 --- a/src/status_im/node/core.cljs +++ b/src/status_im/node/core.cljs @@ -6,7 +6,8 @@ [status-im.utils.config :as config] [status-im.utils.fx :as fx] [status-im.utils.platform :as utils.platform] - [status-im.utils.types :as types]) + [status-im.utils.types :as types] + [taoensso.timbre :as log]) (:require-macros [status-im.utils.slurp :refer [slurp]])) (defn- add-custom-bootnodes [config network all-bootnodes] diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index 759e191245..3cf89572d2 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -1174,6 +1174,33 @@ (let [vt-set (set visible-tokens)] (group-by :custom? (map #(assoc % :checked? (boolean (get vt-set (keyword (:symbol %))))) all-tokens))))) +(re-frame/reg-sub + :wallet/fetching-tx-history? + :<- [:wallet] + (fn [wallet [_ address]] + (get-in wallet [:fetching address :history?]))) + +(re-frame/reg-sub + :wallet/fetching-recent-tx-history? + :<- [:wallet] + (fn [wallet [_ address]] + (get-in wallet [:fetching address :recent?]))) + +(re-frame/reg-sub + :wallet/tx-history-fetched? + :<- [:wallet] + (fn [wallet [_ address]] + (get-in wallet [:fetching address :all-fetched?]))) + +(re-frame/reg-sub + :wallet/etherscan-link + (fn [db [_ address]] + (let [network (:networks/current-network db) + link (get-in constants/default-networks-by-id + [network :etherscan-link])] + (when link + (str link address))))) + (re-frame/reg-sub :wallet/error-message :<- [:wallet] diff --git a/src/status_im/ui/screens/wallet/account/views.cljs b/src/status_im/ui/screens/wallet/account/views.cljs index eab50b5c78..1d969c7704 100644 --- a/src/status_im/ui/screens/wallet/account/views.cljs +++ b/src/status_im/ui/screens/wallet/account/views.cljs @@ -89,7 +89,7 @@ (views/defview transactions [address] (views/letsubs [{:keys [transaction-history-sections]} [:wallet.transactions.history/screen address]] - [history/history-list transaction-history-sections])) + [history/history-list transaction-history-sections address])) (views/defview assets-and-collections [address] (views/letsubs [{:keys [tokens nfts]} [:wallet/visible-assets-with-values address] diff --git a/src/status_im/ui/screens/wallet/transactions/views.cljs b/src/status_im/ui/screens/wallet/transactions/views.cljs index 48d2cdfd30..9ee5752007 100644 --- a/src/status_im/ui/screens/wallet/transactions/views.cljs +++ b/src/status_im/ui/screens/wallet/transactions/views.cljs @@ -4,6 +4,7 @@ [status-im.ui.components.colors :as colors] [status-im.ui.components.list.views :as list] [status-im.ui.components.react :as react] + [status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.styles :as components.styles] [status-im.ui.components.toolbar.actions :as actions] [status-im.ui.components.toolbar.view :as toolbar] @@ -71,16 +72,71 @@ :icon-opts (merge styles/forward {:accessibility-label :show-transaction-button})}]]]]) +(defn etherscan-link [address] + (let [link @(re-frame/subscribe [:wallet/etherscan-link address])] + [react/touchable-highlight + {:on-press #(when link + (.openURL react/linking link))} + [react/view + {:style {:flex 1 + :padding-horizontal 14 + :flex-direction :row + :align-items :center + :background-color colors/blue-light + :height 52}} + [vector-icons/tiny-icon + :tiny-icons/tiny-external + {:color colors/blue + :container-style {:margin-right 5}}] + [react/text + {:style {:marging-left 10 + :color colors/blue}} + (i18n/label :t/check-on-etherscan)]]])) + (defn history-list - [transactions-history-sections] - [react/view components.styles/flex - [list/section-list {:sections transactions-history-sections - :key-fn :hash - :render-fn #(render-transaction %) - :empty-component - [react/i18n-text {:style styles/empty-text - :key :transactions-history-empty}] - :refreshing false}]]) + [transactions-history-sections address] + (let [fetching-recent-history? @(re-frame/subscribe [:wallet/fetching-recent-tx-history? address]) + fetching-more-history? @(re-frame/subscribe [:wallet/fetching-tx-history? address]) + all-fetched? @(re-frame/subscribe [:wallet/tx-history-fetched? address])] + [react/view components.styles/flex + [etherscan-link address] + (when fetching-recent-history? + [react/view + {:style {:flex 1 + :height 40 + :margin-vertical 16}} + [react/activity-indicator {:size :large + :animating true}]]) + [list/section-list + {:sections transactions-history-sections + :key-fn :hash + :render-fn #(render-transaction %) + :empty-component + [react/i18n-text {:style styles/empty-text + :key :transactions-history-empty}] + :refreshing false}] + (when (and (not fetching-recent-history?) + (not all-fetched?)) + (if fetching-more-history? + [react/view + {:style {:flex 1 + :height 40 + :margin-vertical 8}} + [react/activity-indicator {:size :large + :animating true}]] + [react/view + {:style {:flex 1 + :padding-horizontal 14 + :height 52 + :align-items :center + :justify-content :center + :background-color colors/blue-light}} + [react/text + {:style {:color colors/blue} + :on-press (when-not fetching-more-history? + #(re-frame/dispatch + [:transactions/fetch-more address]))} + (i18n/label :t/transactions-load-more)]]))])) (defn- render-item-filter [{:keys [id label checked? on-touch]}] [react/view {:accessibility-label :filter-item} @@ -213,4 +269,4 @@ (defview transaction-details [] (letsubs [{:keys [hash address]} [:get-screen-params]] (when (and hash address) - [transaction-details-view hash address]))) \ No newline at end of file + [transaction-details-view hash address]))) diff --git a/status-go-version.json b/status-go-version.json index e2be4f806f..dc01035a7e 100644 --- a/status-go-version.json +++ b/status-go-version.json @@ -2,7 +2,7 @@ "_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh ' instead", "owner": "status-im", "repo": "status-go", - "version": "v0.39.11", - "commit-sha1": "a4f88d001768d2934f1ddbd75d757c52e823accb", - "src-sha256": "0vai6sdcf930r7zp2pmr3b5b2402xmvxn8087lfb34q4hwgscw3j" + "version": "v0.40.0", + "commit-sha1": "dc31c818fce6dbff6cfd76da09d901b0df742e2e", + "src-sha256": "1f0fp5c4lqnh244wcvbnl5i4p9fb0f3i0rjjvbiqwkz9zwwp3xlv" } diff --git a/translations/en.json b/translations/en.json index 1963648481..4aa94b9b2f 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1160,5 +1160,7 @@ "delete-account": "Delete account", "watch-only": "Watch-only", "cant-report-bug": "Can't report a bug", - "mail-should-be-configured": "Mail client should be configured" + "mail-should-be-configured": "Mail client should be configured", + "check-on-etherscan": "Check on etherscan", + "transactions-load-more": "Load more" }