Add reading nodes from contract

This PR is part of network incentivisation. It adds a way for a client
to pull nodes from a contract.

This is done by selecting the `eth.contract` fleet. If that is selected
on login it will fetch nodes from a contract and pass them to status-go.
If these can't be fetched, it will default to `eth.beta`.

Currently contract information are hard-coded, but eventually the user
will be able to add their own (probably).

Toggled off in release.

Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
Andrea Maria Piana 2019-03-29 19:00:12 +01:00
parent c2fc510c8e
commit f8674c0ee1
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
27 changed files with 233 additions and 33 deletions

1
.env
View File

@ -19,3 +19,4 @@ RN_BRIDGE_THRESHOLD_WARNINGS=0
RPC_NETWORKS_ONLY=0
STICKERS_ENABLED=1
PARTITIONED_TOPIC=0
CONTRACT_NODES=1

View File

@ -16,3 +16,4 @@ POW_TIME=1
RN_BRIDGE_THRESHOLD_WARNINGS=0
STICKERS_ENABLED=0
PARTITIONED_TOPIC=0
CONTRACT_NODES=1

View File

@ -19,3 +19,4 @@ RN_BRIDGE_THRESHOLD_WARNINGS=0
RPC_NETWORKS_ONLY=0
STICKERS_ENABLED=1
PARTITIONED_TOPIC=0
CONTRACT_NODES=1

View File

@ -17,3 +17,4 @@ RN_BRIDGE_THRESHOLD_WARNINGS=0
RPC_NETWORKS_ONLY=0
STICKERS_ENABLED=0
PARTITIONED_TOPIC=0
CONTRACT_NODES=1

View File

@ -16,3 +16,4 @@ POW_TIME=1
RN_BRIDGE_THRESHOLD_WARNINGS=0
STICKERS_ENABLED=0
PARTITIONED_TOPIC=0
CONTRACT_NODES=1

View File

@ -1 +1 @@
0.23.0-beta.8-chaos
0.23.0-beta.10

View File

@ -1047,6 +1047,27 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
StatusThreadPoolExecutor.getInstance().execute(r);
}
@ReactMethod
public void getNodesFromContract(final String rpcEndpoint, final String contractAddress, final Callback callback) {
Log.d(TAG, "getNodesFromContract");
if (!checkAvailability()) {
callback.invoke(false);
return;
}
Runnable r = new Runnable() {
@Override
public void run() {
String res = Statusgo.getNodesFromContract(rpcEndpoint, contractAddress);
Log.d(TAG, res);
callback.invoke(res);
}
};
StatusThreadPoolExecutor.getInstance().execute(r);
}
@Override
public @Nullable
Map<String, Object> getConstants() {

View File

@ -436,6 +436,16 @@ void RCTStatus::updateMailservers(QString enodes, double callbackId) {
}, enodes, callbackId);
}
void RCTStatus::getNodesFromContract(QString url, QString address, double callbackId) {
Q_D(RCTStatus);
qCDebug(RCTSTATUS) << "::getNodesFromContract call - callbackId:" << callbackId;
QtConcurrent::run([&](QString url, QString address, double callbackId) {
const char* result = GetNodesFromContract(url.toUtf8().data(), address.toUtf8().data());
logStatusGoResult("::getNodesFromContract GetNodesFromContract", result);
d->bridge->invokePromiseCallback(callbackId, QVariantList{result});
}, url, address, callbackId);
}
void RCTStatus::chaosModeUpdate(bool on, double callbackId) {
Q_D(RCTStatus);
qCDebug(RCTSTATUS) << "::chaosModeUpdate call - callbackId:" << callbackId;

View File

@ -51,6 +51,7 @@ public:
Q_INVOKABLE void enableInstallation(QString installationId, double callbackId);
Q_INVOKABLE void disableInstallation(QString installationId, double callbackId);
Q_INVOKABLE void updateMailservers(QString enodes, double callbackId);
Q_INVOKABLE void getNodesFromContract(QString url, QString address, double callbackId);
Q_INVOKABLE void chaosModeUpdate(bool on, double callbackId);
Q_INVOKABLE void setAdjustResize();

View File

@ -254,6 +254,17 @@ RCT_EXPORT_METHOD(updateMailservers:(NSString *)enodes
#endif
}
//////////////////////////////////////////////////////////////////// getNodesFromContract
RCT_EXPORT_METHOD(getNodesFromContract:(NSString *)url
address:(NSString *) address
callback:(RCTResponseSenderBlock)callback) {
NSString* result = StatusgoGetNodesFromContract(url, address);
callback(@[result]);
#if DEBUG
NSLog(@"GetNodesFromContract() method called");
#endif
}
//////////////////////////////////////////////////////////////////// chaosModeUpdate
RCT_EXPORT_METHOD(chaosModeUpdate:(BOOL)on
callback:(RCTResponseSenderBlock)callback) {

View File

@ -89,6 +89,11 @@
"node-01.do-ams3.eth.test": "enode://1d193635e015918fb85bbaf774863d12f65d70c6977506187ef04420d74ec06c9e8f0dcb57ea042f85df87433dab17a1260ed8dde1bdf9d6d5d2de4b7bf8e993@206.189.243.163:443",
"node-01.gc-us-central1-a.eth.test": "enode://f593a27731bc0f8eb088e2d39222c2d59dfb9bf0b3950d7a828d51e8ab9e08fffbd9916a82fd993c1a080c57c2bd70ed6c36f489a969de697aff93088dbee1a9@35.194.31.108:443"
}
},
"eth.contract": {
"boot": {},
"mail": {},
"whisper": {}
}
},
"meta": {

View File

@ -67,12 +67,13 @@
{})))
(fx/defn switch-chaos-mode [{:keys [db] :as cofx} chaos-mode?]
(let [settings (get-in db [:account/account :settings])]
(fx/merge cofx
{::chaos-mode-changed chaos-mode?}
(accounts.update/update-settings
(assoc settings :chaos-mode? chaos-mode?)
{}))))
(when (:account/account db)
(let [settings (get-in db [:account/account :settings])]
(fx/merge cofx
{::chaos-mode-changed chaos-mode?}
(accounts.update/update-settings
(assoc settings :chaos-mode? chaos-mode?)
{})))))
(fx/defn enable-notifications [cofx desktop-notifications?]
(accounts.update/account-update cofx

View File

@ -4,6 +4,8 @@
[status-im.data-store.core :as data-store]
[status-im.native-module.core :as status]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.config :as config]
[status-im.fleet.core :as fleet]
[status-im.utils.fx :as fx]
[status-im.react-native.js-dependencies :as rn-dependencies]
[status-im.utils.keychain.core :as keychain]
@ -14,12 +16,33 @@
[status-im.utils.platform :as platform]
[status-im.protocol.core :as protocol]
[status-im.models.wallet :as models.wallet]
[status-im.utils.handlers :as handlers]
[status-im.models.transactions :as transactions]
[status-im.i18n :as i18n]
[status-im.node.core :as node]
[status-im.ui.screens.mobile-network-settings.events :as mobile-network]
[status-im.chaos-mode.core :as chaos-mode]))
(def rpc-endpoint "https://goerli.infura.io/v3/f315575765b14720b32382a61a89341a")
(def contract-address "0xfbf4c8e2B41fAfF8c616a0E49Fb4365a5355Ffaf")
(def contract-fleet? #{:eth.contract})
(defn fetch-nodes [current-fleet resolve reject]
(let [default-nodes (-> (fleet/fleets {})
(get-in [:eth.beta :mail])
vals)]
(if config/contract-nodes-enabled?
(do
(log/debug "fetching contract fleet" current-fleet)
(status/get-nodes-from-contract
rpc-endpoint
contract-address
(handlers/response-handler resolve
(fn [error]
(log/warn "could not fetch nodes from contract defaulting to eth.beta")
(resolve default-nodes)))))
(resolve default-nodes))))
(defn login! [address password]
(status/login address password #(re-frame/dispatch [:accounts.login.callback/login-success %])))
@ -31,11 +54,18 @@
(defn clear-web-data! []
(status/clear-web-data))
(defn change-account! [address password create-database-if-not-exist?]
(defn change-account! [address
password
create-database-if-not-exist?
current-fleet]
;; No matter what is the keychain we use, as checks are done on decrypting base
(.. (keychain/safe-get-encryption-key)
(then #(data-store/change-account address password % create-database-if-not-exist?))
(then (fn [] (re-frame/dispatch [:init.callback/account-change-success address])))
(then #(js/Promise. (fn [resolve reject]
(if (contract-fleet? current-fleet)
(fetch-nodes current-fleet resolve reject)
(resolve)))))
(then (fn [nodes] (re-frame/dispatch [:init.callback/account-change-success address nodes])))
(catch (fn [error]
(log/warn "Could not change account" error)
;; If all else fails we fallback to showing initial error
@ -65,7 +95,10 @@
(assoc-in [:accounts/login :processing] true)
(assoc :node/on-ready :login))
:accounts.login/clear-web-data nil
:data-store/change-account [address password create-database?]})))
:data-store/change-account [address
password
create-database?
(get-in db [:accounts/accounts address :settings :fleet])]})))
(fx/defn account-and-db-password-do-not-match
[{:keys [db] :as cofx} error]
@ -151,7 +184,15 @@
:database-does-not-exist
(let [{:keys [address password]} (accounts.db/credentials cofx)]
{:data-store/change-account [address password true]}))
{:data-store/change-account [address
password
true
(get-in cofx
[:db
:accounts/accounts
address
:settings
:fleet])]}))
{:db (update db :accounts/login assoc
:error error
:processing false)})))))
@ -274,6 +315,8 @@
(re-frame/reg-fx
:data-store/change-account
(fn [[address password create-database-if-not-exist?]]
(change-account! address (security/safe-unmask-data password)
create-database-if-not-exist?)))
(fn [[address password create-database-if-not-exist? current-fleet]]
(change-account! address
(security/safe-unmask-data password)
create-database-if-not-exist?
current-fleet)))

View File

@ -110,10 +110,12 @@
(chat-loading/initialize-chats {:from 10}))))
(defn account-change-success
[{:keys [db] :as cofx} [_ address]]
(let [{:node/keys [status on-ready]} db]
[{:keys [db] :as cofx} [_ address nodes]]
(let [{:node/keys [status]} db]
(fx/merge
cofx
(when nodes
(fleet/set-nodes :eth.contract nodes))
(if (= status :started)
(accounts.login/login)
(node/initialize (get-in db [:accounts/login :address])))

View File

@ -23,10 +23,15 @@
(defn fleet-supports-les? [fleet]
(not (nil? (some #(= fleet %) fleets-with-les))))
(def fleets
(reduce merge (map #(:fleets (types/json->clj %))
[(slurp "resources/config/fleets.json")
(slurp "resources/config/fleets-les.json")])))
(def default-fleets (slurp "resources/config/fleets.json"))
(def default-les-fleets (slurp "resources/config/fleets-les.json"))
(defn fleets [{:keys [custom-fleets]}]
(as-> [default-fleets
default-les-fleets] $
(mapv #(:fleets (types/json->clj %)) $)
(conj $ custom-fleets)
(reduce merge $)))
(defn format-mailserver
[mailserver address]
@ -42,11 +47,11 @@
{}
mailservers))
(def default-mailservers
(defn default-mailservers [db]
(reduce (fn [acc [fleet node-by-type]]
(assoc acc fleet (format-mailservers (:mail node-by-type))))
{}
fleets))
(fleets db)))
(fx/defn show-save-confirmation
[{:keys [db] :as cofx} fleet]
@ -57,6 +62,30 @@
:on-accept #(re-frame/dispatch [:fleet.ui/save-fleet-confirmed (keyword fleet)])
:on-cancel nil}})
(defn nodes->fleet [nodes]
(letfn [(format-nodes [nodes]
(reduce (fn [acc n]
(assoc acc
(keyword n)
n))
{}
nodes))]
{:boot (format-nodes nodes)
:mail (format-nodes nodes)
:whisper (format-nodes nodes)}))
(fx/defn set-nodes [{:keys [db]} fleet nodes]
{:db (-> db
(assoc-in [:custom-fleets fleet] (nodes->fleet nodes))
(assoc-in [:mailserver/mailservers fleet] (format-mailservers
(reduce
(fn [acc e]
(assoc acc
(keyword e)
e))
{}
nodes))))})
(fx/defn save
[{:keys [db now] :as cofx} fleet]
(let [settings (get-in db [:account/account :settings])]

View File

@ -167,7 +167,8 @@
(let [{:universal-links/keys [url]
:keys [accounts/accounts accounts/create networks/networks network
network-status peers-count peers-summary view-id navigation-stack
desktop/desktop hardwallet
mailserver/mailservers
desktop/desktop hardwallet custom-fleets
device-UUID semaphores accounts/login]
:node/keys [status on-ready]
:or {network (get app-db :network)}} db
@ -185,11 +186,13 @@
:account/account current-account
:accounts/login login
:accounts/accounts accounts
:mailserver/mailservers mailservers
:network-status network-status
:network network
:network/type (:network/type db)
:chain (ethereum/network->chain-name account-network)
:universal-links/url url
:custom-fleets custom-fleets
:peers-summary peers-summary
:peers-count peers-count
:device-UUID device-UUID

View File

@ -98,3 +98,5 @@
(def rooted-device? native-module/rooted-device?)
(def chaos-mode-update native-module/chaos-mode-update)
(def get-nodes-from-contract native-module/get-nodes-from-contract)

View File

@ -153,6 +153,10 @@
(when status
(.chaosModeUpdate status on on-result)))
(defn get-nodes-from-contract [rpc-endpoint contract-address on-result]
(when status
(.getNodesFromContract status rpc-endpoint contract-address on-result)))
(defn rooted-device? [callback]
(cond
;; we assume that iOS is safe by default

View File

@ -90,7 +90,7 @@
(defn- get-account-node-config [db address]
(let [accounts (get db :accounts/accounts)
current-fleet-key (fleet/current-fleet db address)
current-fleet (get fleet/fleets current-fleet-key)
current-fleet (get (fleet/fleets db) current-fleet-key)
rendezvous-nodes (pick-nodes 3 (vals (:rendezvous current-fleet)))
{:keys [network installation-id settings bootnodes networks]}
(merge

View File

@ -47,7 +47,7 @@
:transport/chats {}
:transport/filters {}
:transport/message-envelopes {}
:mailserver/mailservers fleet/default-mailservers
:mailserver/mailservers (fleet/default-mailservers {})
:mailserver/topics {}
:mailserver/pending-requests 0
:chat/cooldowns 0

View File

@ -7,3 +7,8 @@
:settings/current-fleet
(fn [db _]
(fleet/current-fleet db)))
(re-frame/reg-sub
:fleets/custom-fleets
(fn [{:keys [custom-fleets]} _]
custom-fleets))

View File

@ -34,18 +34,19 @@
[react/text {:style styles/fleet-item-name-text}
fleet]]]])))
(def fleets
(map name (keys fleet-core/fleets)))
(defn fleets [custom-fleets]
(map name (keys (fleet-core/fleets {:custom-fleets custom-fleets}))))
(views/defview fleet-settings []
(views/letsubs [current-fleet [:settings/current-fleet]]
(views/letsubs [custom-fleets [:fleets/custom-fleets]
current-fleet [:settings/current-fleet]]
[react/view {:flex 1}
[status-bar/status-bar]
[toolbar/toolbar {}
toolbar/default-nav-back
[toolbar/content-title (i18n/label :t/fleet-settings)]]
[react/view styles/wrapper
[list/flat-list {:data fleets
[list/flat-list {:data (fleets custom-fleets)
:default-separator? false
:key-fn identity
:render-fn (render-row (name current-fleet))}]]]))

View File

@ -33,6 +33,7 @@
(def partitioned-topic-enabled? (enabled? (get-config :PARTITIONED_TOPIC "0")))
(def tr-to-talk-enabled? (enabled? (get-config :TRIBUTE_TO_TALK 0)))
(def max-message-delivery-attempts (js/parseInt (get-config :MAX_MESSAGE_DELIVERY_ATTEMPTS "6")))
(def contract-nodes-enabled? (enabled? (get-config :CONTRACT_NODES "0")))
;; CONFIG VALUES
(def log-level

View File

@ -0,0 +1,51 @@
(ns status-im.test.fleet.core
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.constants :as constants]
[status-im.fleet.core :as fleet]))
(deftest fleets-test
(testing "not passing any extra fleet"
(testing "it returns the default fleets"
(is (=
#{:eth.beta
:eth.staging
:eth.test
:eth.contract
:les.dev.ropsten}
(into #{}
(keys (fleet/fleets {})))))))
(testing "passing a custom fleet"
(testing "it sets the custom fleet"
(is (= {:mail {"a" "a"}
:whisper {"w" "w"}
:boot {"b" "b"}}
(:custom-fleet
(fleet/fleets {:custom-fleets {:custom-fleet
{:mail {"a" "a"}
:whisper {"w" "w"}
:boot {"b" "b"}}}})))))))
(deftest set-nodes-test
(testing "set-nodes"
(let [actual (fleet/set-nodes {:db {}} :test-fleet ["a" "b" "c"])
actual-custom-fleet (get-in actual [:db :custom-fleets])
actual-mailservers (get-in actual [:db :mailserver/mailservers :test-fleet])]
(testing "it sets the custom fleet in the db"
(is actual-custom-fleet))
(testing "it sets the custom mailservers in the db"
(is actual-mailservers))
(testing "it correctly formats mailservers"
(is (= {:a {:id :a
:name "a"
:password constants/mailserver-password
:address "a"}
:b {:id :b
:name "b"
:password constants/mailserver-password
:address "b"}
:c {:id :c
:name "c"
:password constants/mailserver-password
:address "c"}}
actual-mailservers))))))

View File

@ -14,6 +14,7 @@
[status-im.test.wallet.transactions.subs]
[status-im.test.wallet.transactions.views]
[status-im.test.mailserver.core]
[status-im.test.fleet.core]
[status-im.test.group-chats.core]
[status-im.test.pairing.core]
[status-im.test.node.core]
@ -86,6 +87,7 @@
'status-im.test.data-store.realm.core
'status-im.test.extensions.core
'status-im.test.mailserver.core
'status-im.test.fleet.core
'status-im.test.group-chats.core
'status-im.test.pairing.core
'status-im.test.node.core

View File

@ -104,6 +104,7 @@
:last-request nil
:desktop-notifications? false
:settings {:web3-opt-in? true
:fleet :eth.beta
:wallet {:visible-tokens {:testnet #{:STT
:HND}
:mainnet #{:SNT}

View File

@ -12,7 +12,8 @@
(deftest on-password-input-submitted
(testing
"handling :accounts.login.ui/password-input-submitted event"
(let [cofx {:db {:accounts/login {:address "address"
(let [cofx {:db {:accounts/accounts {"address" {:settings {:fleet "fleet"}}}
:accounts/login {:address "address"
:password "password"}}}
create-database? false
efx (login.core/user-login cofx create-database?)]
@ -20,7 +21,7 @@
(is (contains? efx :accounts.login/clear-web-data)))
(testing "Change account."
(is (= (:data-store/change-account efx)
["address" "password" false])))
["address" "password" false "fleet"])))
(testing "set `node/on-ready` handler"
(is (= (get-in efx [:db :node/on-ready]) :login)))
(testing "start activity indicator"
@ -142,14 +143,15 @@
(deftest on-verify-account-success-after-database-does-not-exist
(testing ":accounts.login.callback/verify-success event received."
(let [cofx {:db {:accounts/login {:address "address"
(let [cofx {:db {:accounts/accounts {"address" {:settings {:fleet "fleet"}}}
:accounts/login {:address "address"
:password "password"}}}
verify-result "{\"error\":\"\"}"
realm-error {:error :database-does-not-exist}
efx (login.core/verify-callback
cofx verify-result realm-error)]
(testing "Change account."
(is (= ["address" "password" true]
(is (= ["address" "password" true "fleet"]
(:data-store/change-account efx))))
(testing "Stop node."
(is (contains? efx :node/stop))))))