Add custom bootnodes

Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
Andrea Maria Piana 2018-05-23 10:41:59 +02:00
parent 7711dc51ad
commit 33ad919508
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
34 changed files with 605 additions and 46 deletions

View File

@ -19,3 +19,4 @@ INSTABUG_SURVEYS=1
GROUP_CHATS_ENABLED=0
FORCE_SENT_RECEIVED_TRACKING=1
ADD_CUSTOM_MAILSERVERS_ENABLED=0
BOOTNODES_SETTINGS_ENABLED=0

View File

@ -20,3 +20,4 @@ GROUP_CHATS_ENABLED=0
FORCE_SENT_RECEIVED_TRACKING=0
USE_SYM_KEY=0
ADD_CUSTOM_MAILSERVERS_ENABLED=0
BOOTNODES_SETTINGS_ENABLED=0

View File

@ -17,7 +17,7 @@ dependencies {
implementation 'com.github.ericwlange:AndroidJSCore:3.0.1'
implementation 'status-im:function:0.0.1'
String statusGoVersion = 'develop-g5aae87ab'
String statusGoVersion = 'develop-gbc14e6fa'
final String statusGoGroup = 'status-im', statusGoName = 'status-go'
// Check if the local status-go jar exists, and compile against that if it does

View File

@ -183,6 +183,8 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
}
}
int testnetNetworkId = 3;
String testnetDataDir = root + "/ethereum/testnet";
String oldKeystoreDir = testnetDataDir + "/keystore";
String newKeystoreDir = root + "/keystore";
@ -204,14 +206,19 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
}
}
String config = Statusgo.GenerateConfig(testnetDataDir, 3);
String config;
try {
JSONObject customConfig = new JSONObject(defaultConfig);
String dataDir = root + customConfig.get("DataDir");
config = Statusgo.GenerateConfig(dataDir, customConfig.getInt("NetworkId"));
JSONObject jsonConfig = new JSONObject(config);
String gethLogFilePath = prepareLogsFile();
boolean logsEnabled = (gethLogFilePath != null) && !TextUtils.isEmpty(this.logLevel);
String dataDir = root + customConfig.get("DataDir");
jsonConfig.put("LogEnabled", (gethLogFilePath != null && logsEnabled));
jsonConfig.put("LogFile", gethLogFilePath);
jsonConfig.put("LogLevel", TextUtils.isEmpty(this.logLevel) ? "ERROR" : this.logLevel.toUpperCase());
@ -236,10 +243,22 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
} catch (Exception e) {
}
try {
Object clusterConfig = customConfig.get("ClusterConfig");
if (clusterConfig != null) {
Log.d(TAG, "ClusterConfig is not null");
jsonConfig.put("ClusterConfig", clusterConfig);
}
} catch (Exception e) {
Log.w(TAG, "Something went wrong parsing cluster config" + e.getMessage());
}
jsonConfig.put("KeyStoreDir", newKeystoreDir);
config = jsonConfig.toString();
} catch (JSONException e) {
config = Statusgo.GenerateConfig(testnetDataDir, testnetNetworkId);
Log.d(TAG, "Something went wrong " + e.getMessage());
Log.d(TAG, "Default configuration will be used");
}

View File

@ -15,7 +15,7 @@
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self
options:(NSJSONWritingOptions) (prettyPrint ? NSJSONWritingPrettyPrinted : 0)
error:&error];
if (! jsonData) {
NSLog(@"bv_jsonStringWithPrettyPrint: error: %@", error.localizedDescription);
return @"{}";
@ -35,7 +35,7 @@
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self
options:(NSJSONWritingOptions) (prettyPrint ? NSJSONWritingPrettyPrinted : 0)
error:&error];
if (! jsonData) {
NSLog(@"bv_jsonStringWithPrettyPrint: error: %@", error.localizedDescription);
return @"[]";
@ -90,7 +90,7 @@ RCT_EXPORT_METHOD(parseJail:(NSString *)chatId
}
NSDictionary *result = [_jail parseJail:chatId withCode:js];
stringResult = [result bv_jsonStringWithPrettyPrint:NO];
callback(@[stringResult]);
}
@ -103,7 +103,7 @@ RCT_EXPORT_METHOD(callJail:(NSString *)chatId
NSLog(@"CallJail() method called");
#endif
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *stringResult;
if(_jail == nil) {
_jail = [Jail new];
@ -130,12 +130,12 @@ RCT_EXPORT_METHOD(startNode:(NSString *)configString) {
URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask]
lastObject];
NSURL *testnetFolderName = [rootUrl URLByAppendingPathComponent:@"ethereum/testnet"];
if (![fileManager fileExistsAtPath:testnetFolderName.path])
[fileManager createDirectoryAtPath:testnetFolderName.path withIntermediateDirectories:YES attributes:nil error:&error];
NSURL *flagFolderUrl = [rootUrl URLByAppendingPathComponent:@"ropsten_flag"];
if(![fileManager fileExistsAtPath:flagFolderUrl.path]){
NSLog(@"remove lightchaindata");
NSURL *lightChainData = [testnetFolderName URLByAppendingPathComponent:@"StatusIM/lightchaindata"];
@ -148,9 +148,9 @@ RCT_EXPORT_METHOD(startNode:(NSString *)configString) {
attributes:nil
error:&error];
}
NSLog(@"after remove lightchaindata");
NSURL *oldKeystoreUrl = [testnetFolderName URLByAppendingPathComponent:@"keystore"];
NSURL *newKeystoreUrl = [rootUrl URLByAppendingPathComponent:@"keystore"];
if([fileManager fileExistsAtPath:oldKeystoreUrl.path]){
@ -158,15 +158,16 @@ RCT_EXPORT_METHOD(startNode:(NSString *)configString) {
[fileManager copyItemAtPath:oldKeystoreUrl.path toPath:newKeystoreUrl.path error:nil];
[fileManager removeItemAtPath:oldKeystoreUrl.path error:nil];
}
NSLog(@"after lightChainData");
NSLog(@"preconfig: %@", configString);
NSData *configData = [configString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *configJSON = [NSJSONSerialization JSONObjectWithData:configData options:NSJSONReadingMutableContainers error:nil];
int networkId = [configJSON[@"NetworkId"] integerValue];
NSString *dataDir = [configJSON objectForKey:@"DataDir"];
NSString *upstreamURL = [configJSON valueForKeyPath:@"UpstreamConfig.URL"];
NSArray *bootnodes = [configJSON valueForKeyPath:@"ClusterConfig.BootNodes"];
NSString *networkDir = [rootUrl.path stringByAppendingString:dataDir];
NSString *devCluster = [ReactNativeConfig envFor:@"ETHEREUM_DEV_CLUSTER"];
NSString *logLevel = [[ReactNativeConfig envFor:@"LOG_LEVEL_STATUS_GO"] uppercaseString];
@ -180,19 +181,27 @@ RCT_EXPORT_METHOD(startNode:(NSString *)configString) {
[resultingConfigJson setValue:[NSNumber numberWithBool:[logLevel length] != 0] forKey:@"LogEnabled"];
[resultingConfigJson setValue:logUrl.path forKey:@"LogFile"];
[resultingConfigJson setValue:([logLevel length] == 0 ? [NSString stringWithUTF8String: "ERROR"] : logLevel) forKey:@"LogLevel"];
[resultingConfigJson setValue:[NSNumber numberWithBool:YES] forKeyPath:@"WhisperConfig.LightClient"];
if(upstreamURL != nil) {
[resultingConfigJson setValue:[NSNumber numberWithBool:YES] forKeyPath:@"UpstreamConfig.Enabled"];
[resultingConfigJson setValue:upstreamURL forKeyPath:@"UpstreamConfig.URL"];
}
if(bootnodes != nil) {
[resultingConfigJson setValue:[NSNumber numberWithBool:YES] forKeyPath:@"ClusterConfig.Enabled"];
[resultingConfigJson setValue:bootnodes forKeyPath:@"ClusterConfig.BootNodes"];
}
NSString *resultingConfig = [resultingConfigJson bv_jsonStringWithPrettyPrint:NO];
NSLog(@"node config %@", resultingConfig);
if(![fileManager fileExistsAtPath:networkDirUrl.path]) {
[fileManager createDirectoryAtPath:networkDirUrl.path withIntermediateDirectories:YES attributes:nil error:nil];
}
NSLog(@"logUrlPath %@", logUrl.path);
if(![fileManager fileExistsAtPath:logUrl.path]) {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
@ -353,7 +362,7 @@ RCT_EXPORT_METHOD(clearCookies) {
RCT_EXPORT_METHOD(clearStorageAPIs) {
[[NSURLCache sharedURLCache] removeAllCachedResponses];
NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSArray *array = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
for (NSString *string in array) {
@ -408,7 +417,7 @@ RCT_EXPORT_METHOD(getDeviceUUID:(RCTResponseSenderBlock)callback) {
NSLog(@"getDeviceUUID() method called");
#endif
NSString* Identifier = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
callback(@[Identifier]);
}
@ -420,7 +429,7 @@ RCT_EXPORT_METHOD(getDeviceUUID:(RCTResponseSenderBlock)callback) {
#endif
return;
}
NSString *sig = [NSString stringWithUTF8String:signal];
#if DEBUG
NSLog(@"SignalEvent");
@ -428,7 +437,7 @@ RCT_EXPORT_METHOD(getDeviceUUID:(RCTResponseSenderBlock)callback) {
#endif
[bridge.eventDispatcher sendAppEventWithName:@"gethEvent"
body:@{@"jsonEvent": sig}];
return;
}
@ -447,7 +456,7 @@ RCT_EXPORT_METHOD(getDeviceUUID:(RCTResponseSenderBlock)callback) {
#endif
[bridge.eventDispatcher sendAppEventWithName:@"gethEvent"
body:@{@"jsonEvent": signal}];
return;
}

View File

@ -25,7 +25,7 @@
<artifactItem>
<groupId>status-im</groupId>
<artifactId>status-go-ios-simulator</artifactId>
<version>develop-g5aae87ab</version>
<version>develop-gbc14e6fa</version>
<type>zip</type>
<overWrite>true</overWrite>
<outputDirectory>./</outputDirectory>

View File

@ -10,9 +10,22 @@
(core/single-clj :account)
(update :settings core/deserialize)))
(defn- deserialize-bootnodes [bootnodes]
(reduce-kv
(fn [acc id {:keys [chain] :as bootnode}]
(assoc-in acc [chain id] bootnode))
{}
bootnodes))
(defn- serialize-bootnodes [bootnodes]
(->> bootnodes
vals
(mapcat vals)))
(defn- deserialize-account [account]
(-> account
(update :settings core/deserialize)
(update :bootnodes deserialize-bootnodes)
(update :networks (partial reduce-kv
(fn [acc network-id props]
(assoc acc network-id
@ -31,6 +44,7 @@
(defn- serialize-account [account]
(-> account
(update :settings core/serialize)
(update :bootnodes serialize-bootnodes)
(update :networks (partial map (fn [[_ props]]
(update props :config types/clj->json))))))

View File

@ -1,7 +1,8 @@
(ns status-im.data-store.realm.schemas.base.core
(:require [status-im.data-store.realm.schemas.base.v1.core :as v1]
[status-im.data-store.realm.schemas.base.v2.core :as v2]
[status-im.data-store.realm.schemas.base.v3.core :as v3]))
[status-im.data-store.realm.schemas.base.v3.core :as v3]
[status-im.data-store.realm.schemas.base.v4.core :as v4]))
;; put schemas ordered by version
(def schemas [{:schema v1/schema
@ -12,4 +13,7 @@
:migration v2/migration}
{:schema v3/schema
:schemaVersion 3
:migration v3/migration}])
:migration v3/migration}
{:schema v4/schema
:schemaVersion 4
:migration v4/migration}])

View File

@ -0,0 +1,29 @@
(ns status-im.data-store.realm.schemas.base.v4.account)
(def schema {:name :account
:primaryKey :address
:properties {:address :string
:public-key :string
:name {:type :string :optional true}
:email {:type :string :optional true}
:status {:type :string :optional true}
:debug? {:type :bool :default false}
:photo-path :string
:signing-phrase {:type :string}
:mnemonic {:type :string :optional true}
:last-updated {:type :int :default 0}
:last-sign-in {:type :int :default 0}
:signed-up? {:type :bool
:default false}
:network :string
:networks {:type :list
:objectType :network}
:bootnodes {:type :list
:objectType :bootnode}
:last-request {:type :int :optional true}
:settings {:type :string}
:sharing-usage-data? {:type :bool :default false}
:dev-mode? {:type :bool :default false}
:seed-backed-up? {:type :bool :default false}
:wallet-set-up-passed? {:type :bool
:default false}}})

View File

@ -0,0 +1,8 @@
(ns status-im.data-store.realm.schemas.base.v4.bootnode)
(def schema {:name :bootnode
:primaryKey :id
:properties {:id :string
:name {:type :string}
:chain {:type :string}
:address {:type :string}}})

View File

@ -0,0 +1,12 @@
(ns status-im.data-store.realm.schemas.base.v4.core
(:require [status-im.data-store.realm.schemas.base.v1.network :as network]
[status-im.data-store.realm.schemas.base.v4.account :as account]
[status-im.data-store.realm.schemas.base.v4.bootnode :as bootnode]
[taoensso.timbre :as log]))
(def schema [network/schema
bootnode/schema
account/schema])
(defn migration [old-realm new-realm]
(log/debug "migrating base database v4: " old-realm new-realm))

View File

@ -622,6 +622,14 @@
:close-app-button "Confirm"
:connect-wnode-content "Connect to {{name}}?"
;; Bootnodes
:bootnodes "Bootnodes"
:bootnodes-settings "Bootnodes settings"
:bootnodes-enabled "Bootnodes enabled"
:bootnode-address "Bootnode address"
:add-bootnode "Add bootnode"
:specify-bootnode-address "Specify bootnode address"
:mainnet-warning-title "Warning!"
:mainnet-warning-text "While we highly appreciate your contribution as a tester of Status, wed like to point out the dangers. Youre switching to Mainnet mode which is still in Alpha. This means it is still in development and has not been audited yet. Some of the risks you may be exposed to include:\n\n- Accounts may be unrecoverable due to breaking changes\n- Loss of ETH and tokens\n- Failure to send or receive messages\n\nSwitching to Mainnet should be done for testing purposes only. By tapping \"I understand\", you confirm that you assume the full responsibility for all risks concerning your data and funds. "
:mainnet-warning-ok-text "I understand"

View File

@ -3,6 +3,7 @@
(:require [cljs.spec.alpha :as spec]
status-im.utils.db
status-im.ui.screens.network-settings.db
status-im.ui.screens.bootnodes-settings.db
[status-im.constants :as const]))
(defn valid-length? [password]
@ -24,6 +25,7 @@
(spec/def :account/status (spec/nilable string?))
(spec/def :account/network (spec/nilable string?))
(spec/def :account/networks (spec/nilable :networks/networks))
(spec/def :account/bootnodes (spec/nilable :bootnodes/bootnodes))
(spec/def :account/wnode (spec/nilable string?))
(spec/def :account/settings (spec/nilable (spec/map-of keyword? any?)))
(spec/def :account/signing-phrase :global/not-empty-string)
@ -41,7 +43,8 @@
:account/networks :account/settings :account/wnode
:account/last-sign-in :account/sharing-usage-data? :account/dev-mode?
:account/seed-backed-up? :account/mnemonic
:account/wallet-set-up-passed? :account/last-request]))
:account/wallet-set-up-passed? :account/last-request
:account/bootnodes]))
(spec/def :accounts/accounts (spec/nilable (spec/map-of :account/address :accounts/account)))

View File

@ -63,12 +63,32 @@
(assoc db :node/after-start nil)
address password)))
(defn add-custom-bootnodes [config network all-bootnodes]
(let [bootnodes (as-> all-bootnodes $
(get $ network)
(vals $)
(map :address $))]
(if (seq bootnodes)
(assoc config :ClusterConfig {:Enabled true
:BootNodes bootnodes})
config)))
(defn get-network-by-address [db address]
(let [accounts (get db :accounts/accounts)
{:keys [network networks]} (get accounts address)
config (get-in networks [network :config])]
{:network network
:config config}))
{:keys [network
settings
bootnodes
networks]} (get accounts address)
use-custom-bootnodes (get-in settings [:bootnodes network])
config (cond-> (get-in networks [network :config])
(and
config/bootnodes-settings-enabled?
use-custom-bootnodes)
(add-custom-bootnodes network bootnodes))]
{:use-custom-bootnodes use-custom-bootnodes
:network network
:config config}))
(defn wrap-with-initialize-geth-fx [db address password]
(let [{:keys [network config]} (get-network-by-address db address)]
@ -87,21 +107,31 @@
{:db (assoc db :node/after-stop [::start-node address password])
::stop-node nil})
(defn- restart-node? [account-network network use-custom-bootnodes]
(or (not= account-network network)
(and config/bootnodes-settings-enabled?
use-custom-bootnodes)))
(defn login-account [{{:keys [network status-node-started?] :as db} :db} [_ address password]]
(let [{use-custom-bootnodes :use-custom-bootnodes
account-network :network} (get-network-by-address db address)
db' (-> db
(assoc-in [:accounts/login :processing] true))
wrap-fn (cond (not status-node-started?)
wrap-with-initialize-geth-fx
(not (restart-node? account-network
network
use-custom-bootnodes))
wrap-with-login-account-fx
:else
wrap-with-stop-node-fx)]
(wrap-fn db' address password)))
(register-handler-fx
:login-account
(fn [{{:keys [network status-node-started?] :as db} :db} [_ address password]]
(let [{account-network :network} (get-network-by-address db address)
db' (-> db
(assoc-in [:accounts/login :processing] true))
wrap-fn (cond (not status-node-started?)
wrap-with-initialize-geth-fx
(= account-network network)
wrap-with-login-account-fx
:else
wrap-with-stop-node-fx)]
(wrap-fn db' address password))))
login-account)
(register-handler-fx
:login-handler

View File

@ -0,0 +1,18 @@
(ns status-im.ui.screens.bootnodes-settings.db
(:require-macros [status-im.utils.db :refer [allowed-keys]])
(:require
[clojure.string :as string]
[cljs.spec.alpha :as spec]))
(spec/def ::not-blank-string (complement string/blank?))
(spec/def :bootnode/address ::not-blank-string)
(spec/def :bootnode/name ::not-blank-string)
(spec/def :bootnode/id ::not-blank-string)
(spec/def :bootnode/chain ::not-blank-string)
(spec/def :bootnode/bootnode (allowed-keys :req-un [:bootnode/chain
:bootnode/address
:bootnode/name
:bootnode/id]))
(spec/def :bootnodes/bootnodes (spec/nilable (spec/map-of :bootnode/id (spec/map-of :bootnode/id :bootnode/bootnode))))

View File

@ -0,0 +1,51 @@
(ns status-im.ui.screens.bootnodes-settings.edit-bootnode.events
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.utils.handlers :refer [register-handler] :as handlers]
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.ui.screens.accounts.utils :as accounts.utils]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.types :as types]
[status-im.utils.inbox :as utils.inbox]))
(defn- new-bootnode [id bootnode-name address chain]
{:address address
:chain chain
:id (string/replace id "-" "")
:name bootnode-name})
(defn save-new-bootnode [{{:bootnodes/keys [manage] :account/keys [account] :as db} :db :as cofx} _]
(let [{:keys [name url]} manage
network (:network db)
bootnode (new-bootnode
(:random-id cofx)
(:value name)
(:value url)
network)
new-bootnodes (assoc-in (:bootnodes account) [network (:id bootnode)] bootnode)]
(handlers-macro/merge-fx cofx
{:db (dissoc db :bootnodes/manage)
:dispatch [:navigate-back]}
(accounts.utils/account-update {:bootnodes new-bootnodes}))))
(handlers/register-handler-fx
:save-new-bootnode
[(re-frame/inject-cofx :random-id)]
save-new-bootnode)
(handlers/register-handler-fx
:bootnode-set-input
(fn [{db :db} [_ input-key value]]
{:db (update db :bootnodes/manage assoc input-key {:value value
:error (if (= input-key :name)
(string/blank? value)
(not (utils.inbox/valid-enode-address? value)))})}))
(handlers/register-handler-fx
:edit-bootnode
(fn [{db :db} _]
{:db (update-in db [:bootnodes/manage] assoc
:name {:error true}
:url {:error true})
:dispatch [:navigate-to :edit-bootnode]}))

View File

@ -0,0 +1,15 @@
(ns status-im.ui.screens.bootnodes-settings.edit-bootnode.styles
(:require-macros [status-im.utils.styles :refer [defstyle]]))
(def edit-bootnode-view
{:flex 1
:margin-horizontal 16
:margin-vertical 15})
(def input-container
{:margin-bottom 15})
(def bottom-container
{:flex-direction :row
:margin-horizontal 12
:margin-vertical 15})

View File

@ -0,0 +1,14 @@
(ns status-im.ui.screens.bootnodes-settings.edit-bootnode.subs
(:require [re-frame.core :refer [reg-sub]]))
(reg-sub
:get-manage-bootnode
:<- [:get :bootnodes/manage]
(fn [manage]
manage))
(reg-sub
:manage-bootnode-valid?
:<- [:get-manage-bootnode]
(fn [manage]
(not-any? :error (vals manage))))

View File

@ -0,0 +1,42 @@
(ns status-im.ui.screens.bootnodes-settings.edit-bootnode.views
(:require-macros [status-im.utils.views :as views])
(:require
[re-frame.core :as re-frame]
[status-im.ui.components.react :as react]
[status-im.i18n :as i18n]
[status-im.ui.components.styles :as components.styles]
[status-im.ui.components.common.common :as components.common]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.text-input.view :as text-input]
[status-im.ui.screens.bootnodes-settings.edit-bootnode.styles :as styles]))
(views/defview edit-bootnode []
(views/letsubs [manage-bootnode [:get-manage-bootnode]
is-valid? [:manage-bootnode-valid?]]
[react/view components.styles/flex
[status-bar/status-bar]
[react/keyboard-avoiding-view components.styles/flex
[toolbar/simple-toolbar (i18n/label :t/add-bootnode)]
[react/scroll-view
[react/view styles/edit-bootnode-view
[text-input/text-input-with-label
{:label (i18n/label :t/name)
:placeholder (i18n/label :t/specify-name)
:container styles/input-container
:default-value (get-in manage-bootnode [:name :value])
:on-change-text #(re-frame/dispatch [:bootnode-set-input :name %])
:auto-focus true}]
[text-input/text-input-with-label
{:label (i18n/label :t/bootnode-address)
:placeholder (i18n/label :t/specify-bootnode-address)
:container styles/input-container
:default-value (get-in manage-bootnode [:url :value])
:on-change-text #(re-frame/dispatch [:bootnode-set-input :url %])}]]]
[react/view styles/bottom-container
[react/view components.styles/flex]
[components.common/bottom-button
{:forward? true
:label (i18n/label :t/save)
:disabled? (not is-valid?)
:on-press #(re-frame/dispatch [:save-new-bootnode])}]]]]))

View File

@ -0,0 +1,22 @@
(ns status-im.ui.screens.bootnodes-settings.events
(:require [re-frame.core :as re-frame]
[status-im.utils.handlers :as handlers]
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.ui.screens.accounts.events :as accounts-events]
[status-im.i18n :as i18n]
[status-im.transport.core :as transport]
status-im.ui.screens.bootnodes-settings.edit-bootnode.events
[status-im.utils.ethereum.core :as ethereum]))
(defn toggle-custom-bootnodes [value {:keys [db] :as cofx}]
(let [network (get-in db [:account/account :network])
settings (get-in db [:account/account :settings])]
(handlers-macro/merge-fx cofx
(accounts-events/update-settings
(assoc-in settings [:bootnodes network] value)
[:logout]))))
(handlers/register-handler-fx
:toggle-custom-bootnodes
(fn [cofx [_ value]]
(toggle-custom-bootnodes value cofx)))

View File

@ -0,0 +1,30 @@
(ns status-im.ui.screens.bootnodes-settings.styles
(:require [status-im.ui.components.colors :as colors])
(:require-macros [status-im.utils.styles :refer [defstyle]]))
(def wrapper
{:flex 1
:background-color :white})
(def bootnode-item-inner
{:padding-horizontal 16})
(defstyle bootnode-item
{:flex-direction :row
:background-color :white
:align-items :center
:padding-horizontal 16
:ios {:height 64}
:android {:height 56}})
(defstyle bootnode-item-name-text
{:color colors/black
:ios {:font-size 17
:letter-spacing -0.2
:line-height 20}
:android {:font-size 16}})
(defstyle switch-container
{:height 50
:background-color colors/white
:padding-left 15})

View File

@ -0,0 +1,15 @@
(ns status-im.ui.screens.bootnodes-settings.subs
(:require [re-frame.core :as re-frame]
status-im.ui.screens.bootnodes-settings.edit-bootnode.subs
[status-im.utils.ethereum.core :as ethereum]))
(re-frame/reg-sub :settings/bootnodes-enabled
:<- [:get :account/account]
(fn [account]
(let [{:keys [network settings]} account]
(get-in settings [:bootnodes network]))))
(re-frame/reg-sub :settings/network-bootnodes
:<- [:get :account/account]
(fn [account]
(get-in account [:bootnodes (:network account)])))

View File

@ -0,0 +1,46 @@
(ns status-im.ui.screens.bootnodes-settings.views
(:require-macros [status-im.utils.views :as views])
(:require [re-frame.core :as re-frame]
[status-im.i18n :as i18n]
[status-im.utils.config :as config]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.toolbar.actions :as toolbar.actions]
[status-im.ui.screens.profile.components.views :as profile.components]
[status-im.ui.screens.bootnodes-settings.styles :as styles]))
(defn navigate-to-add-bootnode []
(re-frame/dispatch [:edit-bootnode]))
(defn render-row [{:keys [name id]}]
[react/view
{:accessibility-label :bootnode-item}
[react/view styles/bootnode-item
[react/view styles/bootnode-item-inner
[react/text {:style styles/bootnode-item-name-text}
name]]]])
(views/defview bootnodes-settings []
(views/letsubs [bootnodes-enabled [:settings/bootnodes-enabled]
bootnodes [:settings/network-bootnodes]]
[react/view {:flex 1}
[status-bar/status-bar]
[toolbar/toolbar {}
toolbar/default-nav-back
[toolbar/content-title (i18n/label :t/bootnodes-settings)]
[toolbar/actions
[(toolbar.actions/add false navigate-to-add-bootnode)]]]
[react/view styles/switch-container
[profile.components/settings-switch-item
{:label-kw :t/bootnodes-enabled
:value bootnodes-enabled
:action-fn #(re-frame/dispatch [:toggle-custom-bootnodes %])}]]
[react/view styles/wrapper
[list/flat-list {:data (vals bootnodes)
:default-separator? false
:key-fn :id
:render-fn render-row}]]]))

View File

@ -170,6 +170,7 @@
:networks/networks
:networks/manage
:mailservers/manage
:bootnodes/manage
:node/after-start
:node/after-stop
:inbox/wnodes

View File

@ -24,6 +24,7 @@
status-im.ui.screens.wallet.choose-recipient.events
status-im.ui.screens.browser.events
status-im.ui.screens.offline-messaging-settings.events
status-im.ui.screens.bootnodes-settings.events
status-im.ui.screens.currency-settings.events
status-im.ui.screens.usage-data.events
[re-frame.core :as re-frame]

View File

@ -2,7 +2,6 @@
(:require [re-frame.core :refer [dispatch dispatch-sync after] :as re-frame]
[status-im.utils.handlers :refer [register-handler] :as handlers]
status-im.ui.screens.network-settings.edit-network.events
status-im.ui.screens.offline-messaging-settings.edit-mailserver.events
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.ui.screens.accounts.utils :as accounts.utils]
[status-im.i18n :as i18n]

View File

@ -5,6 +5,7 @@
[status-im.ui.screens.accounts.events :as accounts-events]
[status-im.i18n :as i18n]
[status-im.transport.core :as transport]
status-im.ui.screens.offline-messaging-settings.edit-mailserver.events
[status-im.utils.ethereum.core :as ethereum]))
(handlers/register-handler-fx

View File

@ -152,6 +152,11 @@
{:label-kw :t/offline-messaging
:action-fn #(re-frame/dispatch [:navigate-to :offline-messaging-settings])
:accessibility-label :offline-messages-settings-button}])
(when config/bootnodes-settings-enabled?
[profile.components/settings-item
{:label-kw :t/bootnodes
:action-fn #(re-frame/dispatch [:navigate-to :bootnodes-settings])
:accessibility-label :bootnodes-settings-button}])
[profile.components/settings-item-separator]
[profile.components/settings-item
{:label-kw :t/help-improve?

View File

@ -13,6 +13,7 @@
status-im.ui.screens.wallet.transactions.subs
status-im.ui.screens.network-settings.subs
status-im.ui.screens.offline-messaging-settings.subs
status-im.ui.screens.bootnodes-settings.subs
status-im.ui.screens.currency-settings.subs
status-im.ui.screens.browser.subs
status-im.bots.subs

View File

@ -39,6 +39,8 @@
[status-im.ui.screens.network-settings.edit-network.views :refer [edit-network]]
[status-im.ui.screens.offline-messaging-settings.views :refer [offline-messaging-settings]]
[status-im.ui.screens.offline-messaging-settings.edit-mailserver.views :refer [edit-mailserver]]
[status-im.ui.screens.bootnodes-settings.views :refer [bootnodes-settings]]
[status-im.ui.screens.bootnodes-settings.edit-bootnode.views :refer [edit-bootnode]]
[status-im.ui.screens.currency-settings.views :refer [currency-settings]]
[status-im.ui.screens.browser.views :refer [browser]]
[status-im.ui.screens.add-new.open-dapp.views :refer [open-dapp dapp-description]]
@ -85,6 +87,8 @@
:edit-network edit-network
:offline-messaging-settings offline-messaging-settings
:edit-mailserver edit-mailserver
:bootnodes-settings bootnodes-settings
:edit-bootnode edit-bootnode
:currency-settings currency-settings
:recent-recipients recent-recipients
:recipient-qr-code recipient-qr-code

View File

@ -22,6 +22,7 @@
(def stub-status-go? (enabled? (get-config :STUB_STATUS_GO 0)))
(def mainnet-warning-enabled? (enabled? (get-config :MAINNET_WARNING_ENABLED 0)))
(def offline-inbox-enabled? (enabled? (get-config :OFFLINE_INBOX_ENABLED "1")))
(def bootnodes-settings-enabled? (enabled? (get-config :BOOTNODES_SETTINGS_ENABLED "1")))
(def log-level
(-> (get-config :LOG_LEVEL "error")
string/lower-case

View File

@ -35,7 +35,9 @@
[status-im.test.utils.datetime]
[status-im.test.utils.mixpanel]
[status-im.test.utils.prices]
[status-im.test.ui.screens.network-settings.edit-network.events]))
[status-im.test.ui.screens.network-settings.edit-network.events]
[status-im.test.ui.screens.bootnodes-settings.edit-bootnode.events]
[status-im.test.ui.screens.accounts.login.events]))
(enable-console-print!)
@ -81,4 +83,6 @@
'status-im.test.utils.datetime
'status-im.test.utils.mixpanel
'status-im.test.utils.prices
'status-im.test.ui.screens.network-settings.edit-network.events)
'status-im.test.ui.screens.network-settings.edit-network.events
'status-im.test.ui.screens.bootnodes-settings.edit-bootnode.events
'status-im.test.ui.screens.accounts.login.events)

View File

@ -0,0 +1,132 @@
(ns status-im.test.ui.screens.accounts.login.events
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.utils.config :as config]
[status-im.ui.screens.accounts.login.events :as events]))
(deftest login-account
(let [mainnet-account {:network "mainnet_rpc"
:networks {"mainnet_rpc" {:config {:NetworkId 1}}}}
testnet-account {:network "testnet_rpc"
:networks {"testnet_rpc" {:config {:NetworkId 3}}}}
accounts {"mainnet" mainnet-account
"testnet" testnet-account}
initial-db {:db {:network "mainnet_rpc"
:accounts/accounts accounts}}]
(testing "status-go has not started"
(let [actual (events/login-account initial-db [nil "testnet" "password"])]
(testing "it starts status-node if it has not started"
(is (= {:NetworkId 3}
(:initialize-geth-fx
actual))))
(testing "it logins the user after the node started"
(is (= [::events/login-account "testnet" "password"] (get-in actual [:db :node/after-start]))))))
(testing "status-go has started & the user is on mainnet"
(let [db (assoc-in initial-db [:db :status-node-started?] true)
actual (events/login-account
db
[nil "mainnet" "password"])]
(testing "it does not start status-node if it has already started"
(is (not (:initialize-geth-fx actual))))
(testing "it logs in the user"
(is (= ["mainnet" "password"] (::events/login actual))))))
(testing "the user has selected a different network"
(testing "status-go has started"
(let [db (assoc-in initial-db [:db :status-node-started?] true)
actual (events/login-account
db
[nil "testnet" "password"])]
(testing "it dispatches start-node"
(is (get-in actual [:db :node/after-stop] [::events/start-node "testnet" "password"])))
(testing "it stops status-node"
(is (contains? actual ::events/stop-node)))))
(testing "status-go has not started"
(let [actual (events/login-account
initial-db
[nil "testnet" "password"])]
(testing "it starts status-node"
(is (= {:NetworkId 3} (:initialize-geth-fx actual))))
(testing "it logins the user after the node started"
(is (= [::events/login-account "testnet" "password"] (get-in actual [:db :node/after-start])))))))
(testing "custom bootnodes"
(let [custom-bootnodes {"a" {:id "a"
:name "name-a"
:address "address-a"}
"b" {:id "b"
:name "name-b"
:address "address-b"}}
bootnodes-db (assoc-in
initial-db
[:db :accounts/accounts "mainnet" :bootnodes]
{"mainnet_rpc" custom-bootnodes})]
(testing "custom bootnodes enabled"
(let [bootnodes-enabled-db (assoc-in
bootnodes-db
[:db :accounts/accounts "mainnet" :settings]
{:bootnodes {"mainnet_rpc" true}})
actual (events/login-account
bootnodes-enabled-db
[nil "mainnet" "password"])]
(testing "status-node has started"
(let [db (assoc-in bootnodes-enabled-db [:db :status-node-started?] true)
actual (events/login-account
db
[nil "mainnet" "password"])]
(testing "it dispatches start-node"
(is (get-in actual [:db :node/after-stop] [::events/start-node "testnet" "password"])))
(testing "it stops status-node"
(is (contains? actual ::events/stop-node)))))
(testing "status-node has not started"
(let [actual (events/login-account
bootnodes-enabled-db
[nil "mainnet" "password"])]
(testing "it adds bootnodes to the config"
(is (= {:ClusterConfig {:Enabled true
:BootNodes ["address-a" "address-b"]}
:NetworkId 1} (:initialize-geth-fx actual))))
(testing "it logins the user after the node started"
(is (= [::events/login-account "mainnet" "password"] (get-in actual [:db :node/after-start]))))))))
(testing "custom bootnodes not enabled"
(testing "status-node has started"
(let [db (assoc-in bootnodes-db [:db :status-node-started?] true)
actual (events/login-account
db
[nil "mainnet" "password"])]
(testing "it does not start status-node if it has already started"
(is (not (:initialize-geth-fx actual))))
(testing "it logs in the user"
(is (= ["mainnet" "password"] (::events/login actual))))))
(testing "status-node has not started"
(let [actual (events/login-account
bootnodes-db
[nil "mainnet" "password"])]
(testing "it starts status-node without custom bootnodes"
(is (= {:NetworkId 1} (:initialize-geth-fx actual))))
(testing "it logins the user after the node started"
(is (= [::events/login-account "mainnet" "password"] (get-in actual [:db :node/after-start])))))))))))
(deftest restart-node?
(testing "custom bootnodes is toggled off"
(with-redefs [config/bootnodes-settings-enabled? false]
(testing "it returns true when the network is different"
(is (events/restart-node? "mainnet_rpc" "mainnet" true)))
(testing "it returns false when the network is the same"
(is (not (events/restart-node? "mainnet" "mainnet" true))))))
(testing "custom bootnodes is toggled on"
(with-redefs [config/bootnodes-settings-enabled? true]
(testing "the user is not using custom bootnodes"
(testing "it returns true when the network is different"
(is (events/restart-node? "mainnet_rpc" "mainnet" false)))
(testing "it returns false when the network is the same"
(is (not (events/restart-node? "mainnet" "mainnet" false)))))
(testing "the user is using custom bootnodes"
(testing "it returns true when the network is different"
(is (events/restart-node? "mainnet" "mainnet" true)))
(testing "it returns true when the network is the same"
(is (events/restart-node? "mainnet_rpc" "mainnet" true)))))))

View File

@ -0,0 +1,19 @@
(ns status-im.test.ui.screens.bootnodes-settings.edit-bootnode.events
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.ui.screens.bootnodes-settings.edit-bootnode.events :as events]))
(deftest add-new-bootnode
(testing "adding a bootnode"
(let [new-bootnode {:name {:value "name"}
:url {:value "url"}}
expected {"mainnet_rpc" {"someid" {:name "name"
:address "url"
:chain "mainnet_rpc"
:id "someid"}}}
actual (events/save-new-bootnode
{:random-id "some-id"
:db {:bootnodes/manage new-bootnode
:network "mainnet_rpc"
:account/account {}}}
nil)]
(is (= expected (get-in actual [:db :account/account :bootnodes]))))))