├── .gitignore ├── LICENSE ├── README.md ├── apidoc.json ├── build.gradle ├── docs ├── ARCHITECTURE.md ├── CLASSES.md ├── CONTRIBUTE.md ├── assets │ ├── bundle_structure.png │ ├── deployment.png │ ├── eee_diagram.png │ ├── gossipcycle.png │ ├── gpp_with_eee.png │ ├── network.png │ └── tx_structure.png └── gen.py ├── src ├── main │ ├── java │ │ └── org │ │ │ └── iota │ │ │ └── ict │ │ │ ├── Ict.java │ │ │ ├── IctInterface.java │ │ │ ├── Main.java │ │ │ ├── api │ │ │ ├── GithubGateway.java │ │ │ ├── HttpGateway.java │ │ │ ├── JsonIct.java │ │ │ ├── RestApi.java │ │ │ └── RouteImpl.java │ │ │ ├── eee │ │ │ ├── EffectListener.java │ │ │ ├── EffectListenerQueue.java │ │ │ ├── Environment.java │ │ │ ├── call │ │ │ │ ├── EEEFunction.java │ │ │ │ ├── EEEFunctionCaller.java │ │ │ │ ├── EEEFunctionCallerImplementation.java │ │ │ │ ├── FunctionEnvironment.java │ │ │ │ └── FunctionReturnEnvironment.java │ │ │ ├── chain │ │ │ │ ├── ChainIndexEnvironment.java │ │ │ │ ├── ChainedEffectListener.java │ │ │ │ ├── ChainedEffectListenerImplementation.java │ │ │ │ └── ChainedEnvironment.java │ │ │ └── dispatch │ │ │ │ ├── EffectDispatcher.java │ │ │ │ ├── SimpleEffectDispatcher.java │ │ │ │ ├── ThreadedEffectDispatcher.java │ │ │ │ └── ThreadedEffectDispatcherWithChainSupport.java │ │ │ ├── ixi │ │ │ ├── Ixi.java │ │ │ ├── IxiModule.java │ │ │ ├── IxiModuleHolder.java │ │ │ ├── IxiModuleInfo.java │ │ │ └── context │ │ │ │ ├── ConfigurableIxiContext.java │ │ │ │ ├── IxiContext.java │ │ │ │ └── SimpleIxiContext.java │ │ │ ├── model │ │ │ ├── bc │ │ │ │ ├── BalanceChange.java │ │ │ │ ├── BalanceChangeBuilder.java │ │ │ │ ├── BalanceChangeBuilderInterface.java │ │ │ │ ├── BalanceChangeCollector.java │ │ │ │ └── BalanceChangeInterface.java │ │ │ ├── bundle │ │ │ │ ├── Bundle.java │ │ │ │ └── BundleBuilder.java │ │ │ ├── tangle │ │ │ │ ├── RingTangle.java │ │ │ │ └── Tangle.java │ │ │ ├── transaction │ │ │ │ ├── Transaction.java │ │ │ │ └── TransactionBuilder.java │ │ │ └── transfer │ │ │ │ ├── InputBuilder.java │ │ │ │ ├── OutputBuilder.java │ │ │ │ ├── Transfer.java │ │ │ │ └── TransferBuilder.java │ │ │ ├── network │ │ │ ├── Neighbor.java │ │ │ ├── Node.java │ │ │ ├── Receiver.java │ │ │ ├── Sender.java │ │ │ ├── SenderInterface.java │ │ │ └── gossip │ │ │ │ ├── GossipEvent.java │ │ │ │ ├── GossipFilter.java │ │ │ │ ├── GossipListener.java │ │ │ │ ├── GossipListenerQueue.java │ │ │ │ └── GossipPreprocessor.java │ │ │ ├── std │ │ │ └── BundleCollector.java │ │ │ └── utils │ │ │ ├── Constants.java │ │ │ ├── IOHelper.java │ │ │ ├── IssueCollector.java │ │ │ ├── LogAppender.java │ │ │ ├── MultiHashMap.java │ │ │ ├── RestartableThread.java │ │ │ ├── Stats.java │ │ │ ├── Trytes.java │ │ │ ├── Updater.java │ │ │ ├── VersionComparator.java │ │ │ ├── crypto │ │ │ ├── AutoIndexedMerkleTree.java │ │ │ ├── MerkleTree.java │ │ │ ├── SignatureScheme.java │ │ │ └── SignatureSchemeImplementation.java │ │ │ ├── interfaces │ │ │ ├── Configurable.java │ │ │ ├── Installable.java │ │ │ └── Restartable.java │ │ │ └── properties │ │ │ ├── EditableProperties.java │ │ │ ├── FinalProperties.java │ │ │ ├── Properties.java │ │ │ └── PropertiesUser.java │ └── resources │ │ └── log4j2.xml └── test │ ├── java │ └── org │ │ └── iota │ │ └── ict │ │ ├── IctTest.java │ │ ├── IctTestTemplate.java │ │ ├── MainCmdlineTest.java │ │ ├── MainLog4jConfigTest.java │ │ ├── api │ │ ├── GithubGatewayTest.java │ │ └── JsonIctTest.java │ │ ├── eee │ │ └── call │ │ │ └── EEEFunctionCallerTest.java │ │ ├── ixi │ │ ├── IxiConfigurationTest.java │ │ ├── IxiGossipEventTest.java │ │ ├── VirtualIxiModule.java │ │ └── VirtualIxiModuleTest.java │ │ ├── model │ │ ├── bundle │ │ │ └── BundleTest.java │ │ ├── tangle │ │ │ ├── RingTangleTest.java │ │ │ └── TangleTest.java │ │ ├── transaction │ │ │ └── TransactionTest.java │ │ └── transfer │ │ │ └── TransferTest.java │ │ ├── network │ │ ├── GossipTest.java │ │ ├── HostResolveTest.java │ │ ├── LogRoundTest.java │ │ ├── RandomTopologyTest.java │ │ ├── ReferenceTest.java │ │ ├── SimpleTopologyTest.java │ │ ├── SpamProtectionTest.java │ │ ├── TimestampTest.java │ │ ├── TransactionRequestTest.java │ │ └── gossip │ │ │ ├── GossipEventDispatcherTest.java │ │ │ └── GossipPreprocessorTest.java │ │ ├── std │ │ └── BundleCollectorTest.java │ │ └── utils │ │ ├── RestartableThreadTest.java │ │ ├── TrytesTest.java │ │ ├── VersionComparatorTest.java │ │ ├── crypto │ │ ├── MerkleTreeTest.java │ │ └── SignatureSchemeImplementationTest.java │ │ └── properties │ │ └── PropertiesTest.java │ └── resources │ └── utils.VersionComparator_data.csv ├── version.updater.json └── web ├── .babelrc ├── .eslintrc ├── .gitignore ├── .prettierrc ├── package-lock.json ├── package.json └── src ├── assets ├── favicon.png ├── ict-logo-outline.svg └── ict-logo.svg ├── components ├── Card.jsx ├── Confirm.jsx ├── Icon.jsx ├── Popup.jsx └── Sidebar.jsx ├── index.html ├── index.jsx ├── lib ├── api.js ├── configLabels.json ├── helpers.js └── modules.json ├── pages ├── Configuration.jsx ├── Home.jsx ├── Log.jsx ├── Module.jsx ├── Modules.jsx └── Neighbors.jsx └── style ├── components ├── _button.scss ├── _card.scss ├── _highlight.scss ├── _neighbors.scss ├── _popup.scss ├── _section.scss └── _sidebar.scss ├── fonts ├── Inter-ExtraLight.woff ├── Inter-ExtraLight.woff2 ├── Inter-Regular.woff ├── Inter-Regular.woff2 ├── Inter-SemiBold.woff ├── Inter-SemiBold.woff2 ├── PT-Mono-ext.woff2 └── PT-Mono-latin.woff2 ├── global ├── _colors.scss ├── _layout.scss ├── _reset.scss └── _typography.scss ├── index.scss └── pages ├── _home.scss ├── _log.scss └── _modules.scss /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | ict.cfg 3 | *.jar 4 | modules/ 5 | build/ 6 | out/ 7 | web/node_modules 8 | ict.iml 9 | !.gitignore 10 | docs/gen_classes.py 11 | docs/*.pyc 12 | docs/api/ 13 | CodeStyleCheckTool.xml 14 | gradle/ 15 | settings.gradle 16 | gradlew.bat 17 | gradlew 18 | ict.ipr 19 | ict.iws 20 | logs 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Iota Controlled agenT (Ict) 2 | 3 | ## About 4 | 5 | The Iota Controlled agenT is a light-weight IOTA node for the Internet-of-Things relying on swarm logic. 6 | It provides a basic gossip protocol which can be extended with various functionality through the IOTA Extension Interface (IXI). 7 | This modular design enables the customization of the core node, allowing for all kinds of extensions to be plugged in. 8 | 9 | ## Getting Started 10 | 11 | ### Installation 12 | 13 | #### Method A: Download the Pre-Compiled .jar File 14 | 15 | This method is recommended for beginners. 16 | 17 | 1. Go to [releases](https://github.com/iotaledger/ict/releases). 18 | 2. Download **ict.jar** from the most recent release. 19 | 3. Move the .jar file to your favourite directory/folder. 20 | 21 | 22 | #### Method B: Build the .jar Yourself 23 | 24 | This method is recommended for advanced users. You have to install **Git**, **Gradle** and **NPM**. 25 | 26 | ```shell 27 | # 1) clone the source code (you will need Git) 28 | git clone https://github.com/iotaledger/ict 29 | 30 | # 2) move into the cloned repository 31 | cd ict 32 | 33 | # 3) build the runnable .jar file (you will need Gradle) 34 | gradle fatJar 35 | 36 | #4) install dependencies and build the web gui 37 | cd web 38 | npm install 39 | npm run build 40 | ``` 41 | 42 | ### Running the Client 43 | 44 | You will need the JRE (Java Runtime Environment) or JDK (Java Development Kit). 45 | 46 | ```shell 47 | # 1) move to whatever directory/folder where your .jar file is in 48 | cd Desktop/ict/ 49 | 50 | # 2) run the .jar file (example: java -jar ict-0.6.jar) 51 | java -jar ict-[VERSION].jar 52 | ``` 53 | 54 | Use `--config-create` option to create a configuration file (**ict.cfg**). Restart your Ict after modifying the configuration. 55 | 56 | ### Arguments 57 | 58 | You can pass the following arguments to the .jar file when running it. 59 | 60 | Argument|Alias|Example|Description 61 | ---|---|---|--- 62 | `--config`|`-c`|`--config ict.cfg`|Loads the Ict configuration from the specified file. 63 | `--config-print`| |`--config-print`|Print the Ict configuration to stdout and exit. 64 | `--config-create`| |`--config-create`|Write the Ict configuration './ict.cfg'. 65 | `--debug`|`--verbose`|`--debug`|Set root log level to 'debug' (default: INFO). 66 | `--trace`|`--verbose2`|`--trace`|Set root log level to 'trace' (default: INFO). 67 | `--logfile-enabled`| |`--logfile-enabled`|Enable logging to 'logs/ict.log'. 68 | `--log-dir DIR`| |`--log-dir /tmp/`|Write logs to existing 'DIR' (default: logs/). 69 | `--log-file FILE`| |`--log-file ict-log.txt`|Write logs to 'FILE' (default: ict.log). 70 | 71 | ### Usage 72 | 73 | ```bash 74 | # Usage: ict [OPTIONS] 75 | 76 | Start a 'ict' instance by config. 77 | 78 | # Options 79 | --help|-h Print this help and exit 80 | --config|-c FILE Use this config 'FILE' (default: ./ict.cfg;if-exist) 81 | - lookup first on environment for uppercase property keys 82 | - and as last in system properties 83 | --config-create Create or overwrite './ict.cfg' file with parsed config 84 | --config-print Print parsed config and exit 85 | - on verbose print cmdline- and log4j-config too 86 | 87 | --logfile-enabled Enable logging to 'logs/ict.log' 88 | --log-dir DIR Write logs to existing 'DIR' (default: logs/) 89 | --log-file NAME Write logs to 'FILE' (default: ict.log) 90 | -v|--verbose|--debug Set log.level=DEBUG (default:INFO) 91 | -vv|--verbose2|--trace Set log.level=TRACE (default:INFO) 92 | 93 | # Sample 94 | $ict --config-print # print out config 95 | $ict --config my-ict.cfg # use my config 96 | $ict --config my-ict.cfg -Dport=1234 # use my config but with port=1234 97 | $PORT=1234 && ict --config my-ict.cfg # use my config with port=1234 if not declared 98 | ``` 99 | 100 | ## IXI Modules 101 | 102 | An example/template for an IXI module can be found on [iotaledger/ixi](https://github.com/iotaledger/ixi). 103 | 104 | ## Development 105 | 106 | This project is still in early development. Community contributions are highly appreciated. Please read our [contribution 107 | sheet](/docs/CONTRIBUTE.md) first. 108 | -------------------------------------------------------------------------------- /apidoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ict REST API", 3 | "version": "0.4.0", 4 | "description": "This REST API connects the Ict with the Web GUI Control Panel to enable intuitive node maintenance.", 5 | "url" : "http://localhost:2187" 6 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'idea' // for 'gradle idea' to generate intellij configuration 4 | } 5 | 6 | group 'org.iota' 7 | version '0.6.1' 8 | 9 | sourceCompatibility = 1.7 10 | 11 | repositories { 12 | maven { url "https://jitpack.io" } 13 | mavenCentral() 14 | } 15 | 16 | dependencies { 17 | compile "com.sparkjava:spark-core:2.7.2" 18 | compile 'org.json:json:20171018' 19 | compile "org.apache.logging.log4j:log4j-slf4j-impl:2.11.1" 20 | compile "org.apache.logging.log4j:log4j-core:2.11.1" 21 | 22 | // used until pull request for iotaledger/iota.curl.java is merged 23 | compile 'com.github.mikrohash:iota.curl.java:master' 24 | 25 | testCompile group: 'junit', name: 'junit', version: '4.12' 26 | testCompile group: 'pl.pragmatists', name: 'JUnitParams', version: '1.1.1' 27 | testCompile "org.mockito:mockito-core:2.+" 28 | } 29 | 30 | task fatJar(type: Jar) { 31 | manifest { 32 | attributes 'Main-Class': 'org.iota.ict.Main' 33 | } 34 | baseName = 'ict' 35 | destinationDir = file("$rootDir/") 36 | from("./web/dist") { into 'web' } 37 | from { configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } } 38 | 39 | exclude 'META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat' 40 | 41 | with jar 42 | } 43 | 44 | task runDocGen(type:Exec) { 45 | // gen_classes.py cannot be included in this repository because of license issues. 46 | workingDir("$rootDir/docs") 47 | commandLine 'python', 'gen.py' 48 | } 49 | 50 | task validateRelease() { 51 | dependsOn(runDocGen) 52 | doFirst { 53 | println "=== RELEASING VERSION " + version + " ===" 54 | assert !version.contains("SNAPSHOT") 55 | assert 'git status --porcelain -uno'.execute().text.length() == 0 56 | } 57 | } 58 | 59 | task release { 60 | dependsOn(validateRelease) 61 | dependsOn(test) 62 | finalizedBy(fatJar) 63 | } -------------------------------------------------------------------------------- /docs/assets/bundle_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/ict/3bb009c3fd0088943ab9b810c69a76097f9fb6ae/docs/assets/bundle_structure.png -------------------------------------------------------------------------------- /docs/assets/deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/ict/3bb009c3fd0088943ab9b810c69a76097f9fb6ae/docs/assets/deployment.png -------------------------------------------------------------------------------- /docs/assets/eee_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/ict/3bb009c3fd0088943ab9b810c69a76097f9fb6ae/docs/assets/eee_diagram.png -------------------------------------------------------------------------------- /docs/assets/gossipcycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/ict/3bb009c3fd0088943ab9b810c69a76097f9fb6ae/docs/assets/gossipcycle.png -------------------------------------------------------------------------------- /docs/assets/gpp_with_eee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/ict/3bb009c3fd0088943ab9b810c69a76097f9fb6ae/docs/assets/gpp_with_eee.png -------------------------------------------------------------------------------- /docs/assets/network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/ict/3bb009c3fd0088943ab9b810c69a76097f9fb6ae/docs/assets/network.png -------------------------------------------------------------------------------- /docs/assets/tx_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/ict/3bb009c3fd0088943ab9b810c69a76097f9fb6ae/docs/assets/tx_structure.png -------------------------------------------------------------------------------- /docs/gen.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def warn(str): 4 | print('\033[91m[WARN] ' + str + '\033[0m') 5 | 6 | def success(str): 7 | print('\033[92m[OK] ' + str + '\033[0m') 8 | 9 | print('\n=== docs/gen.py: GENERATING DOCUMENTATION ===\n') 10 | 11 | if os.path.isfile('gen_classes.py'): 12 | import gen_classes 13 | success('generated docs/CLASSES.md') 14 | else: 15 | warn('could not generate CLASSES.md because gen_classes.py was not found (it is not included in the repository because of license issues)') 16 | 17 | os.system("apidoc src/main/java/org/iota/ict/api/RestApi.java -o docs/api/") 18 | success('generated docs/api/') 19 | 20 | print('\n=============================================\n') -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/IctInterface.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict; 2 | 3 | import org.iota.ict.ixi.Ixi; 4 | import org.iota.ict.ixi.IxiModuleHolder; 5 | import org.iota.ict.model.tangle.Tangle; 6 | import org.iota.ict.network.Neighbor; 7 | import org.iota.ict.utils.properties.FinalProperties; 8 | import org.iota.ict.utils.properties.PropertiesUser; 9 | import org.iota.ict.utils.interfaces.Restartable; 10 | 11 | import java.util.List; 12 | 13 | public interface IctInterface extends Ixi, PropertiesUser, Restartable { 14 | 15 | List getNeighbors(); 16 | 17 | IxiModuleHolder getModuleHolder(); 18 | 19 | Tangle getTangle(); 20 | 21 | FinalProperties getProperties(); 22 | 23 | 24 | 25 | void request(String transactionHash); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/api/GithubGateway.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.api; 2 | 3 | import org.json.JSONArray; 4 | import org.json.JSONObject; 5 | 6 | import java.net.MalformedURLException; 7 | import java.net.URL; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public final class GithubGateway { 12 | 13 | private final static Map githubRequestProperties; 14 | 15 | static { 16 | githubRequestProperties = new HashMap<>(); 17 | githubRequestProperties.put("Content-Type", "application/json"); 18 | githubRequestProperties.put("Accept", "application/vnd.github.v3+json"); 19 | } 20 | 21 | private GithubGateway() { 22 | } 23 | 24 | private static String BASE_URL = "https://api.github.com"; 25 | 26 | public static JSONObject getRepoInfo(String userSlashRepo) { 27 | String response = send_API_POST_Request("/repos/" + userSlashRepo); 28 | return new JSONObject(response); 29 | } 30 | 31 | public static String getLatestReleaseLabel(String userSlashRepo) { 32 | JSONObject latestRelease = getLatestRelease(userSlashRepo); 33 | return latestRelease.getString("tag_name"); 34 | } 35 | 36 | public static String getContents(String userSlashRepo, String branch, String path) { 37 | return HttpGateway.sendGetRequest("https://raw.githubusercontent.com/" + userSlashRepo + "/" + branch + "/" + path, new HashMap(), githubRequestProperties); 38 | } 39 | 40 | public static URL getAssetDownloadUrl(String userSlashRepo, String label) { 41 | JSONObject release = getRelease(userSlashRepo, label); 42 | return getAssetDownloadUrlOfRelease(release); 43 | } 44 | 45 | private static JSONObject getLatestRelease(String userSlashRepo) { 46 | JSONArray releases = getReleases(userSlashRepo); 47 | if (releases.length() == 0) 48 | throw new RuntimeException("No releases in repository " + userSlashRepo); 49 | return releases.getJSONObject(0); 50 | } 51 | 52 | private static URL getAssetDownloadUrlOfRelease(JSONObject release) { 53 | JSONArray assets = release.getJSONArray("assets"); 54 | if (assets.length() > 0) { 55 | JSONObject asset = assets.getJSONObject(0); 56 | return getDownloadURLOfAsset(asset); 57 | } 58 | throw new RuntimeException("No assets found in release '" + release.getString("tag_name") + "'"); 59 | } 60 | 61 | private static URL getDownloadURLOfAsset(JSONObject asset) { 62 | String downloadUrl = asset.getString("browser_download_url"); 63 | try { 64 | return new URL(downloadUrl); 65 | } catch (MalformedURLException e) { 66 | throw new RuntimeException("should never happen", e); 67 | } 68 | } 69 | 70 | private static JSONObject getRelease(String userSlashRepo, String label) { 71 | JSONArray releases = getReleases(userSlashRepo); 72 | for (int i = 0; i < releases.length(); i++) { 73 | JSONObject release = releases.getJSONObject(i); 74 | if (release.getString("tag_name").equals(label)) 75 | return release; 76 | } 77 | throw new RuntimeException("No release with label '" + label + "' found in '" + userSlashRepo + "'."); 78 | } 79 | 80 | public static JSONArray getReleases(String userSlashRepo) { 81 | String response = send_API_POST_Request("/repos/" + userSlashRepo + "/releases"); 82 | return new JSONArray(response); 83 | } 84 | 85 | private static String send_API_POST_Request(String path) { 86 | return HttpGateway.sendGetRequest(BASE_URL + path, new HashMap(), githubRequestProperties); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/api/HttpGateway.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.api; 2 | 3 | import org.iota.ict.utils.IOHelper; 4 | 5 | import java.io.*; 6 | import java.net.HttpURLConnection; 7 | import java.net.URL; 8 | import java.net.URLEncoder; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | public final class HttpGateway { 14 | 15 | private HttpGateway() { 16 | } 17 | 18 | public static String sendGetRequest(String urlString, Map params, Map requestProperties) { 19 | return sendRequest(urlString, RequestMethod.GET, params, requestProperties); 20 | } 21 | 22 | public static String sendPostRequest(String urlString, Map params, Map requestProperties) { 23 | return sendRequest(urlString, RequestMethod.POST, params, requestProperties); 24 | } 25 | 26 | public static String sendRequest(String urlString, RequestMethod method, Map params, Map requestProperties) { 27 | try { 28 | URL url = new URL(urlString); 29 | HttpURLConnection connection = createConnection(url, method); 30 | applyRequestProperties(connection, requestProperties); 31 | if(method == RequestMethod.POST) 32 | writeParams(connection, params); 33 | connection.connect(); 34 | 35 | if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) 36 | throw new RuntimeException("Failed to connect to " + url + ". Bad response code: " + connection.getResponseCode()); 37 | return IOHelper.readInputStream(connection.getInputStream()); 38 | } catch (IOException t) { 39 | throw new RuntimeException(t); 40 | } 41 | } 42 | 43 | private static void applyRequestProperties(HttpURLConnection connection, Map requestProperties) { 44 | for(Map.Entry requestProperty : requestProperties.entrySet()) 45 | connection.setRequestProperty(requestProperty.getKey(), requestProperty.getValue()); 46 | } 47 | 48 | private static void writeParams(HttpURLConnection connection, Map params) throws IOException { 49 | String queryString = generateQueryString(params); 50 | try(OutputStream os = connection.getOutputStream()) { 51 | try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8))) { 52 | writer.write(queryString); 53 | writer.flush(); 54 | } 55 | } 56 | } 57 | 58 | private static String generateQueryString(Map params) { 59 | StringBuilder queryString = new StringBuilder(); 60 | boolean first = true; 61 | for(Map.Entry entry : params.entrySet()) { 62 | if(!first) 63 | queryString.append("&"); 64 | queryString.append(URLEncoder.encode(entry.getKey())) 65 | .append("=") 66 | .append(URLEncoder.encode(entry.getValue())); 67 | first = false; 68 | } 69 | return queryString.toString(); 70 | } 71 | 72 | private static HttpURLConnection createConnection(URL url, RequestMethod method) throws IOException { 73 | HttpURLConnection connection = (HttpURLConnection) (url.openConnection()); 74 | connection.setRequestMethod(method.name()); 75 | connection.setReadTimeout(10000); 76 | connection.setConnectTimeout(10000); 77 | connection.setDoInput(true); 78 | connection.setDoOutput(true); 79 | return connection; 80 | } 81 | 82 | private enum RequestMethod { 83 | POST, GET 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/eee/EffectListener.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.eee; 2 | 3 | public interface EffectListener { 4 | 5 | void onReceive(T effect); 6 | 7 | Environment getEnvironment(); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/eee/EffectListenerQueue.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.eee; 2 | 3 | import java.util.concurrent.BlockingQueue; 4 | import java.util.concurrent.LinkedBlockingQueue; 5 | 6 | public class EffectListenerQueue implements EffectListener { 7 | 8 | private final Environment environment; 9 | private final BlockingQueue effectQueue = new LinkedBlockingQueue<>(); 10 | 11 | public EffectListenerQueue(Environment environment) { 12 | this.environment = environment; 13 | } 14 | 15 | public T pollEffect() { 16 | return effectQueue.poll(); 17 | } 18 | 19 | public T takeEffect() throws InterruptedException { 20 | return effectQueue.take(); 21 | } 22 | 23 | @Override 24 | public void onReceive(T effect) { 25 | effectQueue.add(effect); 26 | } 27 | 28 | @Override 29 | public Environment getEnvironment() { 30 | return environment; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/eee/Environment.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.eee; 2 | 3 | public class Environment { 4 | 5 | private final String id; 6 | 7 | public Environment(String id) { 8 | this.id = id; 9 | } 10 | 11 | @Override 12 | public String toString() { 13 | return id; 14 | } 15 | 16 | @Override 17 | public boolean equals(Object obj) { 18 | return obj instanceof Environment && id.equals(((Environment) obj).id); 19 | } 20 | 21 | @Override 22 | public int hashCode() { 23 | return id.hashCode(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/eee/call/EEEFunction.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.eee.call; 2 | 3 | import org.iota.ict.eee.EffectListener; 4 | import org.iota.ict.eee.dispatch.EffectDispatcher; 5 | 6 | import java.util.concurrent.BlockingQueue; 7 | import java.util.concurrent.LinkedBlockingQueue; 8 | 9 | public class EEEFunction implements EffectListener { 10 | 11 | private final FunctionEnvironment environment; 12 | private final FunctionReturnEnvironment returnEnvironment; 13 | public final BlockingQueue requestQueue = new LinkedBlockingQueue<>(); 14 | 15 | public EEEFunction(FunctionEnvironment environment) { 16 | this.environment = environment; 17 | this.returnEnvironment = new FunctionReturnEnvironment(environment); 18 | } 19 | 20 | @Override 21 | public FunctionEnvironment getEnvironment() { 22 | return environment; 23 | } 24 | 25 | @Override 26 | public void onReceive(String effect) { 27 | requestQueue.add(new Request(effect)); 28 | } 29 | 30 | public class Request { 31 | 32 | private final String requestID; 33 | public final String argument; 34 | 35 | public Request(String request) { 36 | this.requestID = request.split(";")[0]; 37 | this.argument = request.contains(";") ? request.substring(requestID.length() + 1) : null; 38 | } 39 | 40 | public void submitReturn(EffectDispatcher dispatcher, String returnString) { 41 | dispatcher.submitEffect(returnEnvironment, requestID+";"+returnString); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/eee/call/EEEFunctionCaller.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.eee.call; 2 | 3 | public interface EEEFunctionCaller { 4 | 5 | T call(FunctionEnvironment environment, T argument, long timeoutMS); 6 | } -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/eee/call/EEEFunctionCallerImplementation.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.eee.call; 2 | 3 | import org.iota.ict.eee.EffectListener; 4 | import org.iota.ict.eee.Environment; 5 | import org.iota.ict.eee.dispatch.EffectDispatcher; 6 | 7 | public class EEEFunctionCallerImplementation implements EEEFunctionCaller { 8 | 9 | private final EffectDispatcher dispatcher; 10 | 11 | public EEEFunctionCallerImplementation(EffectDispatcher dispatcher) { 12 | this.dispatcher = dispatcher; 13 | } 14 | 15 | @Override 16 | public String call(FunctionEnvironment environment, String argument, long timeoutMS) { 17 | 18 | String requestID = generateRequestID(); 19 | String request = requestID+";"+argument; 20 | FunctionReturnEnvironment returnEnvironment = new FunctionReturnEnvironment(environment); 21 | ReturnListener returnListener = new ReturnListener(returnEnvironment, requestID); 22 | 23 | dispatcher.addListener(returnListener); 24 | dispatcher.submitEffect(environment, request); 25 | String response = returnListener.waitForResponse(timeoutMS); 26 | dispatcher.removeListener(returnListener); 27 | return response; 28 | } 29 | 30 | private static String generateRequestID() { 31 | return "" + System.currentTimeMillis()+Math.random(); 32 | } 33 | 34 | private class ReturnListener implements EffectListener { 35 | 36 | private final FunctionReturnEnvironment environment; 37 | private final String requestID; 38 | private String response = null; 39 | 40 | private ReturnListener(FunctionReturnEnvironment environment, String requestID) { 41 | this.environment = environment; 42 | this.requestID = requestID; 43 | } 44 | 45 | @Override 46 | public void onReceive(String effect) { 47 | String responseID; 48 | if(effect.contains(";") && (responseID = effect.split(";")[0]).equals(requestID)) { 49 | this.response = effect.substring(responseID.length()+1); 50 | synchronized (this) { 51 | this.notify(); 52 | } 53 | } 54 | } 55 | 56 | private String waitForResponse(long timeoutMS) { 57 | if(response == null) 58 | try { 59 | synchronized (this) { 60 | this.wait(timeoutMS); 61 | } 62 | } catch (InterruptedException e) { } 63 | return response; 64 | } 65 | 66 | @Override 67 | public Environment getEnvironment() { 68 | return environment; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/eee/call/FunctionEnvironment.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.eee.call; 2 | 3 | import org.iota.ict.eee.Environment; 4 | 5 | public class FunctionEnvironment extends Environment { 6 | 7 | public FunctionEnvironment(String service, String function) { 8 | super("public/"+service+"/"+function); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/eee/call/FunctionReturnEnvironment.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.eee.call; 2 | 3 | import org.iota.ict.eee.Environment; 4 | 5 | public class FunctionReturnEnvironment extends Environment { 6 | 7 | public FunctionReturnEnvironment(FunctionEnvironment functionEnvironment) { 8 | super(functionEnvironment + "#return"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/eee/chain/ChainIndexEnvironment.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.eee.chain; 2 | 3 | import org.iota.ict.eee.Environment; 4 | 5 | public class ChainIndexEnvironment extends Environment { 6 | 7 | public ChainIndexEnvironment(ChainedEnvironment chainedEnvironment, long position) { 8 | super(chainedEnvironment + "#" + position); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/eee/chain/ChainedEffectListener.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.eee.chain; 2 | 3 | import org.iota.ict.eee.EffectListener; 4 | 5 | public interface ChainedEffectListener extends EffectListener { 6 | 7 | long getChainPosition(); 8 | 9 | void passOn(T effect); 10 | 11 | ChainedEnvironment getChainedEnvironment(); 12 | 13 | interface Output { 14 | T getEffect(); 15 | long getChainPosition(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/eee/chain/ChainedEffectListenerImplementation.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.eee.chain; 2 | 3 | import org.iota.ict.eee.EffectListenerQueue; 4 | import org.iota.ict.eee.dispatch.EffectDispatcher; 5 | 6 | public class ChainedEffectListenerImplementation extends EffectListenerQueue implements ChainedEffectListener { 7 | 8 | private final long chainPosition; 9 | private final ChainedEnvironment chainedEnvironment; 10 | private EffectDispatcher dispatcher; 11 | 12 | public ChainedEffectListenerImplementation(EffectDispatcher dispatcher, ChainedEnvironment chainedEnvironment, long chainPosition) { 13 | super( new ChainIndexEnvironment(chainedEnvironment, chainPosition)); 14 | this.chainedEnvironment = chainedEnvironment; 15 | this.dispatcher = dispatcher; 16 | this.chainPosition = chainPosition; 17 | } 18 | 19 | public long getChainPosition() { 20 | return chainPosition; 21 | } 22 | 23 | public void passOn(T effect) { 24 | dispatcher.submitEffect(chainedEnvironment, new Output<>(chainPosition, effect)); 25 | } 26 | 27 | @Override 28 | public ChainedEnvironment getChainedEnvironment() { 29 | return chainedEnvironment; 30 | } 31 | 32 | public static final class Output implements ChainedEffectListener.Output { 33 | 34 | private final T effect; 35 | private final long chainPosition; 36 | 37 | @Override 38 | public T getEffect() { 39 | return effect; 40 | } 41 | 42 | @Override 43 | public long getChainPosition() { 44 | return chainPosition; 45 | } 46 | 47 | public Output(long chainPosition, T effect) { 48 | this.effect = effect; 49 | this.chainPosition = chainPosition; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/eee/chain/ChainedEnvironment.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.eee.chain; 2 | 3 | import org.iota.ict.eee.Environment; 4 | 5 | public class ChainedEnvironment extends Environment { 6 | 7 | public ChainedEnvironment(String chainId) { 8 | super(chainId + "#chain"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/eee/dispatch/EffectDispatcher.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.eee.dispatch; 2 | 3 | import org.iota.ict.eee.EffectListener; 4 | import org.iota.ict.eee.Environment; 5 | 6 | public interface EffectDispatcher { 7 | 8 | /** 9 | * Registers a new EffectListener. 10 | * @param listener the EffectListener to register 11 | */ 12 | void addListener(EffectListener listener); 13 | 14 | /** 15 | * Unregisters a previously registered EffectListener. 16 | * @param listener the EffectListener to unregister 17 | */ 18 | void removeListener(EffectListener listener); 19 | 20 | /** 21 | * Adds an effect to a specific environment queue. 22 | * 23 | * @param environment the environment to which the effect should be sent 24 | * @param effect the effect 25 | */ 26 | void submitEffect(Environment environment, T effect); 27 | } -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/eee/dispatch/SimpleEffectDispatcher.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.eee.dispatch; 2 | 3 | import org.iota.ict.eee.EffectListener; 4 | import org.iota.ict.eee.Environment; 5 | 6 | import java.util.HashMap; 7 | import java.util.HashSet; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | public class SimpleEffectDispatcher implements EffectDispatcher { 12 | 13 | protected Map>> listenersByEnvironment = new HashMap<>(); 14 | 15 | public void addListener(EffectListener listener) { 16 | Set> effectListenersOfEnvironment = listenersByEnvironment.get(listener.getEnvironment()); 17 | if(effectListenersOfEnvironment == null) { 18 | effectListenersOfEnvironment = new HashSet<>(); 19 | listenersByEnvironment.put(listener.getEnvironment(), effectListenersOfEnvironment); 20 | } 21 | effectListenersOfEnvironment.add(listener); 22 | } 23 | 24 | @Override 25 | public void removeListener(EffectListener listener) { 26 | Set> listeners = listenersByEnvironment.get(listener.getEnvironment()); 27 | if(listeners != null) 28 | listeners.remove(listener); 29 | } 30 | 31 | public void submitEffect(Environment environment, T effect) { 32 | Set> listeners = listenersByEnvironment.get(environment); 33 | if (listeners != null) 34 | for (EffectListener listener : listeners) 35 | dispatch(listener, effect); 36 | } 37 | 38 | protected void dispatch(EffectListener listener, T effect) { 39 | try { 40 | listener.onReceive(effect); 41 | } catch (Throwable t) { 42 | handleThrowableFromListener(t, listener); 43 | } 44 | } 45 | 46 | protected void handleThrowableFromListener(Throwable throwable, EffectListener listener) { 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/eee/dispatch/ThreadedEffectDispatcher.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.eee.dispatch; 2 | 3 | import org.apache.logging.log4j.Logger; 4 | import org.iota.ict.eee.EffectListener; 5 | import org.iota.ict.eee.Environment; 6 | import org.iota.ict.utils.RestartableThread; 7 | 8 | import java.util.Map; 9 | import java.util.Set; 10 | import java.util.concurrent.BlockingQueue; 11 | import java.util.concurrent.LinkedBlockingQueue; 12 | 13 | public class ThreadedEffectDispatcher extends RestartableThread implements EffectDispatcher { 14 | 15 | protected final Logger logger; 16 | protected final EffectDispatcherImplementation implementation = new EffectDispatcherImplementation(); 17 | protected final BlockingQueue toDispatch = new LinkedBlockingQueue<>(); 18 | 19 | public ThreadedEffectDispatcher(Logger logger) { 20 | super(logger); 21 | this.logger = logger; 22 | } 23 | 24 | @Override 25 | public void run() { 26 | while (isRunning()) { 27 | try { 28 | DispatchItem dispatchItem = toDispatch.take(); 29 | implementation.submitEffect(dispatchItem.environment, dispatchItem.effect); 30 | } catch (InterruptedException e) { 31 | Thread.currentThread().interrupt(); 32 | } 33 | } 34 | } 35 | 36 | @Override 37 | public void onTerminate() { 38 | runningThread.interrupt(); 39 | } 40 | 41 | @Override 42 | public void addListener(EffectListener listener) { 43 | implementation.addListener(listener); 44 | } 45 | 46 | @Override 47 | public void removeListener(EffectListener listener) { 48 | implementation.removeListener(listener); 49 | } 50 | 51 | @Override 52 | public void submitEffect(Environment environment, T effect) { 53 | toDispatch.add(new DispatchItem(environment, effect)); 54 | } 55 | 56 | public void log() { 57 | int amountOfListeners = 0; 58 | for(Map.Entry>> entry : implementation.listenersByEnvironment.entrySet()) 59 | amountOfListeners += entry.getValue().size(); 60 | 61 | int undispatched = toDispatch.size(); 62 | logger.debug("gossip listeners: " + amountOfListeners + " / gossip queue size: " + undispatched); 63 | if (undispatched > 1000) 64 | logger.warn("There is a backlog of " + undispatched + " effects to be dispatched. This can cause memory and communication issues. Possible causes are (1) A listener is taking too long to process effects, (2) there are too many listeners (3) there are too many effects."); 65 | // TODO self-analyze cause 66 | } 67 | 68 | private class EffectDispatcherImplementation extends SimpleEffectDispatcher { 69 | 70 | @Override 71 | protected void dispatch(EffectListener listener, T effect) { 72 | if(isRunning()) 73 | super.dispatch(listener, effect); 74 | } 75 | } 76 | 77 | private class DispatchItem { 78 | private final Environment environment; 79 | private final T effect; 80 | 81 | DispatchItem(Environment environment, T effect) { 82 | this.environment = environment; 83 | this.effect = effect; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/eee/dispatch/ThreadedEffectDispatcherWithChainSupport.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.eee.dispatch; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import org.iota.ict.eee.*; 5 | import org.iota.ict.eee.chain.ChainedEffectListener; 6 | import org.iota.ict.eee.chain.ChainedEffectListenerImplementation; 7 | import org.iota.ict.eee.chain.ChainedEnvironment; 8 | import org.iota.ict.network.gossip.GossipPreprocessor; 9 | 10 | import java.util.*; 11 | 12 | public class ThreadedEffectDispatcherWithChainSupport extends ThreadedEffectDispatcher { 13 | 14 | private final Set chainedEnvironments = new HashSet<>(); 15 | private final List chainedEffectListenersOrderedByPosition = new LinkedList<>(); 16 | 17 | public ThreadedEffectDispatcherWithChainSupport() { 18 | super(LogManager.getLogger("TEDwCS")); 19 | } 20 | 21 | public void addChainedEnvironment(ChainedEnvironment chainedEnvironment, Environment finalEnvironment) { 22 | if(chainedEnvironments.contains(chainedEnvironment)) 23 | throw new IllegalArgumentException("Chained environment " + chainedEnvironment + " is already registered."); 24 | addListener(new ChainedEnvironmentHandler(chainedEnvironment, finalEnvironment)); 25 | chainedEnvironments.add(chainedEnvironment); 26 | } 27 | 28 | @Override 29 | public void addListener(EffectListener listener) { 30 | super.addListener(listener); 31 | if(listener instanceof ChainedEffectListener) { 32 | ChainedEffectListener chainedEffectListener = (ChainedEffectListener) listener; 33 | if(!chainedEnvironments.contains(chainedEffectListener.getChainedEnvironment())) 34 | throw new IllegalArgumentException("Chained environment " + chainedEffectListener.getChainedEnvironment() + " is not registered."); 35 | chainedEffectListenersOrderedByPosition.add((ChainedEffectListenerImplementation)listener); 36 | sortGossipPreprocessors(); 37 | } 38 | } 39 | 40 | private void sortGossipPreprocessors() { 41 | Collections.sort(chainedEffectListenersOrderedByPosition, new Comparator() { 42 | @Override 43 | public int compare(ChainedEffectListenerImplementation o1, ChainedEffectListenerImplementation o2) { 44 | return Long.compare(o1.getChainPosition(), o2.getChainPosition()); 45 | } 46 | }); 47 | } 48 | 49 | @Override 50 | public void removeListener(EffectListener listener) { 51 | if(listener instanceof GossipPreprocessor) { 52 | chainedEffectListenersOrderedByPosition.remove(listener); 53 | } 54 | super.removeListener(listener); 55 | } 56 | 57 | private ChainedEffectListener findChainSuccessor(Environment environment, long position) { 58 | for(ChainedEffectListener chainedEffectListener : chainedEffectListenersOrderedByPosition) 59 | if(chainedEffectListener.getChainPosition() > position && chainedEffectListener.getChainedEnvironment().equals(environment)) 60 | return chainedEffectListener; 61 | return null; 62 | } 63 | 64 | private class ChainedEnvironmentHandler implements EffectListener { 65 | 66 | private final ChainedEnvironment chainedEnvironment; 67 | private final Environment finalEnvironment; 68 | 69 | private ChainedEnvironmentHandler(ChainedEnvironment chainedEnvironment, Environment finalEnvironment) { 70 | this.chainedEnvironment = chainedEnvironment; 71 | this.finalEnvironment = finalEnvironment; 72 | } 73 | 74 | @Override 75 | public void onReceive(ChainedEffectListener.Output output) { 76 | ChainedEffectListener successor = findChainSuccessor(chainedEnvironment, output.getChainPosition()); 77 | if(successor != null) { 78 | submitEffect(successor.getEnvironment(), output.getEffect()); 79 | } else if(finalEnvironment != null) { 80 | submitEffect(finalEnvironment, output.getEffect()); 81 | } 82 | } 83 | 84 | @Override 85 | public Environment getEnvironment() { 86 | return chainedEnvironment; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/ixi/Ixi.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.ixi; 2 | 3 | import org.iota.ict.eee.dispatch.EffectDispatcher; 4 | import org.iota.ict.model.transaction.Transaction; 5 | 6 | import java.util.Set; 7 | 8 | public interface Ixi extends EffectDispatcher { 9 | 10 | /** 11 | * Searches the local tangle for transaction associated with a specific address. 12 | * @param address The 81-tryte address for which to find all transactions. 13 | * @return All transactions found in the local tangle with the specific address field. 14 | * */ 15 | Set findTransactionsByAddress(String address); 16 | 17 | 18 | /** 19 | * Searches the local tangle for transaction associated with a specific tag. 20 | * @param tag The 27-tryte tag for which to find all transactions. 21 | * @return All transactions found in the local tangle with the specific tag field. 22 | * */ 23 | Set findTransactionsByTag(String tag); 24 | 25 | /** 26 | * Searches the local tangle for a specific transaction 27 | * @param hash The 81-tryte hash of the transaction to find. 28 | * @return The transaction with the respective hash or null if no such transaction was found. 29 | * */ 30 | Transaction findTransactionByHash(String hash); 31 | 32 | /** 33 | * Adds a new transaction to the local tangle before broadcasting it to the network. 34 | * @param transaction The transaction to broadcast. 35 | * */ 36 | void submit(Transaction transaction); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/ixi/IxiModule.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.ixi; 2 | 3 | import org.iota.ict.ixi.context.IxiContext; 4 | import org.iota.ict.ixi.context.SimpleIxiContext; 5 | import org.iota.ict.utils.interfaces.Installable; 6 | import org.iota.ict.utils.RestartableThread; 7 | 8 | public abstract class IxiModule extends RestartableThread implements Runnable, Installable { 9 | 10 | protected Ixi ixi; 11 | 12 | public IxiModule(Ixi ixi) { 13 | super(null); 14 | this.ixi = ixi; 15 | } 16 | 17 | @Override 18 | public void install() { 19 | 20 | } 21 | 22 | @Override 23 | public void uninstall() { 24 | 25 | } 26 | 27 | /*** 28 | * Overwrite this method to set a custom context. 29 | * @see org.iota.ict.ixi.context.ConfigurableIxiContext if you want to make your IXI configurable. 30 | * */ 31 | public IxiContext getContext() { 32 | return SimpleIxiContext.INSTANCE; 33 | } 34 | } -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/ixi/IxiModuleInfo.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.ixi; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import org.apache.logging.log4j.Logger; 5 | import org.iota.ict.Ict; 6 | import org.iota.ict.api.GithubGateway; 7 | import org.iota.ict.utils.Constants; 8 | import org.iota.ict.utils.VersionComparator; 9 | import org.json.JSONArray; 10 | import org.json.JSONException; 11 | import org.json.JSONObject; 12 | 13 | public class IxiModuleInfo { 14 | 15 | public final String version; 16 | public final String repository; 17 | public final String name; 18 | public final String mainClass; 19 | public final String description; 20 | private String update = null; 21 | public int guiPort; 22 | public final String path; 23 | public final JSONArray supportedVersions; 24 | 25 | private static final Logger LOGGER = LogManager.getLogger("ModuleInfo"); 26 | 27 | public IxiModuleInfo(JSONObject json, String path) throws JSONException { 28 | version = json.getString("version"); 29 | supportedVersions = json.getJSONArray("supported_versions"); 30 | name = json.getString("name"); 31 | description = json.getString("description"); 32 | repository = json.getString("repository"); 33 | if (!repository.matches("^[a-zA-Z0-9\\-_.]+/[a-zA-Z0-9\\-_.]+$")) 34 | throw new RuntimeException("Illegal repository declared in module.json: '" + repository + "'. Invalid format or unexpected characters."); 35 | mainClass = json.getString("main_class"); 36 | guiPort = json.getInt("gui_port"); 37 | this.path = path; 38 | } 39 | 40 | public JSONObject toJSON() { 41 | JSONObject json = new JSONObject(); 42 | json.put("name", name); 43 | json.put("version", version); 44 | json.put("description", description); 45 | json.put("repository", repository); 46 | json.put("path", path); 47 | json.put("gui_port", guiPort); 48 | json.put("main_class", mainClass); 49 | json.put("supported_versions", supportedVersions); 50 | return json; 51 | } 52 | 53 | public String getUpdate() { 54 | return update; 55 | } 56 | 57 | public void checkForUpdate() { 58 | LOGGER.info("checking module '"+name+"' for updates ..."); 59 | try { 60 | String versionsString = GithubGateway.getContents(repository, "master", "versions.json"); 61 | JSONObject versions = new JSONObject(versionsString); 62 | String latestCompatibleVersion = versions.has(Constants.ICT_VERSION) ? versions.getString(Constants.ICT_VERSION) : "0"; 63 | update = VersionComparator.getInstance().compare(latestCompatibleVersion, version) > 0 ? latestCompatibleVersion : null; 64 | LOGGER.info((update == null ? "No update" : "Update to version "+update) + " available for module '" + name + "'."); 65 | } catch (Throwable t) { 66 | LOGGER.error("Could not check repository '"+repository+"' for updates.", t); 67 | } 68 | } 69 | 70 | boolean supportsCurrentVersion() { 71 | for (int i = 0; i < supportedVersions.length(); i++) 72 | if (supportedVersions.getString(i).equals(Constants.ICT_VERSION)) 73 | return true; 74 | return false; 75 | } 76 | } -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/ixi/context/ConfigurableIxiContext.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.ixi.context; 2 | 3 | import org.json.JSONObject; 4 | 5 | public abstract class ConfigurableIxiContext implements IxiContext { 6 | 7 | protected JSONObject configuration; 8 | private final JSONObject defaultConfiguration; 9 | 10 | @Override 11 | public String respondToRequest(String request) { 12 | return "This module has not implemented a response mechanism."; 13 | } 14 | 15 | protected ConfigurableIxiContext(JSONObject defaultConfiguration) { 16 | this.defaultConfiguration = defaultConfiguration; 17 | this.configuration = defaultConfiguration; 18 | } 19 | 20 | @Override 21 | public void tryToUpdateConfiguration(JSONObject newConfiguration) { 22 | validateConfiguration(newConfiguration); 23 | this.configuration = newConfiguration; 24 | applyConfiguration(); 25 | } 26 | 27 | protected abstract void validateConfiguration(JSONObject newConfiguration); 28 | 29 | protected abstract void applyConfiguration(); 30 | 31 | @Override 32 | public JSONObject getDefaultConfiguration() { 33 | return cloneJSONObject(defaultConfiguration); 34 | } 35 | 36 | @Override 37 | public JSONObject getConfiguration() { 38 | return cloneJSONObject(configuration); 39 | } 40 | 41 | private JSONObject cloneJSONObject(JSONObject jsonObject) { 42 | return new JSONObject(jsonObject, JSONObject.getNames(jsonObject)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/ixi/context/IxiContext.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.ixi.context; 2 | 3 | import org.iota.ict.utils.interfaces.Configurable; 4 | 5 | /** 6 | * This class expands the module-Ict interface with additional features which are not directly related to the Tangle 7 | * and therefore not part of the actual IXI. It's main purpose is streamlining the working mechanisms of IXI modules, 8 | * for example by giving them a standard interface to manage configuration. 9 | *

10 | * Implementations: 11 | * 12 | * @see ConfigurableIxiContext 13 | * @see SimpleIxiContext 14 | */ 15 | public interface IxiContext extends Configurable { 16 | 17 | String respondToRequest(String request); 18 | } -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/ixi/context/SimpleIxiContext.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.ixi.context; 2 | 3 | import org.json.JSONObject; 4 | 5 | public class SimpleIxiContext implements IxiContext { 6 | 7 | public static final SimpleIxiContext INSTANCE = new SimpleIxiContext(); 8 | 9 | @Override 10 | public String respondToRequest(String request) { 11 | return "This module has not implemented a response mechanism."; 12 | } 13 | 14 | @Override 15 | public void tryToUpdateConfiguration(JSONObject newConfiguration) { 16 | // do what you are good for, nothing 17 | } 18 | 19 | @Override 20 | public JSONObject getConfiguration() { 21 | return null; 22 | } 23 | 24 | @Override 25 | public JSONObject getDefaultConfiguration() { 26 | return null; 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/model/bc/BalanceChange.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.model.bc; 2 | 3 | import org.iota.ict.model.transfer.Transfer; 4 | import org.iota.ict.model.transaction.Transaction; 5 | 6 | import java.math.BigInteger; 7 | 8 | /** 9 | * A {@link BalanceChange} models a proposed change for the IOTA token balance of an IOTA address. Since tokens can neither be 10 | * burned nor created, no positive or negative {@link BalanceChange} cannot exist on its own but requires other balance 11 | * changes so the sum of their proposed changes is zero. They are grouped together in a {@link Transfer}. 12 | *

13 | * Depending on its proposed change, each instance is either an input or an output (see {@link #isInput()} and {@link #isOutput()}). 14 | * Inputs have a negative {@link #value} (they remove funds from an address so that another address can receive them). Outputs have a positive or a zero {@link #value}. 15 | * In inputs, the {@link #signatureOrMessage} is used as signature signing the proposed change with the addresses private key, 16 | * in outputs as optional message. 17 | *

18 | * Each {@link BalanceChange} is realized through at least one transactions. The required amount depends on the length 19 | * of {@link #signatureOrMessage}. 20 | * 21 | * @see Transfer as structure consisting of multiple {@link BalanceChange} instances. 22 | * @see BalanceChangeCollector as builder for this class. 23 | */ 24 | public class BalanceChange implements BalanceChangeInterface { 25 | 26 | private static final int SIGNATURE_FRAGMENTS_LENGTH = Transaction.Field.SIGNATURE_FRAGMENTS.tryteLength; 27 | 28 | public final String address; 29 | public final BigInteger value; 30 | public final String signatureOrMessage; 31 | 32 | public BalanceChange(String address, BigInteger value, String signatureOrMessage) { 33 | if (signatureOrMessage.length() % SIGNATURE_FRAGMENTS_LENGTH != 0) 34 | throw new IllegalArgumentException("length of signatureOrMessage must be multiple of " + SIGNATURE_FRAGMENTS_LENGTH); 35 | this.address = address; 36 | this.value = value; 37 | this.signatureOrMessage = signatureOrMessage; 38 | } 39 | 40 | @Override 41 | public BigInteger getValue() { 42 | return value; 43 | } 44 | 45 | @Override 46 | public String getAddress() { 47 | return address; 48 | } 49 | 50 | public int getAmountOfSignatureOrMessageFragments() { 51 | return signatureOrMessage.length() / SIGNATURE_FRAGMENTS_LENGTH; 52 | } 53 | 54 | public String getSignatureOrMessageFragment(int index) { 55 | assert index >= 0 && (index + 1) * SIGNATURE_FRAGMENTS_LENGTH <= signatureOrMessage.length(); 56 | return signatureOrMessage.substring(index * SIGNATURE_FRAGMENTS_LENGTH, (index + 1) * SIGNATURE_FRAGMENTS_LENGTH); 57 | } 58 | 59 | public String getSignatureOrMessage() { 60 | return signatureOrMessage; 61 | } 62 | 63 | public boolean isInput() { 64 | return value.compareTo(BigInteger.ZERO) < 0; 65 | } 66 | 67 | public boolean isOutput() { 68 | return !isInput(); 69 | } 70 | 71 | @Override 72 | public boolean equals(Object o) { 73 | if (o instanceof BalanceChange) { 74 | BalanceChange bc = (BalanceChange) o; 75 | return address.equals(bc.address) && value.compareTo(bc.value) == 0 && signatureOrMessage.equals(bc.signatureOrMessage); 76 | } 77 | return false; 78 | } 79 | 80 | @Override 81 | public int hashCode() { 82 | return (address + signatureOrMessage + value).hashCode(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/model/bc/BalanceChangeBuilder.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.model.bc; 2 | 3 | import org.iota.ict.model.transaction.TransactionBuilder; 4 | 5 | import java.math.BigInteger; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | public abstract class BalanceChangeBuilder implements BalanceChangeBuilderInterface { 10 | 11 | private final BigInteger value; 12 | private final String address; 13 | protected final TransactionBuilder[] buildersFromTailToHead; 14 | 15 | public BalanceChangeBuilder(String address, BigInteger value, int fragments) { 16 | if(address == null) 17 | throw new NullPointerException("address"); 18 | if(value == null) 19 | throw new NullPointerException("value"); 20 | if(fragments <= 0) 21 | throw new IllegalArgumentException("fragments must be positive"); 22 | this.value = value; 23 | this.address = address; 24 | this.buildersFromTailToHead = createTransactionBuildersFromTailToHead(fragments); 25 | } 26 | private TransactionBuilder[] createTransactionBuildersFromTailToHead(int fragments) { 27 | TransactionBuilder[] builders = new TransactionBuilder[fragments]; 28 | for(int i = 0; i < fragments; i++) { 29 | TransactionBuilder builder = new TransactionBuilder(); 30 | builder.address = address; 31 | builder.value = i == 0 ? value : BigInteger.ZERO; 32 | builders[i] = builder; 33 | } 34 | return builders; 35 | } 36 | 37 | public List getBuildersFromTailToHead() { 38 | return Arrays.asList(buildersFromTailToHead); 39 | } 40 | 41 | public BigInteger getValue() { 42 | return value; 43 | } 44 | 45 | @Override 46 | public String getAddress() { 47 | return address; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/model/bc/BalanceChangeBuilderInterface.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.model.bc; 2 | 3 | import org.iota.ict.model.transaction.TransactionBuilder; 4 | 5 | import java.util.List; 6 | 7 | public interface BalanceChangeBuilderInterface extends BalanceChangeInterface { 8 | 9 | String getEssence(); 10 | 11 | List getBuildersFromTailToHead(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/model/bc/BalanceChangeInterface.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.model.bc; 2 | 3 | import java.math.BigInteger; 4 | 5 | public interface BalanceChangeInterface { 6 | 7 | BigInteger getValue(); 8 | 9 | String getAddress(); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/model/bundle/BundleBuilder.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.model.bundle; 2 | 3 | import org.iota.ict.model.transaction.Transaction; 4 | import org.iota.ict.model.transaction.TransactionBuilder; 5 | 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | 9 | /** 10 | * Similar to {@link TransactionBuilder}, {@link BundleBuilder} makes it possible to create a {@link Bundle}. 11 | * Bundles are read from head to tail but created from tail to head. This is why it makes sense to have a dedicated class 12 | * for this purpose. 13 | */ 14 | public class BundleBuilder { 15 | 16 | private final LinkedList tailToHead = new LinkedList<>(); 17 | 18 | public void append(List unfinishedTransactionsFromTailToHead) { 19 | for (TransactionBuilder unfinishedTransaction : unfinishedTransactionsFromTailToHead) 20 | append(unfinishedTransaction); 21 | } 22 | 23 | public void append(TransactionBuilder unfinishedTransaction) { 24 | tailToHead.add(unfinishedTransaction); 25 | } 26 | 27 | public Bundle build() { 28 | if(tailToHead.size() == 0) 29 | throw new IllegalStateException("Cannot build: bundle is empty (0 transactions)."); 30 | setFlags(); 31 | Transaction head = buildTrunkLinkedChainAndReturnHead(); 32 | return tailToHead.size() > 0 ? new Bundle(head) : null; 33 | } 34 | 35 | private Transaction buildTrunkLinkedChainAndReturnHead() { 36 | 37 | Transaction lastTransaction = null; 38 | for (int i = 0; i < tailToHead.size(); i++) { 39 | boolean isFirst = i == 0; 40 | TransactionBuilder unfinished = tailToHead.get(i); 41 | if (!isFirst) 42 | unfinished.trunkHash = lastTransaction.hash; 43 | Transaction currentTransaction = unfinished.build(); 44 | currentTransaction.setTrunk(lastTransaction); 45 | lastTransaction = currentTransaction; 46 | } 47 | 48 | return lastTransaction; 49 | } 50 | 51 | private void setFlags() { 52 | for (TransactionBuilder unfinished : tailToHead) { 53 | unfinished.isBundleHead = false; 54 | unfinished.isBundleTail = false; 55 | } 56 | tailToHead.getFirst().isBundleTail = true; 57 | tailToHead.getLast().isBundleHead = true; 58 | } 59 | 60 | public List getTailToHead() { 61 | return new LinkedList<>(tailToHead); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/model/tangle/RingTangle.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.model.tangle; 2 | 3 | import org.iota.ict.IctInterface; 4 | import org.iota.ict.model.transaction.Transaction; 5 | import org.iota.ict.utils.properties.FinalProperties; 6 | import org.iota.ict.utils.properties.PropertiesUser; 7 | 8 | import java.util.*; 9 | import java.util.concurrent.PriorityBlockingQueue; 10 | 11 | /** 12 | * This Tangle prunes transactions after reaching a certain size. It works similar to a ring memory (hence the name). 13 | * The transactions are pruned in order of their timestamp, always keeping the N ({@link #capacity}) most recent ones. 14 | * As an exception, the NULL transaction will never be pruned away. 15 | */ 16 | public class RingTangle extends Tangle implements PropertiesUser { 17 | 18 | protected final PriorityBlockingQueue transactionsOrderedByTimestamp; 19 | protected long capacity; 20 | protected double maxHeapSize; 21 | 22 | protected double capacityFactor = 1.0; 23 | protected long lastCapacityFactorChangeTimestamp = 0; 24 | 25 | public RingTangle(IctInterface ict) { 26 | super(ict); 27 | capacity = ict.getProperties().tangleCapacity(); 28 | maxHeapSize = ict.getProperties().maxHeapSize(); 29 | transactionsOrderedByTimestamp = new PriorityBlockingQueue<>((int) Math.min(Integer.MAX_VALUE, capacity), TimestampComparator.INSTANCE); 30 | } 31 | 32 | @Override 33 | public void updateProperties(FinalProperties newProperties) { 34 | capacity = newProperties.tangleCapacity(); 35 | maxHeapSize = newProperties.maxHeapSize(); 36 | } 37 | 38 | @Override 39 | public TransactionLog createTransactionLogIfAbsent(Transaction transaction) { 40 | 41 | TransactionLog log = super.createTransactionLogIfAbsent(transaction); 42 | 43 | // do not add NULL transaction to transactionsOrderedByTimestamp to prevent it from being pruned 44 | 45 | if (transaction != Transaction.NULL_TRANSACTION && !transactionsOrderedByTimestamp.contains(log.transaction)) { 46 | // transactionsOrderedByTimestamp == null only when calling the super constructor and adding NULL transaction 47 | transactionsOrderedByTimestamp.put(transaction); 48 | while (transactionsOrderedByTimestamp.size() + 1 > capacity * capacityFactor) { // +1 fpr NULL transaction 49 | deleteTransaction(transactionsOrderedByTimestamp.poll()); 50 | } 51 | adjustTangleCapacityFactor(); 52 | } 53 | 54 | return log; 55 | } 56 | 57 | protected void adjustTangleCapacityFactor() { 58 | if (System.currentTimeMillis() - lastCapacityFactorChangeTimestamp > 60000) { 59 | double availableToUsedMemoryRatio = (Runtime.getRuntime().maxMemory() * maxHeapSize) / Runtime.getRuntime().totalMemory(); 60 | double changeFactor = Math.max(0.7, Math.min(1.4, availableToUsedMemoryRatio)); 61 | double capacityFactorBefore = capacityFactor; 62 | capacityFactor = Math.min(1, Math.max(0.01, capacityFactor * changeFactor)); 63 | lastCapacityFactorChangeTimestamp = System.currentTimeMillis(); 64 | 65 | boolean majorCapacityFactorChange = capacityFactorBefore < 0.8 * capacityFactor || capacityFactorBefore > 1.2 * capacityFactor; 66 | if (majorCapacityFactorChange) 67 | LOGGER.info("Adjusting effective tangle_capacity to ~" + (int) Math.round(capacity * capacityFactor) + " transactions based on max_heap_size."); 68 | 69 | if (capacityFactor != capacityFactorBefore) 70 | LOGGER.debug("Adjusting effective tangle_capacity to ~" + (int) Math.round(capacity * capacityFactor) + " transactions based on max_heap_size."); 71 | 72 | if (changeFactor < 0.9) 73 | System.gc(); 74 | } 75 | } 76 | 77 | private static class TimestampComparator implements Comparator { 78 | 79 | static final TimestampComparator INSTANCE = new TimestampComparator(); 80 | 81 | @Override 82 | public int compare(Transaction tl1, Transaction tl2) { 83 | int cmp = Long.compare(tl1.issuanceTimestamp, tl2.issuanceTimestamp); 84 | return cmp == 0 ? tl1.hash.compareTo(tl2.hash) : cmp; 85 | } 86 | 87 | @Override 88 | public boolean equals(Object o) { 89 | return super.equals(o); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/model/transaction/TransactionBuilder.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.model.transaction; 2 | 3 | import org.iota.ict.utils.Trytes; 4 | 5 | import java.math.BigInteger; 6 | 7 | /** 8 | * Since {@link Transaction} objects are final and their fields cannot be modified during runtime, the {@link TransactionBuilder} 9 | * can be used to define transaction fields before the transaction is created. This class is intended to create new transactions. 10 | */ 11 | public class TransactionBuilder { 12 | public String signatureFragments = generateNullTrytes(Transaction.Field.SIGNATURE_FRAGMENTS); 13 | public String extraDataDigest = generateNullTrytes(Transaction.Field.EXTRA_DATA_DIGEST); 14 | public String address = generateNullTrytes(Transaction.Field.ADDRESS); 15 | public BigInteger value = BigInteger.ZERO; 16 | public long issuanceTimestamp = System.currentTimeMillis(); 17 | public long timelockLowerBound = 0, timelockUpperBound = 0; 18 | public String bundleNonce = generateNullTrytes(Transaction.Field.BUNDLE_NONCE); 19 | public String trunkHash = generateNullTrytes(Transaction.Field.TRUNK_HASH); 20 | public String branchHash = generateNullTrytes(Transaction.Field.BRANCH_HASH); 21 | public String tag = generateNullTrytes(Transaction.Field.TAG); 22 | public long attachmentTimestamp = System.currentTimeMillis(), attachmentTimestampLowerBound = 0, attachmentTimestampUpperBound = 0; 23 | String nonce = Trytes.randomSequenceOfLength(Transaction.Field.NONCE.tryteLength); 24 | public boolean isBundleHead = true; 25 | public boolean isBundleTail = true; 26 | 27 | public void asciiMessage(String asciiMessage) { 28 | signatureFragments = Trytes.padRight(Trytes.fromAscii(asciiMessage), Transaction.Field.SIGNATURE_FRAGMENTS.tryteLength); 29 | } 30 | 31 | private static String generateNullTrytes(Transaction.Field field) { 32 | return Trytes.padRight("", field.tryteLength); 33 | } 34 | 35 | public Transaction buildWhileUpdatingTimestamp() { 36 | return doPow(true); 37 | } 38 | 39 | public Transaction build() { 40 | return doPow(false); 41 | } 42 | 43 | private Transaction doPow(boolean updateTimestamp) { 44 | // try different nonces to find transaction which satisfies required flagging by doing proof-of-work 45 | Transaction transaction = null; 46 | do { 47 | if (updateTimestamp) 48 | issuanceTimestamp = System.currentTimeMillis(); 49 | nonce = Trytes.randomSequenceOfLength(Transaction.Field.NONCE.tryteLength); 50 | try { 51 | transaction = new Transaction(this); 52 | } catch (Transaction.InvalidTransactionFlagException | Transaction.InvalidWeightException e) { 53 | // illegal flags, try next nonce 54 | } 55 | } 56 | while (transaction == null || transaction.isBundleTail != isBundleTail || transaction.isBundleHead != isBundleHead); 57 | return transaction; 58 | } 59 | 60 | public String getEssence() { 61 | String essence = extraDataDigest; 62 | essence += address; 63 | essence += Trytes.fromNumber(value, Transaction.Field.VALUE.tryteLength); 64 | essence += Trytes.fromNumber(BigInteger.valueOf(issuanceTimestamp), Transaction.Field.ISSUANCE_TIMESTAMP.tryteLength); 65 | essence += Trytes.fromNumber(BigInteger.valueOf(timelockLowerBound), Transaction.Field.TIMELOCK_LOWER_BOUND.tryteLength); 66 | essence += Trytes.fromNumber(BigInteger.valueOf(timelockUpperBound), Transaction.Field.TIMELOCK_UPPER_BOUND.tryteLength); 67 | essence += bundleNonce; 68 | assert essence.length() == Transaction.Field.ESSENCE.tryteLength; 69 | return essence; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/model/transfer/InputBuilder.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.model.transfer; 2 | 3 | import org.iota.ict.model.bc.BalanceChange; 4 | import org.iota.ict.model.bc.BalanceChangeBuilder; 5 | import org.iota.ict.model.transaction.Transaction; 6 | import org.iota.ict.model.transaction.TransactionBuilder; 7 | import org.iota.ict.utils.crypto.SignatureSchemeImplementation; 8 | 9 | import java.math.BigInteger; 10 | 11 | public class InputBuilder extends BalanceChangeBuilder { 12 | 13 | private final SignatureSchemeImplementation.PrivateKey privateKey; 14 | 15 | public InputBuilder(SignatureSchemeImplementation.PrivateKey privateKey, BigInteger value) { 16 | super(privateKey.deriveAddress(), value, privateKey.length() / Transaction.Field.SIGNATURE_FRAGMENTS.tryteLength); 17 | this.privateKey = privateKey; 18 | if(value.compareTo(BigInteger.ZERO) >= 0) 19 | throw new IllegalArgumentException("Value must be negative in input."); 20 | } 21 | 22 | public BalanceChange build(String bundleHash) { 23 | SignatureSchemeImplementation.Signature signature = privateKey.sign(bundleHash); 24 | if(signature.fragments() != buildersFromTailToHead.length) 25 | throw new IllegalStateException("BalanceChange has reserved " + buildersFromTailToHead.length + " transactions but signature length is " + signature.length() + " trytes."); 26 | for(int i = 0; i < buildersFromTailToHead.length; i++) { 27 | SignatureSchemeImplementation.Signature signatureFragment = signature.getFragment(i); 28 | buildersFromTailToHead[i].signatureFragments = signatureFragment.toString(); 29 | } 30 | return new BalanceChange(getAddress(), getValue(), signature.toString()); 31 | } 32 | 33 | @Override 34 | public String getEssence() { 35 | StringBuilder essence = new StringBuilder(); 36 | for (TransactionBuilder builder : buildersFromTailToHead) { 37 | essence.insert(0, builder.getEssence()); 38 | } 39 | return essence.toString(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/model/transfer/OutputBuilder.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.model.transfer; 2 | 3 | import com.iota.curl.IotaCurlHash; 4 | import org.iota.ict.model.bc.BalanceChangeBuilder; 5 | import org.iota.ict.model.transaction.Transaction; 6 | import org.iota.ict.model.transaction.TransactionBuilder; 7 | import org.iota.ict.utils.Constants; 8 | import org.iota.ict.utils.Trytes; 9 | 10 | import java.math.BigInteger; 11 | 12 | public class OutputBuilder extends BalanceChangeBuilder { 13 | 14 | public OutputBuilder(String address, BigInteger value, String message) { 15 | super(address, value, (int)Math.max(1, Math.ceil(message.length() * 1.0 / Transaction.Field.SIGNATURE_FRAGMENTS.tryteLength))); 16 | message = Trytes.padRight(message, buildersFromTailToHead.length * Transaction.Field.SIGNATURE_FRAGMENTS.tryteLength); 17 | if(value.compareTo(BigInteger.ZERO) < 0) 18 | throw new IllegalArgumentException("Value must be positive or zero in output."); 19 | for(int fragmentIndex = 0; (fragmentIndex+1) * Transaction.Field.SIGNATURE_FRAGMENTS.tryteLength <= message.length(); fragmentIndex++) { 20 | String fragment = message.substring(fragmentIndex * Transaction.Field.SIGNATURE_FRAGMENTS.tryteLength, (fragmentIndex+1)*Transaction.Field.SIGNATURE_FRAGMENTS.tryteLength); 21 | buildersFromTailToHead[fragmentIndex].signatureFragments = fragment; 22 | } 23 | } 24 | 25 | @Override 26 | public String getEssence() { 27 | StringBuilder essence = new StringBuilder(); 28 | for (TransactionBuilder builder : buildersFromTailToHead) { 29 | essence.insert(0, builder.getEssence()).insert(0, IotaCurlHash.iotaCurlHash(builder.signatureFragments, builder.signatureFragments.length(), Constants.CURL_ROUNDS_BUNDLE_HASH)); 30 | } 31 | return essence.toString(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/model/transfer/TransferBuilder.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.model.transfer; 2 | 3 | import com.iota.curl.IotaCurlHash; 4 | import org.iota.ict.model.bundle.BundleBuilder; 5 | import org.iota.ict.model.bc.BalanceChangeBuilderInterface; 6 | import org.iota.ict.utils.Constants; 7 | 8 | import java.math.BigInteger; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.Set; 12 | 13 | /** 14 | * {@link TransferBuilder} is a tool to create a new {@link Transfer} via {@link #build()}. 15 | */ 16 | public final class TransferBuilder { 17 | 18 | private final Set inputBuilders; 19 | private final Set outputs; 20 | private final int securityLevel; 21 | 22 | public TransferBuilder(Set inputBuilders, Set outputs, int securityLevel) { 23 | if(inputBuilders.isEmpty() && outputs.isEmpty()) 24 | throw new IllegalArgumentException("At least one input or output required."); 25 | 26 | this.inputBuilders = inputBuilders; 27 | this.outputs = outputs; 28 | 29 | ensureSumIsZero(inputBuilders, outputs); 30 | this.securityLevel = securityLevel; 31 | } 32 | 33 | private static void ensureSumIsZero(Iterable inputBuilders, Iterable outputs) { 34 | BigInteger sum = BigInteger.ZERO; 35 | for (InputBuilder signer : inputBuilders) { 36 | sum = sum.add(signer.getValue()); 37 | } 38 | for (OutputBuilder output : outputs) { 39 | sum = sum.add(output.getValue()); 40 | } 41 | if (sum.compareTo(BigInteger.ZERO) != 0) 42 | throw new IllegalArgumentException("Total sum of changes must be 0 but is '" + sum.toString() + "'."); 43 | } 44 | 45 | public BundleBuilder build() { 46 | BundleBuilder bundleBuilder = new BundleBuilder(); 47 | 48 | List orderedChanges = new LinkedList<>(); 49 | orderedChanges.addAll(inputBuilders); 50 | orderedChanges.addAll(outputs); 51 | String determinedBundleHash = determineBundleHash(orderedChanges); 52 | 53 | for (InputBuilder inputBuilder : inputBuilders) { 54 | inputBuilder.build(determinedBundleHash.substring(0, 27 * securityLevel)); 55 | } 56 | 57 | assert orderedChanges.size() > 0; 58 | 59 | for (BalanceChangeBuilderInterface changeBuilder : orderedChanges) 60 | bundleBuilder.append(changeBuilder.getBuildersFromTailToHead()); 61 | 62 | return bundleBuilder; 63 | } 64 | 65 | private static String determineBundleHash(List orderedChanges) { 66 | StringBuilder concat = new StringBuilder(); 67 | for (BalanceChangeBuilderInterface change : orderedChanges) 68 | concat.insert(0, change.getEssence()); 69 | return IotaCurlHash.iotaCurlHash(concat.toString(), concat.length(), Constants.CURL_ROUNDS_BUNDLE_HASH); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/network/Neighbor.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.network; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import org.apache.logging.log4j.Logger; 5 | import java.net.*; 6 | import org.iota.ict.utils.Constants; 7 | import org.iota.ict.utils.Stats; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * This class defines a neighbored Ict node. Neighbor nodes usually run remotely on a different device and connection 14 | * is established via the Internet. Besides the address, this class collect stats about the transaction flow from the 15 | * neighbor. 16 | */ 17 | public class Neighbor { 18 | 19 | public static final Logger logger = LogManager.getLogger("Neighbor"); 20 | private String address; 21 | private InetSocketAddress socketAddress; 22 | private List statsHistory = new ArrayList<>(); 23 | private double maxAllowedTransactionsForRound; 24 | 25 | public Neighbor(String address, long maxTransactionsAbsolute) { 26 | this.address = address; 27 | String host = address.split(":")[0].replaceAll("^.*/$", ""); 28 | int port = Integer.parseInt(address.split(":")[1]); 29 | this.socketAddress = new InetSocketAddress(host, port); 30 | this.maxAllowedTransactionsForRound = maxTransactionsAbsolute; 31 | statsHistory.add(new Stats(this)); 32 | } 33 | 34 | public void resolveHost() { 35 | try { 36 | if (!socketAddress.getAddress().equals(InetAddress.getByName(socketAddress.getHostName()))) 37 | socketAddress = new InetSocketAddress(socketAddress.getHostName(), socketAddress.getPort()); 38 | } catch (UnknownHostException e) { 39 | logger.warn(("Unknown Host for: " + socketAddress.getHostString()) + " (" + e.getMessage() + ")"); 40 | } 41 | } 42 | 43 | public boolean sentPacket(DatagramPacket packet) { 44 | //if (address.equals(packet.getSocketAddress())) 45 | // return true; 46 | boolean sameIP = sentPacketFromSameIP(packet); 47 | boolean samePort = socketAddress.getPort() == packet.getPort(); 48 | return sameIP && samePort; 49 | } 50 | 51 | public boolean sentPacketFromSameIP(DatagramPacket packet) { 52 | try { 53 | return socketAddress.getAddress().getHostAddress().equals(packet.getAddress().getHostAddress()); 54 | } catch (NullPointerException e) { 55 | // cannot resolve ip 56 | return false; 57 | } 58 | } 59 | 60 | public void newRound(long maxAllowedTransactionsForRound, boolean log) { 61 | this.maxAllowedTransactionsForRound = maxAllowedTransactionsForRound; 62 | if(log) reportStatsOfRound(); 63 | statsHistory.add(new Stats(this)); 64 | while (statsHistory.size() > Constants.MAX_AMOUNT_OF_ROUNDS_STORED) 65 | statsHistory.remove(0); 66 | } 67 | 68 | private void reportStatsOfRound() { 69 | Stats stats = getStats(); 70 | StringBuilder report = new StringBuilder(); 71 | report.append(pad(stats.receivedAll)).append('|'); 72 | report.append(pad(stats.receivedNew)).append('|'); 73 | report.append(pad(stats.requested)).append('|'); 74 | report.append(pad(stats.invalid)).append('|'); 75 | report.append(pad(stats.ignored)); 76 | report.append(" ").append(socketAddress); 77 | logger.info(report); 78 | } 79 | 80 | public Stats getStats() { 81 | return statsHistory.get(statsHistory.size()-1); 82 | } 83 | 84 | public List getStatsHistory() { 85 | return new ArrayList<>(statsHistory); 86 | } 87 | 88 | public boolean reachedLimitOfAllowedTransactions() { 89 | return getStats().receivedAll >= maxAllowedTransactionsForRound; 90 | } 91 | 92 | private static String pad(long value) { 93 | return pad(value + ""); 94 | } 95 | 96 | private static String pad(String str) { 97 | return String.format("%1$-5s", str); 98 | } 99 | 100 | public InetSocketAddress getSocketAddress() { 101 | return socketAddress; 102 | } 103 | 104 | public String getAddress() { 105 | return address; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/network/SenderInterface.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.network; 2 | 3 | import org.iota.ict.model.transaction.Transaction; 4 | import org.iota.ict.network.gossip.GossipListener; 5 | import org.iota.ict.utils.properties.PropertiesUser; 6 | import org.iota.ict.utils.interfaces.Restartable; 7 | 8 | public interface SenderInterface extends Restartable, PropertiesUser, GossipListener { 9 | 10 | void request(String transactionHash); 11 | 12 | void queue(Transaction transaction); 13 | 14 | int queueSize(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/network/gossip/GossipEvent.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.network.gossip; 2 | 3 | import org.iota.ict.model.transaction.Transaction; 4 | 5 | public class GossipEvent { 6 | 7 | private final Transaction transaction; 8 | private final boolean isOwnTransaction; 9 | 10 | public GossipEvent(Transaction transaction, boolean isOwnTransaction) { 11 | this.transaction = transaction; 12 | this.isOwnTransaction = isOwnTransaction; 13 | } 14 | 15 | public Transaction getTransaction() { 16 | return transaction; 17 | } 18 | 19 | public boolean isOwnTransaction() { 20 | return isOwnTransaction; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/network/gossip/GossipFilter.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.network.gossip; 2 | 3 | import org.iota.ict.model.transaction.Transaction; 4 | 5 | import java.io.Serializable; 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | 9 | public class GossipFilter implements Serializable { 10 | 11 | private boolean watchingAll; 12 | private final Set watchedAddresses = new HashSet<>(); 13 | private final Set watchedTags = new HashSet<>(); 14 | 15 | public GossipFilter watchAddress(String address) { 16 | assert address.length() == Transaction.Field.ADDRESS.tryteLength; 17 | watchedAddresses.add(address); 18 | return this; 19 | } 20 | 21 | public GossipFilter unwatchAddress(String address) { 22 | watchedAddresses.remove(address); 23 | return this; 24 | } 25 | 26 | public GossipFilter watchTag(String tag) { 27 | assert tag.length() == Transaction.Field.TAG.tryteLength; 28 | watchedTags.add(tag); 29 | return this; 30 | } 31 | 32 | public GossipFilter unwatchTag(String tag) { 33 | watchedTags.remove(tag); 34 | return this; 35 | } 36 | 37 | public Set getWatchedTags() { 38 | return new HashSet<>(watchedTags); 39 | } 40 | 41 | public Set getWatchedAddresses() { 42 | return new HashSet<>(watchedAddresses); 43 | } 44 | 45 | public GossipFilter setWatchingAll(boolean watchingAll) { 46 | this.watchingAll = watchingAll; 47 | return this; 48 | } 49 | 50 | public boolean isWatchingAll() { 51 | return watchingAll; 52 | } 53 | 54 | public boolean passes(Transaction transaction) { 55 | return watchingAll || watchedAddresses.contains(transaction.address()) || watchedTags.contains(transaction.tag()); 56 | } 57 | } -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/network/gossip/GossipListener.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.network.gossip; 2 | 3 | import org.iota.ict.eee.EffectListener; 4 | import org.iota.ict.eee.Environment; 5 | import org.iota.ict.utils.Constants; 6 | 7 | public interface GossipListener extends EffectListener { 8 | 9 | abstract class Implementation implements GossipListener { 10 | @Override 11 | public Environment getEnvironment() { 12 | return Constants.Environments.GOSSIP; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/network/gossip/GossipListenerQueue.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.network.gossip; 2 | 3 | import org.iota.ict.eee.EffectListenerQueue; 4 | import org.iota.ict.utils.Constants; 5 | 6 | public class GossipListenerQueue extends EffectListenerQueue implements GossipListener { 7 | 8 | GossipListenerQueue() { 9 | super(Constants.Environments.GOSSIP); 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/network/gossip/GossipPreprocessor.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.network.gossip; 2 | 3 | import org.iota.ict.eee.chain.ChainedEffectListenerImplementation; 4 | import org.iota.ict.eee.dispatch.EffectDispatcher; 5 | import org.iota.ict.utils.Constants; 6 | 7 | public class GossipPreprocessor extends ChainedEffectListenerImplementation implements GossipListener { 8 | 9 | public GossipPreprocessor(EffectDispatcher dispatcher, int chainPosition) { 10 | super(dispatcher, Constants.Environments.GOSSIP_PREPROCESSOR_CHAIN, chainPosition); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/std/BundleCollector.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.std; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import org.apache.logging.log4j.Logger; 5 | import org.iota.ict.Ict; 6 | import org.iota.ict.eee.EffectListener; 7 | import org.iota.ict.eee.Environment; 8 | import org.iota.ict.model.transaction.Transaction; 9 | import org.iota.ict.network.gossip.GossipEvent; 10 | import org.iota.ict.network.gossip.GossipPreprocessor; 11 | import org.iota.ict.utils.RestartableThread; 12 | 13 | import java.util.*; 14 | 15 | /** 16 | * This class collects bundles before they are further processed by other components. It ensures that no incomplete bundles 17 | * enter the internal state of the Ict node. 18 | * */ 19 | public class BundleCollector extends RestartableThread { 20 | 21 | private final Ict ict; 22 | private static final Logger LOGGER = LogManager.getLogger("BndlColl"); 23 | 24 | private GossipPreprocessor gossipPreprocessor; 25 | private Map existingTransactionsByHash = new HashMap<>(); 26 | private Map existingTransactionsByTrunk = new HashMap<>(); 27 | 28 | public BundleCollector(Ict ict) { 29 | super(LOGGER); 30 | this.gossipPreprocessor = new GossipPreprocessor(ict, -1000); 31 | this.ict = ict; 32 | } 33 | 34 | @Override 35 | public void run() { 36 | while (isRunning()) { 37 | try { 38 | GossipEvent gossipEvent = gossipPreprocessor.takeEffect(); 39 | addIncompleteBundleTransaction(gossipEvent); 40 | passOnBundleIfComplete(gossipEvent); 41 | } catch (InterruptedException e) { 42 | if(isRunning()) 43 | throw new RuntimeException(e); 44 | } 45 | } 46 | ict.removeListener(gossipPreprocessor); 47 | } 48 | 49 | private void addIncompleteBundleTransaction(GossipEvent event) { 50 | Transaction transaction = event.getTransaction(); 51 | if(!transaction.isBundleHead) 52 | existingTransactionsByHash.put(transaction.hash, event); 53 | if(!transaction.isBundleTail) 54 | existingTransactionsByTrunk.put(transaction.trunkHash(), event); 55 | } 56 | 57 | private void passOnBundleIfComplete(GossipEvent last) { 58 | 59 | GossipEvent head = findHead(last); 60 | List headToTail = fetchToTail(head); 61 | 62 | if(headToTail == null) 63 | // bundle not yet complete 64 | return; 65 | 66 | for(int i = headToTail.size()-1; i >= 0; i--) 67 | gossipPreprocessor.passOn(headToTail.get(i)); 68 | 69 | for(GossipEvent event : headToTail) 70 | existingTransactionsByTrunk.remove(event.getTransaction().hash); 71 | } 72 | 73 | private GossipEvent findHead(GossipEvent last) { 74 | while (last != null && !last.getTransaction().isBundleHead) { 75 | last = existingTransactionsByTrunk.get(last.getTransaction().hash); 76 | } 77 | return last; 78 | } 79 | 80 | private List fetchToTail(GossipEvent last) { 81 | List toTail = new LinkedList<>(); 82 | toTail.add(last); 83 | while (last != null && !last.getTransaction().isBundleTail) { 84 | last = existingTransactionsByHash.get(last.getTransaction().trunkHash()); 85 | toTail.add(last); 86 | } 87 | return last == null ? null : toTail; 88 | } 89 | 90 | public void log() { 91 | logger.debug("incomplete bundle parts: " + existingTransactionsByHash.size()+"+"+existingTransactionsByTrunk.size()); 92 | } 93 | 94 | @Override 95 | public void onStart() { 96 | ict.addListener(gossipPreprocessor); 97 | } 98 | 99 | @Override 100 | public void onTerminate() { 101 | runningThread.interrupt(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/utils/Constants.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils; 2 | 3 | import org.iota.ict.eee.chain.ChainedEnvironment; 4 | import org.iota.ict.eee.Environment; 5 | import org.iota.ict.model.transaction.Transaction; 6 | 7 | import java.io.File; 8 | import java.nio.file.Path; 9 | 10 | /** 11 | * Important constants which are not changed during runtime but might be changed during development or are used by 12 | * multiple classes are kept together here to make them easier to find and adjust. 13 | */ 14 | public class Constants { 15 | 16 | public static final Path WORKING_DIRECTORY = (new File("./")).toPath(); 17 | ; 18 | 19 | public static final String ICT_VERSION = "0.6.1"; 20 | public static final String ICT_REPOSITORY = "iotaledger/ict"; 21 | public static final String DEFAULT_PROPERTY_FILE_PATH = "ict.cfg"; 22 | public static final String WEB_GUI_PATH = "web/dist/"; 23 | 24 | public static final int MAX_NEIGHBOR_COUNT = 3; 25 | public static final int TRANSACTION_SIZE_TRITS = Transaction.Field.NONCE.tritOffset + Transaction.Field.NONCE.tritLength; 26 | public static final int TRANSACTION_SIZE_TRYTES = TRANSACTION_SIZE_TRITS / 3; 27 | public static final int TRANSACTION_SIZE_BYTES = TRANSACTION_SIZE_TRITS / 9 * 2; 28 | public static final int PACKET_SIZE_BYTES = TRANSACTION_SIZE_BYTES + Transaction.Field.BRANCH_HASH.byteLength; 29 | public static final long TIMESTAMP_DIFFERENCE_TOLERANCE_IN_MILLIS = 20000; 30 | public static final int MAX_AMOUNT_OF_ROUNDS_STORED = 45000; // 60sec/round --> 1 month 31 | public static final int API_MAX_STATS_PER_NEIGHBOR = 300; 32 | 33 | public static final int CURL_ROUNDS_TRANSACTION_HASH = 27; 34 | public static final int CURL_ROUNDS_BUNDLE_HASH = 27; 35 | 36 | public static RunModus RUN_MODUS = RunModus.TESTING; 37 | public static final int MIN_WEIGHT_MAGNITUDE = 3; 38 | 39 | public static final long CHECK_FOR_UPDATES_INTERVAL_MS = 6 * 3600 * 1000; 40 | 41 | /** 42 | * Specifies through which trit of the transaction hash each flag is defined. 43 | */ 44 | public static final class HashFlags { 45 | public static final int BUNDLE_HEAD_FLAG = 1; 46 | public static final int BUNDLE_TAIL_FLAG = 2; 47 | } 48 | 49 | public static final class Environments { 50 | public static final Environment GOSSIP = new Environment("gossip"); 51 | public static final ChainedEnvironment GOSSIP_PREPROCESSOR_CHAIN = new ChainedEnvironment("gossip_chain"); 52 | } 53 | 54 | public enum RunModus { 55 | MAIN, TESTING, TESTING_BUT_WITH_REAL_MWM 56 | } 57 | } -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/utils/IOHelper.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import org.apache.logging.log4j.Logger; 5 | 6 | import java.io.*; 7 | import java.nio.file.Files; 8 | import java.util.jar.JarEntry; 9 | import java.util.jar.JarFile; 10 | 11 | public class IOHelper { 12 | 13 | protected static final Logger LOGGER = LogManager.getLogger("IOHelper"); 14 | 15 | public static String readInputStream(InputStream in) throws IOException { 16 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in)); 17 | StringBuilder sb = new StringBuilder(); 18 | String line; 19 | while ((line = bufferedReader.readLine()) != null) 20 | sb.append(line); 21 | return sb.toString(); 22 | } 23 | 24 | public static Throwable deleteRecursively(File file) { 25 | if (!file.exists()) 26 | return null; 27 | LOGGER.info("Deleting " + file + " ..."); 28 | try { 29 | if (file.isDirectory()) 30 | for (String subPath : file.list()) { 31 | Throwable throwable = deleteRecursively(new File(file.getPath(), subPath)); 32 | if(throwable != null) 33 | throw throwable; 34 | } 35 | if(!file.delete()) 36 | throw new RuntimeException("Could not delete " + file.getAbsolutePath()); 37 | } catch (Throwable t) { 38 | LOGGER.error("Could not delete " + file.getAbsolutePath(), t); 39 | return t; 40 | } 41 | 42 | return null; 43 | } 44 | 45 | public static String readFile(File file) throws IOException { 46 | return new String(Files.readAllBytes(file.toPath())); 47 | } 48 | 49 | public static void writeToFile(File file, String data) throws IOException { 50 | file.getParentFile().mkdirs(); 51 | file.createNewFile(); 52 | try (Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)))) { 53 | writer.write(data); 54 | } 55 | } 56 | 57 | public static void extractDirectoryFromJarFile(Class classInJar, String relativePath, String target) throws IOException { 58 | try (JarFile jarFile = new java.util.jar.JarFile(new File(classInJar.getProtectionDomain().getCodeSource().getLocation().getPath()))) { 59 | java.util.Enumeration enumEntries = jarFile.entries(); 60 | LOGGER.info("extracting " + relativePath + " in .jar file to " + target + " ..."); 61 | while (enumEntries.hasMoreElements()) { 62 | java.util.jar.JarEntry source = (java.util.jar.JarEntry) enumEntries.nextElement(); 63 | if (source.getName().startsWith(relativePath)) { 64 | java.io.File destination = new java.io.File(target + java.io.File.separator + source.getName().substring(relativePath.length())); 65 | extractFile(jarFile, source, destination); 66 | } 67 | } 68 | } 69 | } 70 | 71 | private static void extractFile(JarFile jarFile, JarEntry source, File destination) throws IOException { 72 | LOGGER.info("extracting file: " + source.getName() + " ..."); 73 | if (source.isDirectory()) { 74 | destination.mkdirs(); 75 | return; 76 | } 77 | destination.createNewFile(); 78 | try (java.io.InputStream is = jarFile.getInputStream(source); java.io.FileOutputStream fos = new java.io.FileOutputStream(destination)) { 79 | while (is.available() > 0) 80 | fos.write(is.read()); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/utils/IssueCollector.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | 6 | /** 7 | * Collects unexpected exceptions and errors thrown during runtime. Makes it simpler to diagnose any issues. 8 | * */ 9 | public final class IssueCollector { 10 | 11 | private static final int MAX_AMOUNT_OF_THROWABLES_TO_PRINT = 10; 12 | private static final int MAX_AMOUNT_OF_THROWABLES_BEFORE_SHUTDOWN = 10; 13 | protected static final List incidents = new LinkedList<>(); 14 | 15 | public static void collect(Throwable t) { 16 | incidents.add(new Incident(t)); 17 | 18 | if (incidents.size() > MAX_AMOUNT_OF_THROWABLES_BEFORE_SHUTDOWN) { 19 | log(); 20 | System.exit(0); 21 | } 22 | } 23 | 24 | public static int amountOfIndicidents() { 25 | return incidents.size(); 26 | } 27 | 28 | public static void log() { 29 | // use system.err instead of logger in case there is an exception with logging 30 | if (incidents.size() > 0) { 31 | System.err.println("***** ERROR REPORT *****"); 32 | System.err.println("This is a list of critical incidents which occurred during runtime."); 33 | System.err.println("Please create an issue on https://github.com/iotaledger/ict or report it in #ict-discussion in IOTA Discord."); 34 | 35 | int amountPrinted = 0; 36 | for (Incident incident : incidents) { 37 | System.err.println(); 38 | if (amountPrinted++ > MAX_AMOUNT_OF_THROWABLES_TO_PRINT) { 39 | System.err.println("And " + (incidents.size() - MAX_AMOUNT_OF_THROWABLES_TO_PRINT) + " more incidents ..."); 40 | break; 41 | } 42 | incident.log(); 43 | } 44 | 45 | System.err.println(); 46 | System.err.println("************************"); 47 | } 48 | } 49 | 50 | 51 | static class Incident { 52 | private final Throwable throwable; 53 | private final long timestamp; 54 | 55 | private Incident(Throwable throwable) { 56 | this.throwable = throwable; 57 | this.timestamp = System.currentTimeMillis(); 58 | } 59 | 60 | private void log() { 61 | System.err.println("[" + timestamp + "]"); 62 | throwable.printStackTrace(); 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/utils/LogAppender.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils; 2 | 3 | import org.apache.logging.log4j.core.*; 4 | import org.apache.logging.log4j.core.appender.AbstractAppender; 5 | import org.apache.logging.log4j.core.config.plugins.Plugin; 6 | import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 7 | import org.apache.logging.log4j.core.config.plugins.PluginElement; 8 | import org.apache.logging.log4j.core.config.plugins.PluginFactory; 9 | 10 | import java.io.Serializable; 11 | 12 | @Plugin(name = "LogAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) 13 | 14 | public class LogAppender extends AbstractAppender { 15 | 16 | public static final Object NOTIFY_SYNCHRONIZER = new Object(); 17 | 18 | private static final int LOG_CAPACITY = 5000; 19 | private static int writePosition = 0; 20 | private static LogEvent[] logs = new LogEvent[LOG_CAPACITY]; 21 | 22 | protected LogAppender(String name, Filter filter, Layout layout, final boolean ignoreExceptions) { 23 | super(name, filter, layout, ignoreExceptions); 24 | } 25 | 26 | @PluginFactory 27 | public static LogAppender createAppender(@PluginAttribute("name") String name, @PluginElement("Layout") Layout layout, @PluginElement("Filter") Filter filter) { 28 | return new LogAppender(name, filter, layout, false); 29 | } 30 | 31 | @Override 32 | public synchronized void append(LogEvent event) { 33 | logs[(writePosition++)%LOG_CAPACITY] = event; 34 | synchronized (NOTIFY_SYNCHRONIZER) { 35 | NOTIFY_SYNCHRONIZER.notifyAll(); 36 | } 37 | } 38 | 39 | public static LogEvent getLogEvent(int index) { 40 | if(index >= writePosition || index < writePosition-LOG_CAPACITY || index < 0) 41 | return null; 42 | return logs[index % LOG_CAPACITY]; 43 | } 44 | 45 | public static int getIndexMin() { 46 | return Math.max(writePosition-LOG_CAPACITY, -1); 47 | } 48 | 49 | public static int getIndexMax() { 50 | return writePosition-1; 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/utils/MultiHashMap.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils; 2 | 3 | import java.util.HashMap; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class MultiHashMap { 9 | 10 | private Map> implementation = new HashMap<>(); 11 | 12 | public void add(K key, V value) { 13 | implementation.putIfAbsent(key, new LinkedList()); 14 | List list = get(key); 15 | if(!list.contains(value)) 16 | list.add(value); 17 | } 18 | 19 | public void clear() { 20 | implementation = new HashMap<>(); 21 | } 22 | 23 | public List get(K key) { 24 | return implementation.getOrDefault(key, new LinkedList()); 25 | } 26 | 27 | public void remove(K key) { 28 | implementation.remove(key); 29 | } 30 | 31 | public int size() { 32 | return implementation.size(); 33 | } 34 | 35 | public boolean containsKey(K key) { 36 | return implementation.containsKey(key); 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/utils/RestartableThread.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils; 2 | 3 | import org.apache.logging.log4j.Logger; 4 | import org.iota.ict.utils.interfaces.Restartable; 5 | 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | 9 | /** 10 | * Framework for a thread that can be restarted. Allows to register sub-threads that will be automatically started and terminated. 11 | * */ 12 | public abstract class RestartableThread implements Restartable, Runnable { 13 | 14 | protected Logger logger; 15 | protected State state = new StateTerminated(); 16 | protected Thread runningThread; 17 | protected List subWorkers = new LinkedList<>(); 18 | 19 | protected RestartableThread(Logger Logger) { 20 | this.logger = Logger; 21 | } 22 | 23 | public void onStart() { 24 | } 25 | 26 | public void onTerminate() { 27 | } 28 | 29 | public void onStarted() { 30 | } 31 | 32 | public void onTerminated() { 33 | } 34 | 35 | @Override 36 | public synchronized void start() { 37 | state.start(); 38 | } 39 | 40 | @Override 41 | public synchronized void terminate() { 42 | state.terminate(); 43 | } 44 | 45 | protected class State implements Restartable { 46 | 47 | protected final String name; 48 | 49 | protected State(String name) { 50 | this.name = name; 51 | } 52 | 53 | public void start() { 54 | throwIllegalStateException("start"); 55 | } 56 | 57 | public void terminate() { 58 | throwIllegalStateException("terminate"); 59 | } 60 | 61 | protected void throwIllegalStateException(String actionName) { 62 | throw new IllegalStateException("Action '" + actionName + "' cannot be performed from state '" + name + "'."); 63 | } 64 | } 65 | 66 | protected class StateTerminated extends State { 67 | protected StateTerminated() { 68 | super("terminated"); 69 | } 70 | 71 | @Override 72 | public void start() { 73 | if (Constants.RUN_MODUS == Constants.RunModus.MAIN && logger != null) 74 | logger.debug("starting ..."); 75 | state = new StateStarting(); 76 | onStart(); 77 | for (Restartable subWorker : subWorkers) 78 | subWorker.start(); 79 | state = new StateRunning(); 80 | runningThread = new Thread(RestartableThread.this, RestartableThread.this.getClass().getName()); 81 | runningThread.start(); 82 | onStarted(); 83 | if (Constants.RUN_MODUS == Constants.RunModus.MAIN && logger != null) 84 | logger.debug("started"); 85 | } 86 | } 87 | 88 | protected class StateStarting extends State { 89 | protected StateStarting() { 90 | super("starting"); 91 | } 92 | } 93 | 94 | protected class StateRunning extends State { 95 | protected StateRunning() { 96 | super("running"); 97 | } 98 | 99 | @Override 100 | public void terminate() { 101 | if (Constants.RUN_MODUS == Constants.RunModus.MAIN && logger != null) 102 | logger.debug("terminating ..."); 103 | state = new StateTerminating(); 104 | onTerminate(); 105 | while (runningThread.isAlive()) 106 | safeSleep(1); 107 | for (Restartable subWorker : subWorkers) 108 | subWorker.terminate(); 109 | state = new StateTerminated(); 110 | onTerminated(); 111 | if (Constants.RUN_MODUS == Constants.RunModus.MAIN && logger != null) 112 | logger.debug("terminated"); 113 | } 114 | 115 | private void safeSleep(long ms) { 116 | try { 117 | Thread.sleep(ms); 118 | } catch (InterruptedException e) { 119 | throw new RuntimeException(e); 120 | } 121 | } 122 | } 123 | 124 | protected class StateTerminating extends State { 125 | protected StateTerminating() { 126 | super("terminate"); 127 | } 128 | } 129 | 130 | public boolean isRunning() { 131 | return state instanceof StateRunning; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/utils/Stats.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils; 2 | 3 | import org.iota.ict.network.Neighbor; 4 | import org.json.JSONObject; 5 | 6 | public class Stats { 7 | public long timestamp; 8 | public final Neighbor neighbor; 9 | public long receivedAll, receivedNew, invalid, requested, ignored; 10 | 11 | public Stats(Neighbor neighbor) { 12 | this.timestamp = System.currentTimeMillis(); 13 | this.neighbor = neighbor; 14 | receivedAll = 0; 15 | receivedNew = 0; 16 | invalid = 0; 17 | requested = 0; 18 | ignored = 0; 19 | } 20 | 21 | public Stats(JSONObject json) { 22 | neighbor = null; 23 | receivedAll = json.getInt("all"); 24 | receivedNew = json.getInt("new"); 25 | ignored = json.getInt("ignored"); 26 | invalid = json.getInt("invalid"); 27 | requested = json.getInt("requested"); 28 | } 29 | 30 | public Stats(Stats reference) { 31 | timestamp = reference.timestamp; 32 | neighbor = reference.neighbor; 33 | receivedAll = reference.receivedAll; 34 | receivedNew = reference.receivedNew; 35 | invalid = reference.invalid; 36 | requested = reference.requested; 37 | ignored = reference.ignored; 38 | } 39 | 40 | public void accumulate(Stats reference) { 41 | receivedAll += reference.receivedAll; 42 | receivedNew += reference.receivedNew; 43 | invalid += reference.invalid; 44 | requested += reference.requested; 45 | ignored += reference.ignored; 46 | } 47 | 48 | public JSONObject toJSON() { 49 | return new JSONObject().put("timestamp", timestamp) 50 | .put("all", receivedAll) 51 | .put("new", receivedNew) 52 | .put("ignored", ignored) 53 | .put("requested", requested) 54 | .put("invalid", invalid); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/utils/Updater.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import org.apache.logging.log4j.Logger; 5 | import org.iota.ict.Main; 6 | import org.iota.ict.api.GithubGateway; 7 | import org.iota.ict.ixi.IxiModule; 8 | import org.iota.ict.ixi.IxiModuleHolder; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.lang.management.ManagementFactory; 14 | import java.net.URL; 15 | import java.nio.file.Files; 16 | import java.nio.file.Path; 17 | import java.nio.file.StandardCopyOption; 18 | 19 | public final class Updater { 20 | 21 | private static long lastTimeChckecForUpdates = 0; 22 | private static String availableUpdate = null; 23 | private static final Logger LOGGER = LogManager.getLogger("Updater"); 24 | 25 | public static void update(String version) throws IOException { 26 | URL url = GithubGateway.getAssetDownloadUrl(Constants.ICT_REPOSITORY, version); 27 | String targetFileName = "ict-" + version + ".jar"; 28 | Path target = Constants.WORKING_DIRECTORY.resolve(targetFileName); 29 | LOGGER.info("Ict Update: downloading precompiled .jar file from " + url + " into " + target + " ..."); 30 | try (InputStream in = url.openStream()) { 31 | Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING); 32 | } 33 | LOGGER.info("Download complete. Please run the new version ("+targetFileName+")."); 34 | } 35 | 36 | public static void checkForUpdatesIfYouHaveNotDoneSoInALongTime(IxiModuleHolder moduleHolder) { 37 | if(lastTimeChckecForUpdates + Constants.CHECK_FOR_UPDATES_INTERVAL_MS < System.currentTimeMillis()) { 38 | checkForUpdates(); 39 | for(IxiModule module : moduleHolder.getModules()) 40 | moduleHolder.getInfo(module).checkForUpdate(); 41 | } 42 | } 43 | 44 | public static void checkForUpdates() { 45 | LOGGER.info("Checking for updates ..."); 46 | lastTimeChckecForUpdates = System.currentTimeMillis(); 47 | 48 | try { 49 | String latestReleaseVersion = GithubGateway.getLatestReleaseLabel(Constants.ICT_REPOSITORY); 50 | if (VersionComparator.getInstance().compare(Constants.ICT_VERSION, latestReleaseVersion) < 0) { 51 | LOGGER.warn(">>>>> A new release of Ict is available. Please update to " + latestReleaseVersion + "! <<<<<"); 52 | availableUpdate = latestReleaseVersion; 53 | } 54 | else { 55 | LOGGER.info("You are already up-to-date!"); 56 | availableUpdate = null; 57 | } 58 | } catch (Throwable t) { 59 | LOGGER.error("Failed checking for updates", t); 60 | } 61 | } 62 | 63 | public static String getAvailableUpdate() { 64 | return availableUpdate; 65 | } 66 | } -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/utils/VersionComparator.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils; 2 | 3 | import java.util.Comparator; 4 | 5 | public final class VersionComparator implements Comparator { 6 | 7 | protected static final VersionComparator instance = new VersionComparator(); 8 | 9 | @Override 10 | public int compare(String version1, String version2) { 11 | assertVersionFormat(version1); 12 | assertVersionFormat(version2); 13 | int[] segments1 = segmentateVersion(version1); 14 | int[] segments2 = segmentateVersion(version2); 15 | return compareSegments(segments1, segments2); 16 | } 17 | 18 | protected int compareSegments(int[] segments1, int[] segments2) { 19 | for (int i = 0; i < Math.max(segments1.length, segments2.length); i++) { 20 | int seg1 = segments1.length > i ? segments1[i] : 0; 21 | int seg2 = segments2.length > i ? segments2[i] : 0; 22 | int segmentCompare = Integer.compare(seg1, seg2); 23 | if (segmentCompare != 0) 24 | return segmentCompare; 25 | } 26 | return 0; 27 | } 28 | 29 | protected void assertVersionFormat(String version) { 30 | if (!version.matches("^[0-9]*(\\.[0-9]*)*(\\-SNAPSHOT)?$")) 31 | throw new IllegalArgumentException("Unexpected format for version '" + version + "'."); 32 | } 33 | 34 | protected int[] segmentateVersion(String version) { 35 | String[] strSegments = version.replace("-SNAPSHOT", ".-1").split("\\."); 36 | int[] intSegments = new int[strSegments.length]; 37 | for (int i = 0; i < intSegments.length; i++) 38 | intSegments[i] = Integer.parseInt(strSegments[i]); 39 | return intSegments; 40 | } 41 | 42 | @Override 43 | public boolean equals(Object o) { 44 | return false; 45 | } 46 | 47 | public static VersionComparator getInstance() { 48 | return instance; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/utils/crypto/AutoIndexedMerkleTree.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils.crypto; 2 | 3 | public class AutoIndexedMerkleTree extends MerkleTree { 4 | 5 | protected int index; 6 | 7 | public AutoIndexedMerkleTree(String seed, int securityLevel, int depth) { 8 | super(seed, securityLevel, depth); 9 | this.index = 0; 10 | } 11 | 12 | public AutoIndexedMerkleTree(String seed, int securityLevel, int depth, int startIndex) { 13 | super(seed, securityLevel, depth); 14 | this.index = startIndex; 15 | } 16 | 17 | @Override 18 | public Signature sign(int index, String toSign) { 19 | throw new RuntimeException("Please use the other sign() function without the 'index' parameter."); 20 | } 21 | 22 | public Signature sign(String toSign) { 23 | return super.sign(index++, toSign); 24 | } 25 | 26 | public int getIndex() { 27 | return index; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/utils/crypto/SignatureScheme.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils.crypto; 2 | 3 | /** 4 | * Abstract description of a signature scheme. For increased modularity and to decouple the signature scheme implementation 5 | * from the Ict code base as much as possible so that it can be easily replaced. 6 | * */ 7 | public abstract class SignatureScheme { 8 | 9 | public interface PrivateKey { 10 | 11 | int length(); 12 | 13 | int fragments(); 14 | 15 | String getFragment(int index); 16 | 17 | String deriveAddress(); 18 | 19 | SignatureSchemeImplementation.PublicKey derivePublicKey(); 20 | 21 | SignatureSchemeImplementation.Signature sign(String toSign); 22 | 23 | String toString(); 24 | } 25 | 26 | 27 | public interface PublicKey { 28 | 29 | String getAddress(); 30 | 31 | String toString(); 32 | } 33 | 34 | 35 | public interface Signature { 36 | 37 | PublicKey derivePublicKey(); 38 | 39 | String deriveAddress(); 40 | 41 | Signature getFragment(int index); 42 | 43 | int fragments(); 44 | 45 | int length(); 46 | 47 | String toString(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/utils/interfaces/Configurable.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils.interfaces; 2 | 3 | import org.json.JSONObject; 4 | 5 | public interface Configurable { 6 | 7 | /** 8 | * Is called when the configuration is externally updated. The implementation can either accept the changes and update 9 | * the internal configuration or throw an Exception with a meaningful message to inform the caller about why the changes 10 | * cannot be applied (invalid format, invalid length, illegal characters, illegal combination of flags, etc.). 11 | */ 12 | void tryToUpdateConfiguration(JSONObject newConfiguration); 13 | 14 | /** 15 | * @return The current internal configuration of the IXI module. 16 | */ 17 | JSONObject getConfiguration(); 18 | 19 | /** 20 | * @return The default configuration which will be used when the user resets the configuration. This one should equal 21 | * the internal configuration right after installation. 22 | */ 23 | JSONObject getDefaultConfiguration(); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/utils/interfaces/Installable.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils.interfaces; 2 | 3 | public interface Installable { 4 | 5 | void install(); 6 | 7 | void uninstall(); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/utils/interfaces/Restartable.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils.interfaces; 2 | 3 | public interface Restartable { 4 | void start(); 5 | 6 | void terminate(); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/utils/properties/EditableProperties.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils.properties; 2 | 3 | import java.net.InetSocketAddress; 4 | import java.util.HashSet; 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | import java.util.Set; 8 | 9 | public class EditableProperties extends Properties { 10 | 11 | public EditableProperties() { 12 | super(); 13 | } 14 | 15 | EditableProperties(java.util.Properties propObject) { 16 | super(propObject); 17 | } 18 | 19 | // === SETTERS === 20 | 21 | public EditableProperties port(int port) { 22 | this.port = port; 23 | return this; 24 | } 25 | 26 | public EditableProperties host(String host) { 27 | this.host = host; 28 | return this; 29 | } 30 | 31 | public EditableProperties guiPort(int guiPort) { 32 | this.guiPort = guiPort; 33 | return this; 34 | } 35 | 36 | public EditableProperties guiEnabled(boolean guiEnabled) { 37 | this.guiEnabled = guiEnabled; 38 | return this; 39 | } 40 | 41 | public EditableProperties neighbors(Set neighbors) { 42 | this.neighbors = new HashSet<>(neighbors); 43 | return this; 44 | } 45 | 46 | public EditableProperties antiSpamAbs(long antiSpamAbs) { 47 | this.antiSpamAbs = antiSpamAbs; 48 | return this; 49 | } 50 | 51 | public EditableProperties maxForwardDelay(long maxForwardDelay) { 52 | this.maxForwardDelay = maxForwardDelay; 53 | return this; 54 | } 55 | 56 | public EditableProperties minForwardDelay(long minForwardDelay) { 57 | this.minForwardDelay = minForwardDelay; 58 | return this; 59 | } 60 | 61 | public EditableProperties roundDuration(long roundDuration) { 62 | this.roundDuration = roundDuration; 63 | return this; 64 | } 65 | 66 | public EditableProperties tangleCapacity(long tangleCapacity) { 67 | this.tangleCapacity = tangleCapacity; 68 | return this; 69 | } 70 | 71 | public EditableProperties guiPassword(String guiPassword) { 72 | this.guiPassword = guiPassword; 73 | return this; 74 | } 75 | 76 | public EditableProperties name(String name) { 77 | this.name = name; 78 | return this; 79 | } 80 | 81 | public EditableProperties sparkSSL(String sparkSSL) { 82 | this.sparkSSL = sparkSSL; 83 | return this; 84 | } 85 | 86 | public EditableProperties maxHeapSize(double maxHeapSize) { 87 | this.maxHeapSize = maxHeapSize; 88 | return this; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/utils/properties/FinalProperties.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils.properties; 2 | 3 | public class FinalProperties extends Properties { 4 | 5 | FinalProperties(java.util.Properties propObject) { 6 | super(propObject); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/org/iota/ict/utils/properties/PropertiesUser.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils.properties; 2 | 3 | public interface PropertiesUser { 4 | void updateProperties(FinalProperties properties); 5 | } -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/IctTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict; 2 | 3 | import org.iota.ict.utils.properties.Properties; 4 | import org.junit.Test; 5 | 6 | public class IctTest { 7 | 8 | @Test 9 | public void testStartAndTerminate() { 10 | Ict ict = new Ict(new Properties().toFinal()); 11 | ict.terminate(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/api/GithubGatewayTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.api; 2 | 3 | import org.iota.ict.utils.Constants; 4 | import org.iota.ict.utils.VersionComparator; 5 | import org.json.JSONArray; 6 | import org.json.JSONObject; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | 10 | public class GithubGatewayTest { 11 | 12 | @Test 13 | public void testGetReleases() { 14 | if(Math.random() < 0.9) // run randomly to safe time and to avoid api rate limits 15 | return; 16 | JSONArray releases = GithubGateway.getReleases(Constants.ICT_REPOSITORY); 17 | Assert.assertNotNull("Failed fetching releases from Github API.", releases); 18 | Assert.assertTrue("Expected releases but found none.", releases.length() > 0); 19 | } 20 | 21 | @Test 22 | public void testGetLatestReleaseLabel() { 23 | if(Math.random() < 0.9) // run randomly to safe time and to avoid api rate limits 24 | return; 25 | String label = GithubGateway.getLatestReleaseLabel(Constants.ICT_REPOSITORY); 26 | Assert.assertNotNull("Failed fetching label of latest release.", label); 27 | Assert.assertTrue("Unexpected label of most recent release: " + label, label.matches("0\\.[0-9]{1,2}(\\.[0-9])?")); 28 | Assert.assertTrue("Label of most recent release ('"+label+"') appears to be greater than or equal to current version ("+Constants.ICT_VERSION+").", VersionComparator.getInstance().compare(Constants.ICT_VERSION, label) > 0); 29 | } 30 | 31 | @Test 32 | public void testGetContents() { 33 | if(Math.random() < 0.9) // run randomly to safe time and to avoid api rate limits 34 | return; 35 | String contents = GithubGateway.getContents("iotaledger/ixi", "dev","versions.json"); 36 | JSONObject versions = new JSONObject(contents); 37 | Assert.assertEquals("Unexpected content of iotaledger/ixi versions.json.","0.4", versions.getString("0.4")); 38 | } 39 | } -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/api/JsonIctTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.api; 2 | 3 | import org.iota.ict.network.Neighbor; 4 | import org.iota.ict.utils.Constants; 5 | import org.iota.ict.utils.Stats; 6 | import org.json.JSONArray; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | 10 | import java.util.List; 11 | 12 | public class JsonIctTest { 13 | 14 | @Test 15 | public void neighborStatsToScaledJSON() { 16 | Neighbor neighbor = generateNeighborWithRandomStats(Constants.API_MAX_STATS_PER_NEIGHBOR * 5); 17 | JSONArray array = JsonIct.neighborStatsToScaledJSON(neighbor, 0, System.currentTimeMillis()+1); 18 | assertSumEquals(neighbor.getStatsHistory(), array); 19 | } 20 | 21 | private void assertSumEquals(List statsHistory, JSONArray jsonArray) { 22 | Stats sumExpected = new Stats(statsHistory.get(0)); 23 | for(int i = 1; i < statsHistory.size(); i++) 24 | sumExpected.accumulate(statsHistory.get(i)); 25 | 26 | Stats sumActual = new Stats((Neighbor) null); 27 | for(int i = 0; i < jsonArray.length(); i++) { 28 | Stats fromJSON = new Stats(jsonArray.getJSONObject(i)); 29 | sumActual.accumulate(fromJSON); 30 | } 31 | 32 | Assert.assertEquals("Incorrect sum of transactions.", sumExpected.receivedAll, sumActual.receivedAll); 33 | } 34 | 35 | private Neighbor generateNeighborWithRandomStats(int rounds) { 36 | Neighbor neighbor = new Neighbor("localhost:1234", 0); 37 | long timestamp = System.currentTimeMillis() - rounds * 100; 38 | neighbor.getStatsHistory().get(0).timestamp = timestamp; 39 | for(int i = 0; i < rounds; i++) { 40 | neighbor.getStats().receivedAll = (int)(Math.random() * 100); 41 | neighbor.getStats().timestamp = timestamp + i * 100; 42 | neighbor.newRound(0, false); 43 | } 44 | return neighbor; 45 | } 46 | } -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/eee/call/EEEFunctionCallerTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.eee.call; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import org.iota.ict.eee.dispatch.ThreadedEffectDispatcher; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.*; 8 | 9 | public class EEEFunctionCallerTest { 10 | 11 | @Test 12 | public void test() { 13 | final ThreadedEffectDispatcher dispatcher = new ThreadedEffectDispatcher(LogManager.getLogger("Test")); 14 | FunctionEnvironment environment = new FunctionEnvironment("Math", "multiply()"); 15 | final EEEFunction function = new EEEFunction(environment); 16 | EEEFunctionCaller functionCaller = new EEEFunctionCallerImplementation(dispatcher); 17 | 18 | 19 | dispatcher.start(); 20 | 21 | new Thread(){ 22 | @Override 23 | public void run() { 24 | try { 25 | EEEFunction.Request request = function.requestQueue.take(); 26 | String[] args = request.argument.split(","); 27 | int arg0 = Integer.parseInt(args[0]); 28 | int arg1 = Integer.parseInt(args[1]); 29 | request.submitReturn(dispatcher, (arg0*arg1)+""); 30 | } catch (InterruptedException e) { 31 | e.printStackTrace(); 32 | } 33 | } 34 | }.start(); 35 | dispatcher.addListener(function); 36 | 37 | int arg0 = 3, arg1 = 7; 38 | String result = functionCaller.call(environment, arg0+","+arg1, 50); 39 | assertEquals("" + (arg0*arg1), result); 40 | 41 | dispatcher.removeListener(function); 42 | } 43 | } -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/ixi/IxiGossipEventTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.ixi; 2 | 3 | import org.iota.ict.Ict; 4 | import org.iota.ict.IctTestTemplate; 5 | import org.iota.ict.model.transaction.Transaction; 6 | import org.iota.ict.model.transaction.TransactionBuilder; 7 | import org.iota.ict.network.gossip.GossipEvent; 8 | import org.iota.ict.network.gossip.GossipListener; 9 | import org.iota.ict.utils.Trytes; 10 | import org.junit.*; 11 | 12 | import java.util.Set; 13 | 14 | public class IxiGossipEventTest extends IctTestTemplate { 15 | 16 | @Test 17 | public void testIxiReceivesGossipEvent() { 18 | 19 | // given 20 | Ict ict = createIct(); 21 | TestIxiModule module = new TestIxiModule(ict); 22 | module.start(); 23 | saveSleep(100); 24 | 25 | // then 26 | Assert.assertNotNull("IXI module did not receive gossip.", module.gossipEvent); 27 | Assert.assertEquals("Event received by IXI module contains wrong transaction", module.transaction, module.gossipEvent.getTransaction()); 28 | Assert.assertNotNull("Ict did not store transaction submitted by IXI module.", ict.getTangle().findTransactionByHash(module.transaction.hash)); 29 | Assert.assertTrue("IXI module can't query transaction from tangle.", module.findTransactionsByAddress(module.transaction.address()).contains(module.transaction)); 30 | } 31 | } 32 | 33 | class TestIxiModule extends IxiModule { 34 | 35 | Transaction transaction = createTransaction(); 36 | GossipEvent gossipEvent = null; 37 | 38 | public TestIxiModule(Ixi ixi) { 39 | super(ixi); 40 | ixi.addListener(new GossipListener.Implementation() { 41 | @Override 42 | public void onReceive(GossipEvent event) { 43 | gossipEvent = event; 44 | } 45 | }); 46 | } 47 | 48 | @Override 49 | public void run() { 50 | ixi.submit(transaction); 51 | } 52 | 53 | Set findTransactionsByAddress(String address) { 54 | return ixi.findTransactionsByAddress(address); 55 | } 56 | 57 | private static Transaction createTransaction() { 58 | TransactionBuilder builder = new TransactionBuilder(); 59 | builder.address = Trytes.randomSequenceOfLength(Transaction.Field.ADDRESS.tryteLength); 60 | return builder.build(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/ixi/VirtualIxiModule.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.ixi; 2 | 3 | 4 | public class VirtualIxiModule extends IxiModule { 5 | 6 | static boolean success = false; 7 | 8 | public VirtualIxiModule(Ixi ixi) { 9 | super(ixi); 10 | } 11 | 12 | @Override 13 | public void run() { 14 | success = true; 15 | } 16 | } -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/ixi/VirtualIxiModuleTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.ixi; 2 | 3 | import org.iota.ict.Ict; 4 | import org.iota.ict.IctTestTemplate; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | public class VirtualIxiModuleTest extends IctTestTemplate { 9 | 10 | @Test 11 | public void test() throws Exception { 12 | Ict ict = createIct(); 13 | ict.getModuleHolder().loadVirtualModule(VirtualIxiModule.class, "Module"); 14 | ict.getModuleHolder().startAllModules(); 15 | saveSleep(200); 16 | Assert.assertTrue("run() of virtual IXI module was not invoked", VirtualIxiModule.success); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/model/tangle/RingTangleTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.model.tangle; 2 | 3 | import org.iota.ict.Ict; 4 | import org.iota.ict.IctTestTemplate; 5 | import org.iota.ict.model.transaction.Transaction; 6 | import org.iota.ict.model.transaction.TransactionBuilder; 7 | import org.iota.ict.utils.properties.EditableProperties; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | 11 | import java.util.*; 12 | 13 | public class RingTangleTest extends IctTestTemplate { 14 | 15 | @Test 16 | public void testSameTimestamp() { 17 | int ringTangleCapacity = 10; 18 | 19 | EditableProperties properties = new EditableProperties(); 20 | properties.tangleCapacity(ringTangleCapacity); 21 | properties.maxHeapSize(1.0); 22 | Ict ict = createIct(properties); 23 | 24 | TransactionBuilder builder = new TransactionBuilder(); 25 | for (int i = 0; i < ringTangleCapacity * 2; i++) { 26 | ict.submit(builder.build()); 27 | int amountSubmitted = i + 1; 28 | int expectedTangleSize = Math.min(amountSubmitted + 1, ringTangleCapacity); // +1 for NULL transaction 29 | Assert.assertEquals("Unexpected amount of transactions.", expectedTangleSize, ict.getTangle().size()); 30 | } 31 | } 32 | 33 | @Test 34 | public void testMaxNumberOfTransactions() { 35 | int ringTangleCapacity = 10; 36 | int offset = ringTangleCapacity / 2; 37 | int totalTransactions = ringTangleCapacity * 2; 38 | 39 | EditableProperties properties = new EditableProperties(); 40 | properties.maxHeapSize(1.0); 41 | properties.tangleCapacity (ringTangleCapacity); 42 | Ict ict = createIct(properties); 43 | 44 | List transactionsOrderedByTimestamps = generateTransactionsOrderedByTimestamps(totalTransactions); 45 | 46 | Set previousTransactions = new HashSet<>(transactionsOrderedByTimestamps.subList(0, offset + ringTangleCapacity - 1)); 47 | Set tangleContentBefore = new HashSet<>(transactionsOrderedByTimestamps.subList(offset, offset + ringTangleCapacity - 1)); 48 | 49 | Set newTransactions = new HashSet<>(transactionsOrderedByTimestamps.subList(offset + ringTangleCapacity - 1, transactionsOrderedByTimestamps.size())); 50 | Set tangleContentAfter = new HashSet<>(transactionsOrderedByTimestamps.subList(transactionsOrderedByTimestamps.size() - ringTangleCapacity + 1, transactionsOrderedByTimestamps.size())); 51 | 52 | 53 | submit(ict, previousTransactions); 54 | assertTangleContainsExactlyPlusNullTx(ict.getTangle(), tangleContentBefore); 55 | 56 | submit(ict, newTransactions); 57 | assertTangleContainsExactlyPlusNullTx(ict.getTangle(), tangleContentAfter); 58 | } 59 | 60 | private static void submit(Ict ict, Iterable transactions) { 61 | for (Transaction transaction : transactions) 62 | ict.submit(transaction); 63 | } 64 | 65 | private static void assertTangleContainsExactlyPlusNullTx(Tangle tangle, Set transactionsToContain) { 66 | Assert.assertNotNull("The NULL transaction is missing.", tangle.findTransactionLog(Transaction.NULL_TRANSACTION)); 67 | for (Transaction transaction : transactionsToContain) 68 | Assert.assertNotNull("A transaction is missing.", tangle.findTransactionLog(transaction)); 69 | assertIntEqualsWithTolerance("Unexpected amount of transactions.", transactionsToContain.size() + 1, tangle.size(), 1); 70 | } 71 | 72 | private static void assertIntEqualsWithTolerance(String message, int expected, int actual, int tolerance) { 73 | if(Math.abs(expected-actual) > tolerance) 74 | Assert.assertEquals("Unexpected amount of transactions.", expected, actual); 75 | } 76 | 77 | private static List generateTransactionsOrderedByTimestamps(int amount) { 78 | List transactions = new LinkedList<>(); 79 | long timestamp = System.currentTimeMillis(); 80 | while (amount > 0) { 81 | timestamp += Math.random() * 1000 + 1; 82 | TransactionBuilder builder = new TransactionBuilder(); 83 | builder.issuanceTimestamp = timestamp; 84 | transactions.add(builder.build()); 85 | amount--; 86 | } 87 | return transactions; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/network/GossipTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.network; 2 | 3 | import org.iota.ict.Ict; 4 | import org.iota.ict.IctTestTemplate; 5 | import org.iota.ict.model.transaction.Transaction; 6 | import org.iota.ict.model.transaction.TransactionBuilder; 7 | import org.junit.Assert; 8 | 9 | import java.util.*; 10 | 11 | public abstract class GossipTest extends IctTestTemplate { 12 | 13 | 14 | void testBidirectionalCommunication(Ict ictA, Ict ictB, int messagesPerDirection) { 15 | testUnidirectionalCommunication(ictA, ictB, messagesPerDirection); 16 | testUnidirectionalCommunication(ictB, ictA, messagesPerDirection); 17 | } 18 | 19 | void testUnidirectionalCommunication(Ict sender, Ict receiver, int amountOfMessages) { 20 | Map sentMessagesByHash = sendMessages(sender, amountOfMessages); 21 | Assert.assertEquals("unique hashes of sent transactions", amountOfMessages, sentMessagesByHash.values().size()); 22 | waitUntilCommunicationEnds(1000); 23 | assertThatTransactionsReceived(receiver, sentMessagesByHash, (int) Math.ceil(amountOfMessages * 0.85)); 24 | } 25 | 26 | Map sendMessages(Ict sender, int amountOfMessages) { 27 | Map sentMessagesByHash = new HashMap<>(); 28 | TransactionBuilder builder = new TransactionBuilder(); 29 | for (int i = 0; i < amountOfMessages; i++) { 30 | String message = randomAsciiMessage(); 31 | builder.asciiMessage(message); 32 | Transaction transaction = builder.buildWhileUpdatingTimestamp(); 33 | sender.submit(transaction); 34 | sentMessagesByHash.put(transaction.hash, message); 35 | } 36 | return sentMessagesByHash; 37 | } 38 | 39 | void assertThatTransactionsReceived(Ict receiver, Map sentMessagesByHash, int minRequired) { 40 | int receivedTransactions = 0; 41 | for (String hash : sentMessagesByHash.keySet()) 42 | if (receiver.findTransactionByHash(hash) != null) 43 | receivedTransactions++; 44 | if (receivedTransactions < minRequired) 45 | Assert.fail("sent " + sentMessagesByHash.size() + " but received " + receivedTransactions); 46 | } 47 | 48 | private static String randomAsciiMessage() { 49 | char[] message = new char[(int) (Math.random() * 20)]; 50 | for (int i = 0; i < message.length - 1; i++) 51 | message[i] = (char) (Math.random() * 127 + 1); 52 | return new String(message); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/network/HostResolveTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.network; 2 | 3 | import org.junit.Test; 4 | 5 | import java.net.DatagramPacket; 6 | import java.net.InetSocketAddress; 7 | 8 | import static org.junit.Assert.*; 9 | 10 | /** 11 | * @author https://github.com/georgmittendorfer (https://github.com/iotaledger/ict/issues/3#issuecomment-450352986) 12 | */ 13 | public class HostResolveTest { 14 | 15 | private final Neighbor neighbor = new Neighbor("localhost:42", 0); 16 | 17 | @Test 18 | public void sameIpWhenSentPacketFromSameIpThenTrue() { 19 | assertTrue("expected match", neighbor.sentPacketFromSameIP(packetFrom("localhost", 42))); 20 | assertTrue("expected match", neighbor.sentPacketFromSameIP(packetFrom("localhost", 666))); 21 | assertTrue("expected match", neighbor.sentPacketFromSameIP(packetFrom("127.0.0.1", 42))); 22 | assertTrue("expected match", neighbor.sentPacketFromSameIP(packetFrom("127.0.0.1", 666))); 23 | } 24 | 25 | @Test 26 | public void differentIpWhenSentPacketFromSameIpThenFalse() { 27 | assertFalse("expected no match", neighbor.sentPacketFromSameIP(packetFrom("google-public-dns-a.google.com", 42))); 28 | assertFalse("expected no match", neighbor.sentPacketFromSameIP(packetFrom("8.8.8.8", 42))); 29 | } 30 | 31 | private DatagramPacket packetFrom(String hostOrIp, int port) { 32 | return new DatagramPacket(new byte[]{}, 0, new InetSocketAddress(hostOrIp, port)); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/network/LogRoundTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.network; 2 | 3 | import org.iota.ict.Ict; 4 | import org.iota.ict.utils.Stats; 5 | import org.iota.ict.utils.Trytes; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | 9 | public class LogRoundTest extends GossipTest { 10 | 11 | @Test 12 | public void testTransactionCounting() { 13 | Ict a = createIct(); 14 | Ict b = createIct(); 15 | connect(a, b); 16 | 17 | a.request(Trytes.randomSequenceOfLength(81)); 18 | sendMessages(a, 10); 19 | waitUntilCommunicationEnds(200); 20 | 21 | Stats statsForB = a.getNeighbors().get(0).getStats(); 22 | Stats statsForA = b.getNeighbors().get(0).getStats(); 23 | 24 | Assert.assertEquals("transaction was back-broadcasted (useless communication)", 0, statsForB.receivedAll); 25 | Assert.assertEquals("neighbor did not receive all transactions", 10, statsForA.receivedAll); 26 | Assert.assertEquals("neighbor considered new transaction as not new", 10, statsForA.receivedNew); 27 | Assert.assertEquals("neighbor received invalid transactions", 0, statsForA.invalid); 28 | Assert.assertEquals("neighbor did not add request to stats", 1, statsForA.requested); 29 | 30 | b.getNeighbors().get(0).newRound(10000, false); 31 | Stats statsForANew = b.getNeighbors().get(0).getStats(); 32 | Assert.assertEquals("stats was not reset upon new round", 0, statsForANew.receivedAll); 33 | Assert.assertEquals("previous stats were lost upon new round", 10, statsForA.receivedAll); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/network/RandomTopologyTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.network; 2 | 3 | import org.iota.ict.Ict; 4 | import org.junit.Assert; 5 | import org.junit.Ignore; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.junit.runners.Parameterized; 9 | 10 | import java.util.Collections; 11 | import java.util.LinkedList; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | @Ignore // takes too long, only test occasionally 16 | @RunWith(Parameterized.class) 17 | public class RandomTopologyTest extends GossipTest { 18 | 19 | @Parameterized.Parameters 20 | public static Object[][] data() { 21 | // lets this Test run 10 times 22 | return new Object[10][0]; 23 | } 24 | 25 | @Test 26 | public void testRandomNetworkTopologies() { 27 | List network = createRandomNetworkTopology(10); 28 | randomlyConnectIcts(network, 3); 29 | Ict randomIct = network.get((int) (Math.random() * network.size())); 30 | LinkedList otherIcts = new LinkedList<>(network); 31 | otherIcts.remove(randomIct); 32 | testCommunicationRange(randomIct, otherIcts, 20); 33 | } 34 | 35 | private void testCommunicationRange(Ict sender, List otherIcts, int amountOfMessages) { 36 | Map sentMessagesByHash = sendMessages(sender, amountOfMessages); 37 | waitUntilCommunicationEnds(1000); 38 | for (Ict receiver : otherIcts) 39 | assertThatTransactionsReceived(receiver, sentMessagesByHash, (int) Math.ceil(amountOfMessages * 0.85)); 40 | } 41 | 42 | private List createRandomNetworkTopology(int amountOfIcts) { 43 | List icts = new LinkedList<>(); 44 | for (; amountOfIcts > 0; amountOfIcts--) 45 | icts.add(createIct()); 46 | return icts; 47 | } 48 | 49 | private void randomlyConnectIcts(List icts, int requiredNeighbors) { 50 | for (Ict ict : icts) { 51 | LinkedList neighborCandidates = new LinkedList<>(icts); 52 | neighborCandidates.remove(ict); 53 | while (ict.getNeighbors().size() < requiredNeighbors) { 54 | Ict neighbor = findIctWithLeastNeighbors(neighborCandidates); 55 | neighborCandidates.remove(neighbor); 56 | if (neighbor == null) 57 | Assert.fail("Couldn't find neighbor."); 58 | connect(ict, neighbor); 59 | } 60 | } 61 | } 62 | 63 | private Ict findIctWithLeastNeighbors(List icts) { 64 | Collections.shuffle(icts); 65 | Ict ictWithLeastNeighbors = null; 66 | for (Ict ict : icts) 67 | if (ictWithLeastNeighbors == null || ict.getNeighbors().size() < ictWithLeastNeighbors.getNeighbors().size()) 68 | ictWithLeastNeighbors = ict; 69 | return ictWithLeastNeighbors; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/network/SimpleTopologyTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.network; 2 | 3 | import org.iota.ict.Ict; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | 7 | public class SimpleTopologyTest extends GossipTest { 8 | 9 | @Test 10 | public void testCommunication() { 11 | 12 | Ict a = createIct(); 13 | Ict b = createIct(); 14 | Ict c = createIct(); 15 | 16 | connect(a, b); 17 | connect(b, c); 18 | 19 | testBidirectionalCommunication(a, b, 10); 20 | testBidirectionalCommunication(b, c, 10); 21 | testBidirectionalCommunication(a, c, 10); 22 | } 23 | 24 | @Test 25 | public void testNoInfiniteLoopMessageForwarding() { 26 | 27 | Ict a = createIct(); 28 | Ict b = createIct(); 29 | Ict c = createIct(); 30 | 31 | connect(a, b); 32 | connect(a, c); 33 | connect(b, c); 34 | 35 | int amountOfMessages = 10; 36 | sendMessages(a, amountOfMessages); 37 | 38 | waitUntilCommunicationEnds(500); 39 | Assert.assertTrue("no infinite loop message forwarding", c.getNeighbors().get(0).getStats().receivedAll <= amountOfMessages); 40 | Assert.assertTrue("no infinite loop message forwarding", c.getNeighbors().get(1).getStats().receivedAll <= amountOfMessages); 41 | Assert.assertTrue("all messages received", c.getNeighbors().get(0).getStats().receivedAll + c.getNeighbors().get(1).getStats().receivedAll >= amountOfMessages); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/network/SpamProtectionTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.network; 2 | 3 | import org.iota.ict.Ict; 4 | import org.iota.ict.model.transaction.Transaction; 5 | import org.iota.ict.model.transaction.TransactionBuilder; 6 | import org.iota.ict.utils.Constants; 7 | import org.iota.ict.utils.Stats; 8 | import org.iota.ict.utils.properties.EditableProperties; 9 | import org.junit.Assert; 10 | import org.junit.Test; 11 | 12 | import java.lang.reflect.Field; 13 | import java.lang.reflect.Modifier; 14 | import java.util.HashSet; 15 | import java.util.Set; 16 | 17 | public class SpamProtectionTest extends GossipTest { 18 | 19 | @Test 20 | public void testMaxTransactionsPerRound() { 21 | 22 | int maxTransactionsPerRound = 666; 23 | 24 | EditableProperties properties = new EditableProperties(); 25 | properties.antiSpamAbs(maxTransactionsPerRound); 26 | 27 | Ict a = createIct(); 28 | Ict b = createIct(properties); 29 | 30 | connect(a, b); 31 | 32 | Stats statsForA = b.getNeighbors().get(0).getStats(); 33 | 34 | statsForA.receivedAll = maxTransactionsPerRound - 10; 35 | testUnidirectionalCommunication(a, b, 10); 36 | assertTransactionDoesNotMakeItThrough(b); 37 | } 38 | 39 | private void assertTransactionDoesNotMakeItThrough(Ict receiver) { 40 | Transaction toIgnore = new TransactionBuilder().build(); 41 | waitUntilCommunicationEnds(100); 42 | Assert.assertNull("Spam protection failed: transaction passed.", receiver.findTransactionByHash(toIgnore.hash)); 43 | } 44 | 45 | 46 | @Test 47 | public void testMWM() { 48 | TransactionBuilder builder = new TransactionBuilder(); 49 | Set transactionsWithoutMWM = new HashSet<>(); 50 | for(int i = 0; i < 10; i++) 51 | transactionsWithoutMWM.add(builder.buildWhileUpdatingTimestamp()); 52 | 53 | Ict a = createIct(); 54 | Ict b = createIct(); 55 | connect(a, b); 56 | 57 | Constants.RUN_MODUS = Constants.RunModus.TESTING_BUT_WITH_REAL_MWM; 58 | 59 | for(Transaction t : transactionsWithoutMWM) 60 | a.submit(t); 61 | waitUntilCommunicationEnds(200); 62 | 63 | Constants.RUN_MODUS = Constants.RunModus.TESTING; 64 | 65 | Stats statsForA = b.getNeighbors().get(0).getStats(); 66 | Assert.assertTrue("Ict accepted transactions not satisfying MWM ("+statsForA.invalid+"/"+transactionsWithoutMWM.size()+" recognized as invalid).", statsForA.invalid > transactionsWithoutMWM.size() * 0.7); 67 | } 68 | } -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/network/TimestampTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.network; 2 | 3 | import org.iota.ict.Ict; 4 | import org.iota.ict.model.transaction.TransactionBuilder; 5 | import org.iota.ict.utils.Constants; 6 | import org.iota.ict.utils.Stats; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | 10 | public class TimestampTest extends GossipTest { 11 | 12 | @Test 13 | public void testTimestampDiffTolerance() { 14 | TransactionBuilder builder = new TransactionBuilder(); 15 | builder.issuanceTimestamp = System.currentTimeMillis() - (long) (Constants.TIMESTAMP_DIFFERENCE_TOLERANCE_IN_MILLIS * 1.2); 16 | 17 | Ict a = createIct(); 18 | Ict b = createIct(); 19 | connect(a, b); 20 | 21 | a.submit(builder.build()); 22 | waitUntilCommunicationEnds(100); 23 | Stats statsForA = b.getNeighbors().get(0).getStats(); 24 | Assert.assertEquals("Ict did forward transaction with timestamp out of tolerated time interval.", 0, statsForA.receivedAll); 25 | 26 | builder.issuanceTimestamp = System.currentTimeMillis() - (long) (Constants.TIMESTAMP_DIFFERENCE_TOLERANCE_IN_MILLIS * 0.8); 27 | a.submit(builder.build()); 28 | waitUntilCommunicationEnds(100); 29 | Assert.assertEquals("Ict did not receive transaction.", 1, statsForA.receivedAll); 30 | Assert.assertEquals("Ict rejected transaction with timestamp in tolerated time interval.", 0, statsForA.invalid); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/network/TransactionRequestTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.network; 2 | 3 | import org.iota.ict.Ict; 4 | import org.iota.ict.model.tangle.Tangle; 5 | import org.iota.ict.model.transaction.Transaction; 6 | import org.iota.ict.model.transaction.TransactionBuilder; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | 10 | public class TransactionRequestTest extends GossipTest { 11 | 12 | @Test 13 | public void testRequestingOverNewTransaction() { 14 | Ict a = createIct(); 15 | Ict b = createIct(); 16 | 17 | connect(a, b); 18 | 19 | Transaction original = submitTransactionAndReturnTransaction(a); 20 | waitUntilCommunicationEnds(100); 21 | Assert.assertNotNull("did not receive transaction", b.findTransactionByHash(original.hash)); 22 | 23 | // scenario: existing ict forgets transaction 24 | a.getTangle().deleteTransaction(original); 25 | Assert.assertNull("did not delete original transaction", a.findTransactionByHash(original.hash)); 26 | requestTransaction(a, original.hash); 27 | Assert.assertNotNull("could not request transaction from neighbor", a.findTransactionByHash(original.hash)); 28 | 29 | // scenario: new ict joins 30 | Ict c = createIct(); 31 | connect(a, c); 32 | Assert.assertNull("did not delete original transaction", c.findTransactionByHash(original.hash)); 33 | requestTransaction(c, original.hash); 34 | Assert.assertNotNull("could not request transaction from neighbor", c.findTransactionByHash(original.hash)); 35 | } 36 | 37 | @Test 38 | public void testRequestingOverOldTransaction() { 39 | Ict a = createIct(); 40 | Ict b = createIct(); 41 | 42 | connect(a, b); 43 | 44 | String oldTransactionHash = submitTransactionAndReturnTransaction(a).hash; 45 | Transaction original = submitTransactionAndReturnTransaction(a); 46 | waitUntilCommunicationEnds(100); 47 | 48 | // === scenario: existing ict forgets transaction ==== 49 | a.getTangle().deleteTransaction(original); 50 | Assert.assertNull("did not delete original transaction", a.findTransactionByHash(original.hash)); 51 | requestTransactionByRebroadcast(a, original.hash, oldTransactionHash); 52 | Assert.assertNotNull("could not request transaction from neighbor", a.findTransactionByHash(original.hash)); 53 | 54 | // === scenario: new ict joins === 55 | Ict c = createIct(); 56 | connect(a, c); 57 | 58 | // old (already known) transaction acting as carrier for c's request which can't use oldTransaction1 because c hasn't received it either 59 | String anotherOldTransactionHash = submitTransactionAndReturnTransaction(a).hash; 60 | waitUntilCommunicationEnds(100); 61 | 62 | Assert.assertNull("did not delete original transaction", c.findTransactionByHash(original.hash)); 63 | requestTransactionByRebroadcast(c, original.hash, anotherOldTransactionHash); 64 | Assert.assertNotNull("could not request transaction from neighbor", c.findTransactionByHash(original.hash)); 65 | } 66 | 67 | private void requestTransaction(Ict ict, String hash) { 68 | ict.request(hash); 69 | ict.submit(new TransactionBuilder().build()); // request carrier 70 | waitUntilCommunicationEnds(100); 71 | } 72 | 73 | private void requestTransactionByRebroadcast(Ict sender, String hash, String carrierHash) { 74 | sender.request(hash); 75 | Transaction carrier = sender.findTransactionByHash(carrierHash); 76 | Tangle.TransactionLog log = sender.getTangle().findTransactionLog(carrier); 77 | log.senders.removeAll(log.senders); 78 | sender.broadcast(carrier); 79 | waitUntilCommunicationEnds(100); 80 | } 81 | 82 | private static Transaction submitTransactionAndReturnTransaction(Ict sender) { 83 | Transaction transaction = new TransactionBuilder().build(); 84 | sender.submit(transaction); 85 | return transaction; 86 | } 87 | } -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/network/gossip/GossipEventDispatcherTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.network.gossip; 2 | 3 | import org.iota.ict.Ict; 4 | import org.iota.ict.IctTestTemplate; 5 | import org.iota.ict.eee.dispatch.ThreadedEffectDispatcherWithChainSupport; 6 | import org.iota.ict.model.transaction.Transaction; 7 | import org.iota.ict.model.transaction.TransactionBuilder; 8 | import org.iota.ict.utils.Constants; 9 | import org.iota.ict.utils.IssueCollector; 10 | import org.junit.Assert; 11 | import org.junit.Test; 12 | 13 | public class GossipEventDispatcherTest extends IctTestTemplate { 14 | 15 | private boolean eventReceived; 16 | 17 | @Test 18 | public void testListenersCanNotBlock() { 19 | Ict ict = createIct(); 20 | 21 | eventReceived = false; 22 | long start = System.currentTimeMillis(); 23 | 24 | ict.addListener(new GossipListener.Implementation() { 25 | @Override 26 | public void onReceive(GossipEvent e) { 27 | eventReceived = true; 28 | // try to block for a few second 29 | saveSleep(5000); 30 | } 31 | }); 32 | 33 | ict.submit(new TransactionBuilder().build()); 34 | saveSleep(100); 35 | Assert.assertTrue("gossip not delivered to gossip listener", eventReceived); 36 | 37 | long duration = System.currentTimeMillis() - start; 38 | Assert.assertTrue("gossip listener blocked main thread", duration < 2000); 39 | } 40 | 41 | @Test 42 | public void testCatchExceptionFromListener() { 43 | 44 | Thread.UncaughtExceptionHandler handlerBefore = Thread.getDefaultUncaughtExceptionHandler(); 45 | 46 | Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { 47 | @Override 48 | public void uncaughtException(Thread thread, Throwable throwable) { 49 | IssueCollector.collect(throwable); 50 | throwable.printStackTrace(); 51 | } 52 | }); 53 | 54 | final ThreadedEffectDispatcherWithChainSupport threadedEffectDispatcher = new ThreadedEffectDispatcherWithChainSupport(); 55 | 56 | threadedEffectDispatcher.addListener(new GossipListener.Implementation() { 57 | @Override 58 | public void onReceive(GossipEvent e) { 59 | throw new RuntimeException(); 60 | } 61 | }); 62 | threadedEffectDispatcher.start(); 63 | threadedEffectDispatcher.submitEffect(Constants.Environments.GOSSIP, new GossipEvent(Transaction.NULL_TRANSACTION, false)); 64 | 65 | saveSleep(20); 66 | 67 | Thread.setDefaultUncaughtExceptionHandler(handlerBefore); 68 | 69 | IssueCollector.log(); 70 | Assert.assertEquals("There were uncatched exceptions", 0, IssueCollector.amountOfIndicidents()); 71 | } 72 | } -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/network/gossip/GossipPreprocessorTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.network.gossip; 2 | 3 | import org.iota.ict.Ict; 4 | import org.iota.ict.IctTestTemplate; 5 | import org.iota.ict.model.transaction.TransactionBuilder; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | 9 | public class GossipPreprocessorTest extends IctTestTemplate { 10 | 11 | @Test 12 | public void testInsertion() { 13 | Ict ict = createIct(); 14 | 15 | GossipPreprocessor preprocessor1 = new GossipPreprocessor(ict,1); 16 | GossipPreprocessor preprocessor2 = new GossipPreprocessor(ict,2); 17 | CustomGossipListener gossipListener = new CustomGossipListener(); 18 | 19 | ict.addListener(gossipListener); 20 | ict.addListener(preprocessor1); 21 | ict.addListener(preprocessor2); 22 | 23 | ict.submit(new TransactionBuilder().build()); 24 | saveSleep(50); 25 | 26 | GossipEvent effect = preprocessor1.pollEffect(); 27 | Assert.assertNotNull(effect); 28 | Assert.assertNull(preprocessor2.pollEffect()); 29 | Assert.assertNull(gossipListener.lastEvent); 30 | 31 | preprocessor1.passOn(effect); 32 | saveSleep(50); 33 | 34 | Assert.assertNull(preprocessor1.pollEffect()); 35 | effect = preprocessor2.pollEffect(); 36 | Assert.assertNotNull(effect); 37 | Assert.assertNull(gossipListener.lastEvent); 38 | 39 | preprocessor2.passOn(effect); 40 | saveSleep(50); 41 | 42 | Assert.assertNull(preprocessor1.pollEffect()); 43 | Assert.assertNull(preprocessor2.pollEffect()); 44 | Assert.assertNotNull(gossipListener.lastEvent); 45 | } 46 | 47 | private class CustomGossipListener extends GossipListener.Implementation { 48 | 49 | private GossipEvent lastEvent; 50 | 51 | @Override 52 | public void onReceive(GossipEvent event) { 53 | lastEvent = event; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/std/BundleCollectorTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.std; 2 | 3 | import org.iota.ict.Ict; 4 | import org.iota.ict.IctTestTemplate; 5 | import org.iota.ict.eee.Environment; 6 | import org.iota.ict.model.bundle.Bundle; 7 | import org.iota.ict.model.bundle.BundleBuilder; 8 | import org.iota.ict.model.transaction.Transaction; 9 | import org.iota.ict.model.transaction.TransactionBuilder; 10 | import org.iota.ict.network.gossip.GossipEvent; 11 | import org.iota.ict.network.gossip.GossipListener; 12 | import org.iota.ict.utils.Constants; 13 | import org.junit.Assert; 14 | import org.junit.Test; 15 | 16 | import java.util.Collections; 17 | import java.util.LinkedList; 18 | import java.util.List; 19 | 20 | public class BundleCollectorTest extends IctTestTemplate { 21 | 22 | @Test 23 | public void testCollectionOfCompleteBundle() { 24 | Ict ictA = createIct(); 25 | Ict ictB = createIct(); 26 | connect(ictA, ictB); 27 | CustomGossipListener customGossipListener = new CustomGossipListener(); 28 | ictB.addListener(customGossipListener); 29 | List bundles = new LinkedList<>(); 30 | 31 | int amountOfTests = 100; 32 | for(int i = 0; i < amountOfTests; i++) { 33 | Bundle bundle = createBundleOfRandomLength(); 34 | bundles.add(bundle); 35 | submiTransactionsInRandomOrder(ictA, bundle.getTransactions()); 36 | assertBundleComplete(ictA, bundle.getHead().hash); 37 | } 38 | waitUntilCommunicationEnds(500); 39 | 40 | for(Bundle bundle : bundles) 41 | assertBundleComplete(ictB, bundle.getHead().hash); 42 | Assert.assertEquals("Ict did not receive expected amount of bundle heads.", amountOfTests, customGossipListener.receivedHeadEvents.size()); 43 | } 44 | 45 | @Test 46 | public void testIncompleteBundleWillNotPass() { 47 | Ict ictA = createIct(); 48 | Ict ictB = createIct(); 49 | connect(ictA, ictB); 50 | CustomGossipListener customGossipListener = new CustomGossipListener(); 51 | ictB.addListener(customGossipListener); 52 | 53 | for(int amountOfTests = 100; amountOfTests > 0; amountOfTests--) { 54 | Bundle bundle = createBundleOfRandomLength(); 55 | submitTransactionsPartially(ictA, bundle.getTransactions()); 56 | } 57 | 58 | waitUntilCommunicationEnds(500); 59 | 60 | Assert.assertEquals("Ict received events from incomplete bundles.", 0, customGossipListener.receivedHeadEvents.size()); 61 | } 62 | 63 | private static void assertBundleComplete(Ict ict, String hashOfBundleHead) { 64 | Transaction head = ict.findTransactionByHash(hashOfBundleHead); 65 | Assert.assertNotNull("Ict did not receive head of bundle", head); 66 | Bundle bundle = new Bundle(head); 67 | Assert.assertTrue("Ict did not receive complete bundle", bundle.isComplete()); 68 | } 69 | 70 | private static void submitTransactionsPartially(Ict ict, List transactions) { 71 | transactions.remove((int)(Math.random() * transactions.size())); 72 | submiTransactionsInRandomOrder(ict, transactions); 73 | } 74 | 75 | private static void submiTransactionsInRandomOrder(Ict ict, List transactions) { 76 | Collections.shuffle(transactions); 77 | for(Transaction transaction : transactions) { 78 | ict.submit(transaction); 79 | } 80 | } 81 | 82 | private static Bundle createBundleOfRandomLength() { 83 | return createBundle((int)(Math.random() * 5)+1); 84 | } 85 | 86 | private static Bundle createBundle(int length) { 87 | BundleBuilder bundleBuilder = new BundleBuilder(); 88 | while (length-- > 0) 89 | bundleBuilder.append(new TransactionBuilder()); 90 | return bundleBuilder.build(); 91 | } 92 | 93 | private static class CustomGossipListener extends GossipListener.Implementation { 94 | 95 | private List receivedHeadEvents = new LinkedList<>(); 96 | 97 | @Override 98 | public void onReceive(GossipEvent event) { 99 | if(!event.isOwnTransaction() && event.getTransaction().isBundleHead) 100 | receivedHeadEvents.add(event); 101 | } 102 | 103 | @Override 104 | public Environment getEnvironment() { 105 | return Constants.Environments.GOSSIP; 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/utils/RestartableThreadTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class RestartableThreadTest { 7 | 8 | @Test 9 | public void testInterfaceCalls() { 10 | Worker underTest = new Worker(); 11 | 12 | underTest.assertCalls(false, false, false); 13 | for(int i = 0; i < 5; i++) { 14 | underTest.resetCalls(); 15 | underTest.start(); 16 | safeSleep(10); // allow thread to call run() 17 | underTest.assertCalls(true, true, false); 18 | 19 | underTest.resetCalls(); 20 | underTest.terminate(); 21 | underTest.assertCalls(false, false, true); 22 | } 23 | } 24 | 25 | @Test 26 | public void testSubWorkerInterfaceCalls() { 27 | Worker superWorker = new Worker(); 28 | Worker subWorker = new Worker(); 29 | Worker subSubWorker = new Worker(); 30 | superWorker.subWorkers.add(subWorker); 31 | subWorker.subWorkers.add(subSubWorker); 32 | 33 | subSubWorker.assertCalls(false, false, false); 34 | for(int i = 0; i < 5; i++) { 35 | subSubWorker.resetCalls(); 36 | superWorker.start(); 37 | safeSleep(10); // allow thread to call run() 38 | subSubWorker.assertCalls(true, true, false); 39 | 40 | subSubWorker.resetCalls(); 41 | superWorker.terminate(); 42 | subSubWorker.assertCalls(false, false, true); 43 | } 44 | } 45 | 46 | @Test(expected = IllegalStateException.class) 47 | public void testCallTerminateBeforeStarting() { 48 | Worker underTest = new Worker(); 49 | underTest.terminate(); 50 | } 51 | 52 | @Test(expected = IllegalStateException.class) 53 | public void testStartingWhileAlreadyRunning() { 54 | Worker underTest = new Worker(); 55 | underTest.start(); 56 | underTest.start(); 57 | } 58 | 59 | private static void safeSleep(long ms) { 60 | try { 61 | Thread.sleep(ms); 62 | } catch (InterruptedException e) { 63 | throw new RuntimeException(e); 64 | } 65 | } 66 | } 67 | 68 | class Worker extends RestartableThread { 69 | 70 | Worker() { 71 | super(null); 72 | } 73 | 74 | private boolean called_run = false; 75 | private boolean called_onStart = false; 76 | private boolean called_onTerminate = false; 77 | 78 | void resetCalls() { 79 | called_run = false; 80 | called_onStart = false; 81 | called_onTerminate = false; 82 | } 83 | 84 | void assertCalls(boolean expected_called_run, boolean expected_called_onStart, boolean expected_called_onTerminate) { 85 | Assert.assertEquals(expected_called_run, called_run); 86 | Assert.assertEquals(expected_called_onStart, called_onStart); 87 | Assert.assertEquals(expected_called_onTerminate, called_onTerminate); 88 | } 89 | 90 | @Override 91 | public void run() { 92 | called_run = true; 93 | } 94 | 95 | @Override 96 | public void onStart() { 97 | called_onStart = true; 98 | } 99 | 100 | @Override 101 | public void onTerminate() { 102 | called_onTerminate = true; 103 | } 104 | } -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/utils/TrytesTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import java.math.BigInteger; 7 | 8 | public class TrytesTest { 9 | @Test 10 | public void testNumberEncoding() { 11 | for (int i = -Trytes.MAX_TRYTE_TRIPLET_ABS; i <= Trytes.MAX_TRYTE_TRIPLET_ABS; i += 30 * Math.random()) 12 | Assert.assertEquals(i, Trytes.toNumber(Trytes.fromNumber(BigInteger.valueOf(i), 3)).intValue()); 13 | } 14 | 15 | @Test 16 | public void testPadding() { 17 | for (int i = 0; i < 1000; i++) { 18 | int contentLength = (int) (Math.random() * 20) + 1; 19 | int padLength = (int) (Math.random() * 10); 20 | String raw = Trytes.randomSequenceOfLength(contentLength - 1); 21 | raw += "A"; // make sure last tryte is not 9 22 | Assert.assertEquals(raw, Trytes.unpadRight(Trytes.padRight(raw, contentLength + padLength))); 23 | } 24 | } 25 | 26 | @Test 27 | public void testAsciiEncoding() { 28 | Trytes.fromAscii(""); 29 | long a = System.currentTimeMillis(); 30 | for (int i = 0; i < 1000; i++) { 31 | String tryteTriplet = Trytes.randomSequenceOfLength(3); 32 | Assert.assertEquals(tryteTriplet.equals("999") ? "" : tryteTriplet, Trytes.fromAscii(Trytes.toAscii(tryteTriplet))); 33 | } 34 | System.out.println(System.currentTimeMillis() - a); 35 | } 36 | 37 | @Test 38 | public void testByteEncoding() { 39 | String trytes = Trytes.randomSequenceOfLength(3 * (int) (10 * Math.random())); 40 | byte[] bytes = Trytes.toBytes(trytes); 41 | Assert.assertEquals(bytes.length, 2 * (int) Math.ceil(trytes.length() / 3.0)); 42 | Assert.assertEquals("decoded bytes did not result in original trytes", trytes, Trytes.fromBytes(bytes, 0, bytes.length)); 43 | } 44 | } -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/utils/VersionComparatorTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils; 2 | 3 | import junitparams.FileParameters; 4 | import junitparams.JUnitParamsRunner; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | 9 | @RunWith(JUnitParamsRunner.class) 10 | public class VersionComparatorTest { 11 | 12 | @Test 13 | @FileParameters("src/test/resources/utils.VersionComparator_data.csv") 14 | public void testComparison(String version1, String version2, int expected) { 15 | VersionComparator underTest = new VersionComparator(); 16 | 17 | int actual = underTest.compare(version1, version2); 18 | int actualInverse = underTest.compare(version2, version1); 19 | 20 | Assert.assertEquals("Failed comparing version numbers.", expected, actual); 21 | Assert.assertEquals("Failed comparing version numbers.", -expected, actualInverse); 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/utils/crypto/MerkleTreeTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils.crypto; 2 | 3 | import org.iota.ict.utils.Trytes; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | 7 | public class MerkleTreeTest { 8 | 9 | // tree generation takes long -> use one tree for all tests 10 | private static final MerkleTree merkleTree = new MerkleTree(Trytes.randomSequenceOfLength(81), 3, 3); 11 | 12 | @Test 13 | public void testSignatureVerification() { 14 | String toSign = Trytes.randomSequenceOfLength(81); 15 | int randomIndex = (int)(Math.random()*Math.pow(2, merkleTree.getDepth())); 16 | MerkleTree.Signature signature = merkleTree.sign(randomIndex, toSign); 17 | 18 | String addressOfMerkleTree = merkleTree.getAddress(); 19 | String addressOfSignature = signature.deriveAddress(); 20 | 21 | Assert.assertEquals("Signature validation failed.", addressOfMerkleTree, addressOfSignature); 22 | Assert.assertEquals("Signature index derived incorrectly.", randomIndex, signature.deriveIndex()); 23 | } 24 | 25 | @Test 26 | public void testSignatureFromTrytesConcatenatedWithMerklePath() { 27 | String toSign = Trytes.randomSequenceOfLength(81); 28 | int randomIndex = (int)(Math.random()*Math.pow(2, merkleTree.getDepth())); 29 | MerkleTree.Signature signature = merkleTree.sign(randomIndex, toSign); 30 | 31 | MerkleTree.Signature splitSignature = MerkleTree.Signature.fromTrytesConcatenatedWithMerklePath(signature.toString(), toSign); 32 | 33 | String addressOfMerkleTree = merkleTree.getAddress(); 34 | String addressOfSplitSignature = splitSignature.deriveAddress(); 35 | 36 | Assert.assertEquals("Concatenated signature split failed.", addressOfMerkleTree, addressOfSplitSignature); 37 | } 38 | } -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/utils/crypto/SignatureSchemeImplementationTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils.crypto; 2 | 3 | import org.iota.ict.utils.Trytes; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | 7 | public class SignatureSchemeImplementationTest { 8 | 9 | 10 | @Test 11 | public void testPublicKey() { 12 | 13 | String seed = Trytes.randomSequenceOfLength(81); 14 | int securityLevel = (int)(Math.random() * 3)+1; 15 | String toSign = Trytes.randomSequenceOfLength(27 * securityLevel); 16 | 17 | SignatureSchemeImplementation.PrivateKey privateKey = SignatureSchemeImplementation.derivePrivateKeyFromSeed(seed, 0, securityLevel); 18 | SignatureSchemeImplementation.PublicKey publicKey = privateKey.derivePublicKey(); 19 | 20 | SignatureSchemeImplementation.Signature signature = privateKey.sign(toSign); 21 | Assert.assertEquals("Public key of signature derived incorrectly.", publicKey, signature.derivePublicKey()); 22 | } 23 | 24 | 25 | @Test 26 | public void testSignature() { 27 | 28 | String seed = Trytes.randomSequenceOfLength(81); 29 | int securityLevel = (int)(Math.random() * 3)+1; 30 | 31 | SignatureSchemeImplementation.PrivateKey privateKey = SignatureSchemeImplementation.derivePrivateKeyFromSeed(seed, 0, securityLevel); 32 | String addressOfPrivateKey = privateKey.deriveAddress(); 33 | String toSign = Trytes.randomSequenceOfLength(27 * securityLevel); 34 | 35 | SignatureSchemeImplementation.Signature signature = privateKey.sign(toSign); 36 | Assert.assertEquals("Address of signature derived incorrectly.", addressOfPrivateKey, signature.deriveAddress()); 37 | } 38 | } -------------------------------------------------------------------------------- /src/test/java/org/iota/ict/utils/properties/PropertiesTest.java: -------------------------------------------------------------------------------- 1 | package org.iota.ict.utils.properties; 2 | 3 | import org.iota.ict.utils.Trytes; 4 | import org.iota.ict.utils.properties.Properties; 5 | import org.json.JSONObject; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | 9 | import java.net.InetSocketAddress; 10 | 11 | public class PropertiesTest { 12 | 13 | 14 | @Test 15 | public void when_encode_and_decode_as_json_then_keep_properties() { 16 | // given 17 | Properties original = createRandomProperties(); 18 | 19 | // when 20 | JSONObject encoded = original.toJSON(); 21 | Properties decoded = Properties.fromJSON(encoded); 22 | 23 | // then 24 | Assert.assertEquals("JSON encoding-decoding results in different properties.", original, decoded); 25 | } 26 | 27 | private static Properties createRandomProperties() { 28 | Properties properties = new Properties(); 29 | properties.port = 1 + (int)(Math.random() * 10000); 30 | properties.guiEnabled = Math.random() < 0.5; 31 | properties.name = Trytes.randomSequenceOfLength(10); 32 | properties.neighbors.add("example.org:1337"); 33 | properties.neighbors.add("123.4.5.678:14265"); 34 | return properties; 35 | } 36 | } -------------------------------------------------------------------------------- /src/test/resources/utils.VersionComparator_data.csv: -------------------------------------------------------------------------------- 1 | 0.0.0,0.0,0 2 | 1.0.0,1.0,0 3 | 1.1.0,1.1,0 4 | 0.0,0,0 5 | 1.0,1,0 6 | 0,0,0 7 | 1,1,0 8 | 0.0.1,0.0.0,1 9 | 0.1.0,0.0.0,1 10 | 1.0.0,0.0.0,1 11 | 0.1.0,0.0.1,1 12 | 0.3.0,0.2.0,1 13 | 0.2.0,0.0.3,1 14 | 0.1.1,0.1,1 15 | 0.1.10,0.1.2,1 16 | 1,0.1-SNAPSHOT,1 17 | 0.1,0.1-SNAPSHOT,1 18 | 0.2-SNAPSHOT,0.1.3,1 19 | 0.4,0.4,0 -------------------------------------------------------------------------------- /version.updater.json: -------------------------------------------------------------------------------- 1 | {"version":"0.6","dependencies":["java","node"],"build-dependencies":["gradle"]} 2 | -------------------------------------------------------------------------------- /web/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "browsers": ["last 2 Chrome versions"] 8 | } 9 | } 10 | ], 11 | "@babel/preset-react" 12 | ], 13 | "plugins": [["@babel/plugin-proposal-class-properties", { "loose": false }]] 14 | } 15 | -------------------------------------------------------------------------------- /web/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb", "prettier"], 3 | "parser": "babel-eslint", 4 | "rules": { 5 | "indent": ["error", "tab", { "SwitchCase": 1 }], 6 | "no-tabs": ["error", { "allowIndentationTabs": true }], 7 | "react/jsx-indent-props": ["error", "tab"], 8 | "react/jsx-indent": ["error", "tab"], 9 | "max-len": ["error", { "code": 140, "ignoreStrings": true }], 10 | "react/jsx-one-expression-per-line": "off", 11 | "jsx-a11y/click-events-have-key-events": "off", 12 | "jsx-a11y/no-noninteractive-element-interactions": "off", 13 | "camelcase": ["error", { "allow": ["^default_config", "gui_port", "user_slash_repo"] }], 14 | "react/forbid-prop-types": "off" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .cache 4 | !.babelrc 5 | !.eslintrc 6 | !.prettierrc -------------------------------------------------------------------------------- /web/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "arrowParens": "always", 4 | "bracketSpacing": true, 5 | "singleQuote": true, 6 | "printWidth": 120 7 | } 8 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ict", 3 | "version": "0.5.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "parcel src/index.html --out-dir=dist", 8 | "build": "parcel build src/index.html --no-source-maps --out-dir=dist", 9 | "lint": "eslint src/**/*.jsx" 10 | }, 11 | "author": "Rihards Gravis ", 12 | "license": "Apache-2.0", 13 | "dependencies": { 14 | "js-cookie": "^2.2.0", 15 | "prop-types": "^15.7.2", 16 | "react": "^16.8.6", 17 | "react-dom": "^16.8.6", 18 | "react-highlight": "^0.12.0", 19 | "react-highlight.js": "^1.0.7", 20 | "react-infinite-scroller": "^1.2.4", 21 | "react-router-dom": "^5.0.0", 22 | "recharts": "^1.5.0" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "^7.4.4", 26 | "@babel/plugin-proposal-class-properties": "^7.4.4", 27 | "@babel/preset-react": "^7.0.0", 28 | "babel-core": "^7.0.0-bridge.0", 29 | "babel-eslint": "^10.0.1", 30 | "babel-plugin-transform-runtime": "^6.23.0", 31 | "babel-preset-env": "^1.7.0", 32 | "babel-runtime": "^6.26.0", 33 | "eslint": "^5.16.0", 34 | "eslint-config-airbnb": "^17.1.0", 35 | "eslint-config-prettier": "^4.2.0", 36 | "eslint-plugin-import": "^2.17.2", 37 | "eslint-plugin-jsx-a11y": "^6.2.1", 38 | "eslint-plugin-react": "^7.12.4", 39 | "parcel-bundler": "^1.12.3", 40 | "sass": "^1.19.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /web/src/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/ict/3bb009c3fd0088943ab9b810c69a76097f9fb6ae/web/src/assets/favicon.png -------------------------------------------------------------------------------- /web/src/assets/ict-logo-outline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/Card.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Card = ({ title, children, columns }) => ( 5 |

6 |

{title}

7 |
{children}
8 |
9 | ); 10 | 11 | Card.propTypes = { 12 | title: PropTypes.string, 13 | children: PropTypes.node.isRequired, 14 | columns: PropTypes.bool 15 | }; 16 | 17 | Card.defaultProps = { 18 | title: '', 19 | columns: false 20 | }; 21 | 22 | export default Card; 23 | -------------------------------------------------------------------------------- /web/src/components/Confirm.jsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/ict/3bb009c3fd0088943ab9b810c69a76097f9fb6ae/web/src/components/Confirm.jsx -------------------------------------------------------------------------------- /web/src/components/Popup.jsx: -------------------------------------------------------------------------------- 1 | /* global window */ 2 | import React, { PureComponent } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import Card from './Card'; 6 | import Icon from './Icon'; 7 | 8 | class Popup extends PureComponent { 9 | static propTypes = { 10 | title: PropTypes.string, 11 | cta: PropTypes.string, 12 | loading: PropTypes.bool, 13 | type: PropTypes.oneOf(['warning', 'success']), 14 | children: PropTypes.node.isRequired, 15 | onClose: PropTypes.func, 16 | onConfirm: PropTypes.func 17 | }; 18 | 19 | static defaultProps = { 20 | title: '', 21 | cta: 'Yes', 22 | loading: false, 23 | type: 'success', 24 | onClose: () => {}, 25 | onConfirm: null 26 | }; 27 | 28 | componentDidMount() { 29 | window.addEventListener('keydown', this.onKeyDown, false); 30 | } 31 | 32 | componentWillUnmount() { 33 | window.removeEventListener('keydown', this.onKeyDown, false); 34 | } 35 | 36 | onKeyDown = (e) => { 37 | const { onClose } = this.props; 38 | 39 | if (e.key === 'Escape' && onClose) { 40 | onClose(); 41 | } 42 | }; 43 | 44 | render() { 45 | const { children, title, onClose, onConfirm, type, cta, loading } = this.props; 46 | 47 | return ( 48 |
49 |
50 | 51 | {onClose && ( 52 | 57 | )} 58 | {children} 59 | {onConfirm && ( 60 |
61 | 64 | 67 |
68 | )} 69 |
70 |
71 |
72 | ); 73 | } 74 | } 75 | 76 | export default Popup; 77 | -------------------------------------------------------------------------------- /web/src/components/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { NavLink } from 'react-router-dom'; 4 | 5 | import Icon from './Icon'; 6 | 7 | import Logo from '../assets/ict-logo.svg'; 8 | 9 | const Link = ({ to, label }) => ( 10 | 11 | 12 | {label} 13 | 14 | ); 15 | 16 | Link.propTypes = { 17 | to: PropTypes.string.isRequired, 18 | label: PropTypes.string.isRequired 19 | }; 20 | 21 | const Sidebar = ({ modules }) => { 22 | const [active, setActive] = useState(false); 23 | 24 | return ( 25 | 49 | ); 50 | }; 51 | 52 | Sidebar.propTypes = { 53 | modules: PropTypes.array.isRequired 54 | }; 55 | 56 | export default Sidebar; 57 | -------------------------------------------------------------------------------- /web/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | lct 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /web/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { PureComponent, Fragment } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Switch, Route } from 'react-router'; 4 | import { BrowserRouter as Router } from 'react-router-dom'; 5 | import Cookies from 'js-cookie'; 6 | 7 | import { get } from './lib/api'; 8 | 9 | import Sidebar from './components/Sidebar'; 10 | import Popup from './components/Popup'; 11 | 12 | import Home from './pages/Home'; 13 | import Neighbors from './pages/Neighbors'; 14 | import Configuration from './pages/Configuration'; 15 | import Log from './pages/Log'; 16 | import Modules from './pages/Modules'; 17 | import Module from './pages/Module'; 18 | 19 | import './style/index.scss'; 20 | 21 | class Main extends PureComponent { 22 | state = { 23 | authorised: null, 24 | loading: false, 25 | password: '', 26 | modules: [] 27 | }; 28 | 29 | componentDidMount() { 30 | this.authorise(); 31 | this.updateModules(); 32 | } 33 | 34 | updateModules = async () => { 35 | const { modules } = await get('modules'); 36 | if (modules) { 37 | this.setState({ modules }); 38 | } 39 | }; 40 | 41 | authorise = async (e) => { 42 | const { password, authorised } = this.state; 43 | 44 | if (e) { 45 | e.preventDefault(); 46 | } 47 | 48 | this.setState({ 49 | loading: true 50 | }); 51 | 52 | const { modules, error } = await get('modules', password); 53 | 54 | this.setState({ 55 | loading: false 56 | }); 57 | 58 | if (error && authorised) { 59 | this.setState({ 60 | authorised: error 61 | }); 62 | } else if (modules) { 63 | this.setState({ 64 | authorised: 'authorised' 65 | }); 66 | if (password.length) { 67 | Cookies.set('password', password); 68 | } 69 | } else { 70 | this.setState({ 71 | authorised: 'init' 72 | }); 73 | } 74 | }; 75 | 76 | render() { 77 | const { authorised, password, loading, modules } = this.state; 78 | 79 | if (!authorised) return null; 80 | 81 | const renderMergedProps = (component, ...rest) => { 82 | const finalProps = Object.assign({}, ...rest); 83 | return React.createElement(component, finalProps); 84 | }; 85 | 86 | const ModulesRoute = ({ component, ...rest }) => { 87 | return ( 88 | { 91 | return renderMergedProps(component, routeProps, rest); 92 | }} 93 | /> 94 | ); 95 | }; 96 | 97 | if (authorised !== 'authorised') { 98 | return ( 99 | 100 |
101 |
102 |

103 | The password is set in your ict.cfg. The default value is change_me_now 104 |

105 | 115 | {authorised !== 'init' && {authorised}} 116 |
117 |
118 | 119 |
120 |
121 |
122 | ); 123 | } 124 | 125 | return ( 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | ); 140 | } 141 | } 142 | 143 | ReactDOM.render(
, document.getElementById('app')); 144 | -------------------------------------------------------------------------------- /web/src/lib/api.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie'; 2 | 3 | export const get = async (route, password, params) => { 4 | try { 5 | const payload = params ? params : {}; 6 | payload.password = password || Cookies.get('password'); 7 | 8 | const body = stringify(payload); 9 | 10 | const response = await fetch(`/get${route.charAt(0).toUpperCase() + route.slice(1)}`, { 11 | headers: { 12 | 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' 13 | }, 14 | method: 'POST', 15 | body 16 | }); 17 | 18 | if (response.status === 401) { 19 | return { error: 'Incorrect password' }; 20 | } 21 | 22 | const data = await response.json(); 23 | 24 | return typeof data === 'object' && data.success ? data : []; 25 | } catch (err) { 26 | console.log(err); 27 | return []; 28 | } 29 | }; 30 | 31 | const stringify = (payload) => { 32 | return Object.entries(payload) 33 | .map(([key, value]) => { 34 | return encodeURIComponent(key) + '=' + encodeURIComponent(value); 35 | }) 36 | .join('&'); 37 | }; 38 | 39 | export const set = async (route, payload) => { 40 | try { 41 | payload.password = Cookies.get('password'); 42 | 43 | const body = stringify(payload); 44 | 45 | const response = await fetch(`/${route}`, { 46 | headers: { 47 | 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' 48 | }, 49 | method: 'POST', 50 | body 51 | }); 52 | 53 | const data = await response.json(); 54 | 55 | return data; 56 | } catch (err) { 57 | console.log(err); 58 | return []; 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /web/src/lib/configLabels.json: -------------------------------------------------------------------------------- 1 | { 2 | "General": [ 3 | { 4 | "label": "ICT Name", 5 | "name": "name" 6 | }, 7 | { 8 | "label": "Round duration (ms)", 9 | "name": "round_duration" 10 | }, 11 | { 12 | "label": "Tangle capacity", 13 | "name": "tangle_capacity" 14 | }, 15 | { 16 | "label": "Max heap size", 17 | "name": "max_heap_size" 18 | } 19 | ], 20 | "Address": [ 21 | { 22 | "label": "Host name", 23 | "name": "host" 24 | }, 25 | { 26 | "label": "Port", 27 | "name": "port" 28 | } 29 | ], 30 | "Gossip": [ 31 | { 32 | "label": "Min forward delay (ms)", 33 | "name": "min_forward_delay" 34 | }, 35 | { 36 | "label": "Max forward delay (ms)", 37 | "name": "max_forward_delay" 38 | }, 39 | { 40 | "label": "Max. Abs. Tx./Round", 41 | "name": "anti_spam_abs" 42 | } 43 | ], 44 | "Web access": [ 45 | { 46 | "label": "Web access enabled", 47 | "name": "gui_enabled" 48 | }, 49 | { 50 | "label": "Port", 51 | "name": "gui_port" 52 | }, 53 | { 54 | "label": "Password", 55 | "name": "gui_password" 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /web/src/lib/helpers.js: -------------------------------------------------------------------------------- 1 | export const toDate = (timestamp, long) => { 2 | const date = new Date(timestamp) 3 | .toLocaleString('en-GB', { hour12: false }) 4 | .replace(/\//g, '.') 5 | .replace(',', ''); 6 | return long ? date.substr(0, date.length - 3) : date.substr(-8, 5); 7 | }; 8 | 9 | export const moduleURI = (name, port) => { 10 | let url = `${window.location.protocol}//${window.location.hostname}`; 11 | return port ? `${url}:${port}/` : `${url}:${window.location.port}/modules/${name}/`; 12 | }; 13 | 14 | export const downloadFile = (fileName, content) => { 15 | var element = document.createElement('a'); 16 | element.setAttribute('href', 'data:text/plain;charset=utf-8,' + content); 17 | element.setAttribute('download', fileName); 18 | 19 | element.style.display = 'none'; 20 | document.body.appendChild(element); 21 | 22 | element.click(); 23 | 24 | document.body.removeChild(element); 25 | }; 26 | -------------------------------------------------------------------------------- /web/src/lib/modules.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "CHAT.ixi", 4 | "repo": "iotaledger/chat.ixi", 5 | "description": "CHAT.ixi is an IXI (IOTA eXtension Interface) module for the Iota Controlled agenT (Ict). It extends the functionality of the core Ict client with a chat application which allows users in the Ict network to exchange messages on a permissionless, distributed data-integrity protocol - the Tangle." 6 | }, 7 | { 8 | "name": "Economic Clustering", 9 | "repo": "iotaledger/ec.ixi", 10 | "description": "Economic Clustering (EC) is the consensus mechanism in the Ict network. This allows you to come to consensus with others on the confirmation state of transactions." 11 | }, 12 | { 13 | "name": "Bridge.ixi", 14 | "repo": "iotaledger/bridge.ixi", 15 | "description": "Bridge.ixi represents the interface which allows modules written in any programming language to interact with the underlying infrastructure. By giving everyone the opportunity to participate with their desired programming language, Bridge.ixi will help grow the module ecosystem." 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /web/src/pages/Configuration.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Cookies from 'js-cookie'; 3 | 4 | import Card from '../components/Card'; 5 | import Popup from '../components/Popup'; 6 | 7 | import { get, set } from '../lib/api'; 8 | import configLabels from '../lib/configLabels'; 9 | 10 | const defaultState = { 11 | config: {}, 12 | default_config: {}, 13 | loading: false, 14 | shouldConfirm: false, 15 | error: null 16 | }; 17 | 18 | class Configuration extends Component { 19 | state = Object.assign({}, defaultState); 20 | 21 | componentDidMount() { 22 | this.init(); 23 | } 24 | 25 | init = async () => { 26 | const config = await get('config'); 27 | delete config.success; 28 | 29 | const { default_config } = await get('info'); 30 | 31 | this.setState(Object.assign({}, defaultState, { config, default_config })); 32 | }; 33 | 34 | updateEntry = (name) => (e) => { 35 | const { config } = this.state; 36 | 37 | this.setState({ 38 | config: { 39 | ...config, 40 | [name]: e.target.value 41 | } 42 | }); 43 | }; 44 | 45 | saveConfig = async () => { 46 | const { config } = this.state; 47 | 48 | this.setState({ 49 | loading: true 50 | }); 51 | 52 | const { error } = await set('setConfig', { config: JSON.stringify(config) }); 53 | 54 | if (!error) { 55 | if (config.gui_password) { 56 | Cookies.set('password', config.gui_password); 57 | } 58 | this.init(); 59 | } else { 60 | this.setState({ 61 | error, 62 | loading: false 63 | }); 64 | } 65 | }; 66 | 67 | render() { 68 | const { config, default_config, loading, shouldConfirm, error } = this.state; 69 | 70 | return ( 71 |
72 |
73 |

74 | Configuration 75 | 87 |

88 | {shouldConfirm && ( 89 | this.setState({ shouldConfirm: null, error: null })} 95 | > 96 |

Save changes to configuration?

97 | {error && {error}} 98 |
99 | )} 100 |
101 | {Object.entries(configLabels).map(([title, items]) => ( 102 | 103 | {items.map( 104 | ({ name, label }) => 105 | name in config && ( 106 | 109 | ) 110 | )} 111 | 112 | ))} 113 |
114 |
115 |
116 | ); 117 | } 118 | } 119 | 120 | export default Configuration; 121 | -------------------------------------------------------------------------------- /web/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | 3 | import { get } from '../lib/api'; 4 | 5 | const Home = () => { 6 | const [version, setVersion] = useState(null); 7 | 8 | useEffect(() => { 9 | const getConfig = async () => { 10 | const info = await get('info'); 11 | setVersion(info.version); 12 | }; 13 | getConfig(); 14 | }, []); 15 | 16 | return ( 17 |
18 |
19 |

20 | IOTA Controlled Agent 21 | {version && ( 22 | 23 | version {version} 24 | 25 | )} 26 |

27 |
28 |
29 | ); 30 | }; 31 | 32 | export default Home; 33 | -------------------------------------------------------------------------------- /web/src/pages/Log.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import InfiniteScroll from 'react-infinite-scroller'; 3 | 4 | import Card from '../components/Card'; 5 | 6 | import { get } from '../lib/api'; 7 | import { toDate, downloadFile } from '../lib/helpers'; 8 | 9 | const PAGE_LENGTH = 40 10 | 11 | const Log = () => { 12 | const [logs, setLogs] = useState([]); 13 | const [offset, setOffset] = useState({ max: 0, min: 0 }); 14 | 15 | const getLogs = async () => { 16 | const params = 17 | offset.max > 0 18 | ? { 19 | min: offset.min + logs.length, 20 | max: Math.min(offset.max, offset.min + logs.length + PAGE_LENGTH) 21 | } 22 | : { 23 | min: -1, 24 | max: PAGE_LENGTH 25 | }; 26 | 27 | const response = await get('logs', null, params); 28 | 29 | setOffset({ max: response.max, min: response.min }); 30 | setLogs(logs.concat(response.logs)); 31 | }; 32 | 33 | useEffect(() => { 34 | getLogs(); 35 | }, []); 36 | 37 | const parseLog = () => { 38 | const log = logs.map(({ timestamp, message }) => `${toDate(timestamp, true)} ${message}`); 39 | return log.join('\n'); 40 | }; 41 | 42 | return ( 43 |
44 |
45 |

Log

46 | 47 | 56 | {logs.length > 0 && ( 57 | Loading ...

} 62 | > 63 | {logs.map(({ timestamp, level, message }, index) => ( 64 |

65 | {toDate(timestamp, true)} 66 | {message} 67 |

68 | ))} 69 |
70 | )} 71 |
72 |
73 |
74 | ); 75 | }; 76 | 77 | export default Log; 78 | -------------------------------------------------------------------------------- /web/src/style/components/_button.scss: -------------------------------------------------------------------------------- 1 | @keyframes rotate { 2 | 0% { 3 | transform: rotate(-45deg); 4 | } 5 | 100% { 6 | transform: rotate(315deg); 7 | } 8 | } 9 | 10 | @mixin loading() { 11 | &:before { 12 | content: ''; 13 | position: absolute; 14 | top: 0px; 15 | left: 0px; 16 | width: 100%; 17 | height: 100%; 18 | border-radius: 4px; 19 | background: inherit; 20 | } 21 | &:after { 22 | content: ''; 23 | position: absolute; 24 | top: 50%; 25 | left: 50%; 26 | width: 14px; 27 | height: 14px; 28 | margin: -7px 0 0 -7px; 29 | animation: rotate 0.8s infinite cubic-bezier(0.455, 0.03, 0.515, 0.955); 30 | border: 2px solid #fff; 31 | border-right-color: transparent; 32 | border-radius: 50%; 33 | } 34 | } 35 | 36 | .button { 37 | position: relative; 38 | display: inline-block; 39 | border-radius: 4px; 40 | background: $color-action; 41 | color: $color-body-button; 42 | font-size: 11px; 43 | font-weight: 200; 44 | line-height: 30px; 45 | min-width: 90px; 46 | padding: 0 20px; 47 | text-align: center; 48 | text-transform: uppercase; 49 | letter-spacing: 1.5px; 50 | cursor: pointer; 51 | 52 | &.small { 53 | line-height: 24px; 54 | } 55 | 56 | &:hover { 57 | opacity: 0.8; 58 | } 59 | 60 | &.active { 61 | opacity: 0.75; 62 | &:hover { 63 | opacity: 0.35; 64 | } 65 | } 66 | 67 | &.primary { 68 | background: $color-primary; 69 | } 70 | &.secondary { 71 | background: $color-secondary; 72 | } 73 | &.success { 74 | background: $color-success; 75 | } 76 | &.warning { 77 | background: $color-warning; 78 | } 79 | &.loading { 80 | @include loading(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /web/src/style/components/_card.scss: -------------------------------------------------------------------------------- 1 | @keyframes appear { 2 | 0% { 3 | transform: translate(0, 30px); 4 | opacity: 0; 5 | } 6 | 100% { 7 | transform: translate(0, 0); 8 | opacity: 1; 9 | } 10 | } 11 | 12 | .card { 13 | position: relative; 14 | background: $color-box-bg; 15 | margin-bottom: 40px; 16 | box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.1); 17 | animation: appear 0.4s cubic-bezier(0.23, 1, 0.32, 1); 18 | animation-fill-mode: forwards; 19 | opacity: 0; 20 | transform: translate(0, 30px); 21 | border-radius: 6px; 22 | 23 | @for $i from 2 through 20 { 24 | &:nth-of-type(#{$i}) { 25 | animation-delay: #{$i * 0.03}s; 26 | } 27 | } 28 | 29 | nav.corner { 30 | position: absolute; 31 | display: flex; 32 | align-items: center; 33 | top: 0px; 34 | right: 16px; 35 | height: 48px; 36 | } 37 | 38 | h2 { 39 | font-size: 16px; 40 | font-weight: 600; 41 | text-transform: uppercase; 42 | line-height: 53px; 43 | height: 49px; 44 | background: $color-box-header; 45 | padding: 0 28px; 46 | user-select: all; 47 | color: $color-primary; 48 | border-bottom: 1px solid $color-box-divider; 49 | border-radius: 6px 6px 0 0; 50 | } 51 | 52 | > div { 53 | padding: 28px; 54 | } 55 | 56 | label { 57 | display: block; 58 | color: $color-body; 59 | text-transform: uppercase; 60 | font-size: 11px; 61 | line-height: 21px; 62 | 63 | &.inline { 64 | display: flex; 65 | align-items: flex-start; 66 | 67 | > div { 68 | margin-right: 20px; 69 | flex: 1 1 calc(100% - 100px); 70 | input { 71 | max-width: none; 72 | } 73 | } 74 | 75 | button { 76 | margin-top: 22px; 77 | height: 36px; 78 | } 79 | } 80 | } 81 | 82 | p { 83 | font-size: 14px; 84 | margin-bottom: 20px; 85 | user-select: text; 86 | > * { 87 | user-select: text; 88 | } 89 | } 90 | 91 | strong { 92 | font-weight: 600; 93 | } 94 | 95 | input[type='text'], 96 | input[type='password'] { 97 | display: block; 98 | width: 100%; 99 | max-width: 320px; 100 | background: $color-input-bg; 101 | border: 1px solid $color-input-border; 102 | border-radius: 3px; 103 | font-size: 12px; 104 | padding: 10px; 105 | margin-bottom: 16px; 106 | } 107 | 108 | .error { 109 | display: block; 110 | margin-bottom: 16px; 111 | font-size: 12px; 112 | color: $color-warning; 113 | } 114 | } -------------------------------------------------------------------------------- /web/src/style/components/_highlight.scss: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | user-select: text; 14 | * { 15 | user-select: text; 16 | } 17 | } 18 | 19 | .hljs-comment, 20 | .hljs-quote { 21 | color: #998; 22 | font-style: italic; 23 | } 24 | 25 | .hljs-keyword, 26 | .hljs-selector-tag, 27 | .hljs-subst { 28 | color: #333; 29 | font-weight: bold; 30 | } 31 | 32 | .hljs-number, 33 | .hljs-literal, 34 | .hljs-variable, 35 | .hljs-template-variable, 36 | .hljs-tag .hljs-attr { 37 | color: #008080; 38 | } 39 | 40 | .hljs-string, 41 | .hljs-doctag { 42 | color: #d14; 43 | } 44 | 45 | .hljs-title, 46 | .hljs-section, 47 | .hljs-selector-id { 48 | color: #900; 49 | font-weight: bold; 50 | } 51 | 52 | .hljs-subst { 53 | font-weight: normal; 54 | } 55 | 56 | .hljs-type, 57 | .hljs-class .hljs-title { 58 | color: #458; 59 | font-weight: bold; 60 | } 61 | 62 | .hljs-tag, 63 | .hljs-name, 64 | .hljs-attribute { 65 | color: #000080; 66 | font-weight: normal; 67 | } 68 | 69 | .hljs-regexp, 70 | .hljs-link { 71 | color: #009926; 72 | } 73 | 74 | .hljs-symbol, 75 | .hljs-bullet { 76 | color: #990073; 77 | } 78 | 79 | .hljs-built_in, 80 | .hljs-builtin-name { 81 | color: #0086b3; 82 | } 83 | 84 | .hljs-meta { 85 | color: #999; 86 | font-weight: bold; 87 | } 88 | 89 | .hljs-deletion { 90 | background: #fdd; 91 | } 92 | 93 | .hljs-addition { 94 | background: #dfd; 95 | } 96 | 97 | .hljs-emphasis { 98 | font-style: italic; 99 | } 100 | 101 | .hljs-strong { 102 | font-weight: bold; 103 | } 104 | -------------------------------------------------------------------------------- /web/src/style/components/_neighbors.scss: -------------------------------------------------------------------------------- 1 | .neighbors { 2 | nav { 3 | text-align: right; 4 | } 5 | 6 | .graph { 7 | width: 100%; 8 | height: 40vh; 9 | max-height: 340px; 10 | font-size: 12px; 11 | position: relative; 12 | margin-bottom: 10px; 13 | > div { 14 | position: absolute; 15 | top: 0px; 16 | left: 0px; 17 | } 18 | 19 | .legend-item-0, 20 | .legend-item-0 path, 21 | .recharts-line.line-0 path, 22 | .tooltip p:nth-of-type(1) { 23 | stroke: $color-primary; 24 | color: $color-primary; 25 | } 26 | .legend-item-1, 27 | .legend-item-1 path, 28 | .recharts-line.line-1 path, 29 | .tooltip p:nth-of-type(2) { 30 | stroke: $color-action; 31 | color: $color-action; 32 | } 33 | .legend-item-2, 34 | .legend-item-2 path, 35 | .recharts-line.line-3 path, 36 | .tooltip p:nth-of-type(3) { 37 | stroke: $color-secondary; 38 | color: $color-secondary; 39 | } 40 | .legend-item-3, 41 | .legend-item-3 path, 42 | .recharts-line.line-4 path, 43 | .tooltip p:nth-of-type(4) { 44 | stroke: $color-success; 45 | color: $color-success; 46 | } 47 | .legend-item-4, 48 | .legend-item-4 path, 49 | .recharts-line.line-5 path, 50 | .tooltip p:nth-of-type(5) { 51 | stroke: $color-warning; 52 | color: $color-warning; 53 | } 54 | 55 | .recharts-tooltip-item-list { 56 | background: red; 57 | min-width: 160px; 58 | } 59 | } 60 | 61 | .tooltip { 62 | background: #fff; 63 | padding: 12px; 64 | 65 | h6 { 66 | font-size: 14px; 67 | font-weight: 600; 68 | margin-bottom: 12px; 69 | } 70 | 71 | p, 72 | strong { 73 | line-height: 12px; 74 | font-size: 12px; 75 | text-transform: capitalize; 76 | margin-bottom: 8px; 77 | } 78 | strong { 79 | font-weight: 600; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /web/src/style/components/_popup.scss: -------------------------------------------------------------------------------- 1 | .popup { 2 | position: fixed; 3 | display: flex; 4 | align-items: center; 5 | justify-content: center; 6 | top: 0px; 7 | left: 0px; 8 | width: 100vw; 9 | height: 100vh; 10 | overflow: hidden; 11 | z-index: 10; 12 | 13 | &:before { 14 | content: ''; 15 | position: absolute; 16 | width: 100%; 17 | height: 100%; 18 | background: $color-sidebar-bg; 19 | opacity: 0.8; 20 | } 21 | 22 | .x { 23 | background: none; 24 | display: block; 25 | width: 38px; 26 | height: 38px; 27 | line-height: 44px; 28 | cursor: pointer; 29 | margin-right: -10px; 30 | 31 | path { 32 | fill: $color-body; 33 | } 34 | &:hover { 35 | opacity: 0.75; 36 | } 37 | } 38 | 39 | .card { 40 | min-width: 480px; 41 | border-radius: 0px; 42 | 43 | p{ 44 | font-size: 13px; 45 | line-height: 20px; 46 | } 47 | 48 | @include layout(mobile) { 49 | min-width: 0px; 50 | width: 100vw; 51 | } 52 | 53 | input { 54 | max-width: 100%; 55 | } 56 | } 57 | 58 | .confirm { 59 | margin-top: 12px; 60 | text-align: right; 61 | 62 | button{ 63 | margin-left: 16px; 64 | } 65 | } 66 | 67 | &.loading { 68 | pointer-events: none; 69 | fieldset button:first-of-type { 70 | pointer-events: none; 71 | @include loading(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /web/src/style/components/_section.scss: -------------------------------------------------------------------------------- 1 | section { 2 | padding: 32px 50px; 3 | 4 | @include layout(tablet) { 5 | padding: 100px 20px 32px; 6 | } 7 | 8 | h1 { 9 | display: flex; 10 | justify-content: space-between; 11 | align-items: center; 12 | text-transform: uppercase; 13 | font-size: 30px; 14 | margin-bottom: 35px; 15 | font-weight: 200; 16 | color: $color-header; 17 | 18 | nav { 19 | display: flex; 20 | align-items: center; 21 | > * { 22 | margin-left: 14px; 23 | } 24 | } 25 | 26 | @include layout(mobile) { 27 | display: block; 28 | 29 | nav { 30 | > * { 31 | margin: 14px 14px 0 0; 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /web/src/style/components/_sidebar.scss: -------------------------------------------------------------------------------- 1 | @keyframes pulse { 2 | 0% { 3 | background-color: rgba(255, 255, 255, 0.6); 4 | } 5 | 100% { 6 | background-color: $color-sidebar-item-active; 7 | } 8 | } 9 | 10 | aside { 11 | background: $color-sidebar-bg; 12 | color: $color-sidebar-body; 13 | text-transform: uppercase; 14 | height: 100%; 15 | 16 | @include layout(tablet) { 17 | position: fixed; 18 | height: 100vh; 19 | width: 100vw; 20 | left: 0px; 21 | top: 0px; 22 | z-index: 5; 23 | background: none; 24 | overflow: hidden; 25 | pointer-events: none; 26 | } 27 | 28 | header { 29 | padding: 12px 30px; 30 | background: $color-sidebar-bg; 31 | pointer-events: all; 32 | } 33 | 34 | > div { 35 | background: $color-sidebar-bg; 36 | max-width: 248px; 37 | width: 100%; 38 | pointer-events: all; 39 | @include layout(tablet) { 40 | position: absolute; 41 | top: 74px; 42 | left: -248px; 43 | height: calc(100vh - 68px); 44 | } 45 | @include layout(mobile) { 46 | max-width: none; 47 | left: -100%; 48 | } 49 | } 50 | 51 | @include layout(tablet) { 52 | &.open { 53 | background: rgba(0, 0, 0, 0.45); 54 | > div { 55 | left: 0px; 56 | } 57 | } 58 | } 59 | 60 | img { 61 | display: block; 62 | height: 43px; 63 | margin: 10px 0; 64 | @include layout(tablet) { 65 | height: 50px; 66 | } 67 | } 68 | 69 | h1 { 70 | line-height: 64px; 71 | font-size: 14px; 72 | padding-left: 38px; 73 | letter-spacing: 1px; 74 | color: $color-sidebar-body-hover; 75 | &:first-of-type { 76 | border-top: 1px solid $color-sidebar-divider; 77 | } 78 | } 79 | 80 | a { 81 | display: block; 82 | position: relative; 83 | font-size: 12px; 84 | line-height: 56px; 85 | background: $color-sidebar-item; 86 | padding-left: 32px; 87 | border-left: 6px solid transparent; 88 | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 89 | 90 | svg { 91 | position: absolute; 92 | top: 50%; 93 | left: 32px; 94 | margin-top: -10px; 95 | opacity: 0; 96 | transition: all 0.12s ease-out; 97 | path { 98 | fill: $color-sidebar-body-hover; 99 | } 100 | } 101 | 102 | &:hover, 103 | &.active { 104 | background: $color-sidebar-item-hover; 105 | color: $color-sidebar-body-hover; 106 | padding-left: 62px; 107 | svg { 108 | opacity: 1; 109 | transition-delay: 0.1s; 110 | } 111 | } 112 | 113 | &.active { 114 | background: $color-sidebar-item-active; 115 | border-left-color: $color-sidebar-item-border; 116 | animation: pulse 0.4s; 117 | } 118 | } 119 | 120 | button { 121 | display: none; 122 | background: none; 123 | position: absolute; 124 | top: 25px; 125 | right: 20px; 126 | cursor: pointer; 127 | 128 | svg path { 129 | fill: $color-sidebar-body-hover; 130 | } 131 | 132 | @include layout(tablet) { 133 | display: block; 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /web/src/style/fonts/Inter-ExtraLight.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/ict/3bb009c3fd0088943ab9b810c69a76097f9fb6ae/web/src/style/fonts/Inter-ExtraLight.woff -------------------------------------------------------------------------------- /web/src/style/fonts/Inter-ExtraLight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/ict/3bb009c3fd0088943ab9b810c69a76097f9fb6ae/web/src/style/fonts/Inter-ExtraLight.woff2 -------------------------------------------------------------------------------- /web/src/style/fonts/Inter-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/ict/3bb009c3fd0088943ab9b810c69a76097f9fb6ae/web/src/style/fonts/Inter-Regular.woff -------------------------------------------------------------------------------- /web/src/style/fonts/Inter-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/ict/3bb009c3fd0088943ab9b810c69a76097f9fb6ae/web/src/style/fonts/Inter-Regular.woff2 -------------------------------------------------------------------------------- /web/src/style/fonts/Inter-SemiBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/ict/3bb009c3fd0088943ab9b810c69a76097f9fb6ae/web/src/style/fonts/Inter-SemiBold.woff -------------------------------------------------------------------------------- /web/src/style/fonts/Inter-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/ict/3bb009c3fd0088943ab9b810c69a76097f9fb6ae/web/src/style/fonts/Inter-SemiBold.woff2 -------------------------------------------------------------------------------- /web/src/style/fonts/PT-Mono-ext.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/ict/3bb009c3fd0088943ab9b810c69a76097f9fb6ae/web/src/style/fonts/PT-Mono-ext.woff2 -------------------------------------------------------------------------------- /web/src/style/fonts/PT-Mono-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/ict/3bb009c3fd0088943ab9b810c69a76097f9fb6ae/web/src/style/fonts/PT-Mono-latin.woff2 -------------------------------------------------------------------------------- /web/src/style/global/_colors.scss: -------------------------------------------------------------------------------- 1 | $color-bg: linear-gradient(to bottom, #d1d2de 0%, #e9e9e9 100%); 2 | $color-body: #404040; 3 | $color-header: #201040; 4 | $color-body-button: #fff; 5 | 6 | $color-sidebar-bg: linear-gradient(to right, #0b0943 0%, #3a1532 100%); 7 | $color-sidebar-body: rgba(255, 255, 255, 0.5); 8 | $color-sidebar-body-hover: #fff; 9 | $color-sidebar-item: rgba(255, 255, 255, 0.1); 10 | $color-sidebar-item-hover: rgba(255, 255, 255, 0.2); 11 | $color-sidebar-item-active: linear-gradient(to right, #6a0552 0, rgba(159, 0, 96, 0.7) 10px); 12 | $color-sidebar-item-border: #a1038e; 13 | $color-sidebar-divider: #bfbfbf; 14 | 15 | $color-box-header: #fff; 16 | $color-box-divider: #f3f3f4; 17 | $color-box-bg: linear-gradient(to bottom, #f7f5f5 0%, #f5f7f7 100%); 18 | 19 | $color-input-bg: #fff; 20 | $color-input-border: #d3d3d3; 21 | 22 | $color-action: #e84393; 23 | $color-primary: #35a4e4; 24 | $color-secondary: #636e72; 25 | $color-success: #00b8af; 26 | $color-warning: #db3733; 27 | -------------------------------------------------------------------------------- /web/src/style/global/_layout.scss: -------------------------------------------------------------------------------- 1 | @mixin layout($media) { 2 | @if $media == tablet { 3 | @media only screen and (max-width: 960px) { 4 | @content; 5 | } 6 | } @else if $media == mobile { 7 | @media only screen and (max-width: 640px) { 8 | @content; 9 | } 10 | } 11 | } 12 | 13 | main { 14 | display: grid; 15 | grid-template-columns: 248px 1fr; 16 | 17 | @include layout(tablet) { 18 | grid-template-columns: 1fr; 19 | } 20 | } 21 | 22 | .columns { 23 | display: grid; 24 | grid-template-columns: 1fr 1fr; 25 | grid-column-gap: 40px; 26 | 27 | @include layout(mobile) { 28 | display: block; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /web/src/style/global/_reset.scss: -------------------------------------------------------------------------------- 1 | *, 2 | *:after, 3 | *:before { 4 | margin: 0; 5 | padding: 0; 6 | border: 0; 7 | font-weight: 400; 8 | box-sizing: border-box; 9 | outline: none; 10 | user-select: none; 11 | } 12 | 13 | ol, 14 | ul { 15 | list-style: none; 16 | } 17 | 18 | html, 19 | body, 20 | main { 21 | width: 100%; 22 | min-height: 100vh; 23 | } 24 | 25 | body { 26 | background: $color-bg; 27 | color: $color-body; 28 | } 29 | 30 | a { 31 | color: inherit; 32 | text-decoration: none; 33 | } 34 | -------------------------------------------------------------------------------- /web/src/style/global/_typography.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Inter UI'; 3 | font-style: normal; 4 | font-weight: 200; 5 | src: url('./fonts/Inter-ExtraLight.woff2') format('woff2'), url('./fonts/Inter-ExtraLight.woff') format('woff'); 6 | } 7 | 8 | @font-face { 9 | font-family: 'Inter UI'; 10 | font-style: normal; 11 | font-weight: 400; 12 | src: url('./fonts/Inter-Regular.woff2') format('woff2'), url('./fonts/Inter-Regular.woff') format('woff'); 13 | } 14 | 15 | @font-face { 16 | font-family: 'Inter UI'; 17 | font-style: normal; 18 | font-weight: 600; 19 | src: url('./fonts/Inter-SemiBold.woff2') format('woff2'), url('./fonts/Inter-SemiBold.woff') format('woff'); 20 | } 21 | 22 | @font-face { 23 | font-family: 'PT Mono'; 24 | font-style: normal; 25 | font-weight: 400; 26 | src: url('./fonts/PT-Mono-latin.woff2') format('woff2'); 27 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, 28 | U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 29 | } 30 | 31 | @font-face { 32 | font-family: 'PT Mono'; 33 | font-style: normal; 34 | font-weight: 400; 35 | src: url('./fonts/PT-Mono-ext.woff2') format('woff2'); 36 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; 37 | } 38 | 39 | body { 40 | font-family: 'Inter UI'; 41 | font-weight: 400; 42 | } 43 | -------------------------------------------------------------------------------- /web/src/style/index.scss: -------------------------------------------------------------------------------- 1 | @import 'global/colors'; 2 | @import 'global/typography'; 3 | @import 'global/reset'; 4 | @import 'global/layout'; 5 | 6 | @import 'components/sidebar'; 7 | @import 'components/section'; 8 | @import 'components/button'; 9 | @import 'components/card'; 10 | @import 'components/popup'; 11 | @import 'components/highlight'; 12 | 13 | @import 'components/neighbors'; 14 | 15 | @import 'pages/home'; 16 | @import 'pages/log'; 17 | @import 'pages/modules'; 18 | -------------------------------------------------------------------------------- /web/src/style/pages/_home.scss: -------------------------------------------------------------------------------- 1 | section.home { 2 | article { 3 | display: flex; 4 | align-items: center; 5 | justify-content: center; 6 | text-align: center; 7 | margin: 0 auto; 8 | background: url('../assets/ict-logo-outline.svg') center center no-repeat; 9 | background-size: 100% auto; 10 | height: 100%; 11 | width: 100%; 12 | max-width: 570px; 13 | color: #240849; 14 | 15 | h1{ 16 | display: block; 17 | 18 | small{ 19 | display: block; 20 | margin: 20px auto; 21 | font-size: 18px; 22 | text-transform: none; 23 | 24 | strong{ 25 | font-weight: 600; 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /web/src/style/pages/_log.scss: -------------------------------------------------------------------------------- 1 | .log { 2 | .card { 3 | * { 4 | user-select: text; 5 | } 6 | 7 | p { 8 | display: flex; 9 | font-family: 'PT Mono'; 10 | font-size: 12px; 11 | line-height: 14px; 12 | margin-bottom: 8px; 13 | word-break: break-all; 14 | word-break: break-word; 15 | 16 | &.warn * { 17 | color: $color-warning; 18 | } 19 | 20 | strong { 21 | font-weight: 600; 22 | flex: 0 0 140px; 23 | } 24 | 25 | @include layout(mobile) { 26 | display: block; 27 | width: 100%; 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /web/src/style/pages/_modules.scss: -------------------------------------------------------------------------------- 1 | @keyframes appear { 2 | 0% { 3 | transform: translate(0, 30px); 4 | opacity: 0; 5 | } 6 | 100% { 7 | transform: translate(0, 0); 8 | opacity: 1; 9 | } 10 | } 11 | 12 | .modules { 13 | h3 { 14 | font-size: 16px; 15 | text-transform: uppercase; 16 | margin-bottom: 20px; 17 | color: $color-primary; 18 | font-weight: 600; 19 | } 20 | 21 | .module-list { 22 | background: $color-box-bg; 23 | padding: 30px 28px; 24 | animation: appear 0.4s cubic-bezier(0.23, 1, 0.32, 1); 25 | animation-fill-mode: forwards; 26 | } 27 | 28 | li { 29 | display: block; 30 | background: #fff; 31 | cursor: pointer; 32 | padding: 10px 12px; 33 | border-radius: 2px; 34 | border-left: 4px solid transparent; 35 | max-height: 46px; 36 | overflow: hidden; 37 | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 38 | margin-bottom: 14px; 39 | 40 | p { 41 | font-size: 13px; 42 | line-height: 20px; 43 | margin-bottom: 16px; 44 | } 45 | 46 | h4 { 47 | position: relative; 48 | display: flex; 49 | justify-content: space-between; 50 | align-items: center; 51 | text-transform: uppercase; 52 | font-size: 16px; 53 | line-height: 25px; 54 | margin-bottom: 18px; 55 | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 56 | 57 | nav { 58 | display: flex; 59 | align-items: center; 60 | > * { 61 | margin-left: 14px; 62 | } 63 | } 64 | } 65 | 66 | > a { 67 | display: block; 68 | font-size: 13px; 69 | font-weight: 600; 70 | color: $color-primary; 71 | margin-bottom: 16px; 72 | 73 | svg { 74 | margin: 0 10px 0 0; 75 | } 76 | 77 | &:hover { 78 | text-decoration: underline; 79 | } 80 | } 81 | 82 | h4 > svg { 83 | position: absolute; 84 | top: 2px; 85 | left: 0px; 86 | opacity: 0; 87 | transition: all 0.12s ease-out; 88 | path { 89 | fill: $color-primary; 90 | } 91 | &:last-of-type { 92 | display: none; 93 | } 94 | } 95 | 96 | &:hover, 97 | &.active { 98 | h4 { 99 | padding-left: 28px; 100 | 101 | svg { 102 | opacity: 1; 103 | transition-delay: 0.1s; 104 | } 105 | } 106 | } 107 | 108 | &:hover { 109 | box-shadow: 0px 0px 16px rgba(0, 0, 0, 0.1); 110 | } 111 | 112 | &.active { 113 | border-left: 4px solid $color-primary; 114 | border-radius: 0px; 115 | max-height: 200px; 116 | 117 | p { 118 | display: block; 119 | } 120 | h4 { 121 | margin-bottom: 28px; 122 | svg { 123 | &:first-of-type { 124 | display: none; 125 | } 126 | &:last-of-type { 127 | display: block; 128 | } 129 | } 130 | } 131 | } 132 | } 133 | } 134 | --------------------------------------------------------------------------------