@@ -0,0 +1,73 @@ | |||
/* | |||
* This file was generated by the Gradle 'init' task. | |||
* | |||
* This generated file contains a sample Java Library project to get you started. | |||
* For more details take a look at the Java Libraries chapter in the Gradle | |||
* User Manual available at https://docs.gradle.org/5.4/userguide/java_library_plugin.html | |||
*/ | |||
plugins { | |||
id 'java-library' | |||
id 'groovy' | |||
id 'eclipse' | |||
id 'application' | |||
id 'idea' | |||
} | |||
// Groovy interop | |||
sourceSets.main.java.srcDirs = [] | |||
sourceSets.main.groovy.srcDirs += ["src/main/java"] | |||
compileJava.options.encoding = 'UTF-8' | |||
mainClassName = 'fr.slixe.mining.Main' | |||
group 'fr.slixe' | |||
version '1.0.0' | |||
sourceCompatibility = 1.8 | |||
targetCompatibility = 1.8 | |||
repositories { | |||
maven { | |||
url 'http://wytrem.github.io/maven' | |||
} | |||
maven { | |||
url 'https://paladin-framework.github.io/maven' | |||
} | |||
mavenCentral() | |||
} | |||
dependencies { | |||
implementation( | |||
'com.sparkjava:spark-core:2.8.0', | |||
'net.sf.trove4j:trove4j:3.0.3', | |||
'com.google.code.gson:gson:2.8.5', | |||
'com.mashape.unirest:unirest-java:1.4.9', | |||
'org.apache.commons:commons-lang3:3.8.1', | |||
'org.apache.logging.log4j:log4j-core:2.10.0', | |||
) | |||
implementation('fr.litarvan.paladin:paladin-framework:1.2.0') { | |||
exclude group: 'org.apache.httpcomponents', module: 'httpcore-nio' | |||
exclude group: 'org.apache.logging.log4j', module: 'log4j-core' | |||
} | |||
} | |||
task fatJar(type: Jar) { | |||
from { | |||
configurations | |||
.runtimeClasspath | |||
.findAll { !it.name.endsWith('pom') } | |||
.collect { it.isDirectory() ? it : zipTree(it) } | |||
} | |||
with jar | |||
baseName = 'dero-mining' | |||
manifest { | |||
attributes 'Main-Class': mainClassName | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
# dero-stats | |||
## Project setup | |||
``` | |||
yarn install | |||
``` | |||
### Compiles and hot-reloads for development | |||
``` | |||
yarn run serve | |||
``` | |||
### Compiles and minifies for production | |||
``` | |||
yarn run build | |||
``` | |||
### Run your tests | |||
``` | |||
yarn run test | |||
``` | |||
### Lints and fixes files | |||
``` | |||
yarn run lint | |||
``` | |||
### Customize configuration | |||
See [Configuration Reference](https://cli.vuejs.org/config/). |
@@ -0,0 +1,5 @@ | |||
module.exports = { | |||
presets: [ | |||
'@vue/cli-plugin-babel/preset' | |||
] | |||
} |
@@ -0,0 +1,43 @@ | |||
{ | |||
"name": "dero-stats", | |||
"version": "0.1.0", | |||
"private": true, | |||
"scripts": { | |||
"serve": "vue-cli-service serve", | |||
"build": "vue-cli-service build", | |||
"lint": "vue-cli-service lint" | |||
}, | |||
"dependencies": { | |||
"core-js": "^3.4.4", | |||
"material-design-icons": "^3.0.1", | |||
"vue": "^2.6.10", | |||
"vuetify": "^2.2.4" | |||
}, | |||
"devDependencies": { | |||
"@vue/cli-plugin-babel": "^4.1.0", | |||
"@vue/cli-plugin-eslint": "^4.1.0", | |||
"@vue/cli-service": "^4.1.0", | |||
"babel-eslint": "^10.0.3", | |||
"eslint": "^5.16.0", | |||
"eslint-plugin-vue": "^5.0.0", | |||
"vue-template-compiler": "^2.6.10" | |||
}, | |||
"eslintConfig": { | |||
"root": true, | |||
"env": { | |||
"node": true | |||
}, | |||
"extends": [ | |||
"plugin:vue/essential", | |||
"eslint:recommended" | |||
], | |||
"rules": {}, | |||
"parserOptions": { | |||
"parser": "babel-eslint" | |||
} | |||
}, | |||
"browserslist": [ | |||
"> 1%", | |||
"last 2 versions" | |||
] | |||
} |
@@ -0,0 +1,18 @@ | |||
<!DOCTYPE html> | |||
<html lang="en"> | |||
<head> | |||
<meta charset="utf-8"> | |||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |||
<meta name="viewport" content="width=device-width,initial-scale=1.0"> | |||
<link rel="icon" href="<%= BASE_URL %>logo.png"> | |||
<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700%7CMaterial+Icons' rel="stylesheet"> | |||
<title>DERO Stats</title> | |||
</head> | |||
<body> | |||
<noscript> | |||
<strong>We're sorry but DERO Stats doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> | |||
</noscript> | |||
<div id="app"></div> | |||
<!-- built files will be auto injected --> | |||
</body> | |||
</html> |
@@ -0,0 +1,74 @@ | |||
<template> | |||
<div id="app"> | |||
<v-app> | |||
<header> | |||
<v-toolbar color="secondary"> | |||
<v-avatar size="60" tile> | |||
<v-img src="/logo.png"> | |||
</v-img> | |||
</v-avatar> | |||
<v-btn to="/" text> | |||
<v-toolbar-title>{{ $name }}</v-toolbar-title> | |||
</v-btn> | |||
<v-toolbar-items> | |||
<v-btn @click="changeTheme()" text><span><v-icon>invert_colors</v-icon></span></v-btn> | |||
</v-toolbar-items> | |||
</v-toolbar> | |||
</header> | |||
<v-main> | |||
<Main></Main> | |||
</v-main> | |||
<v-footer elevation="10" padless> | |||
<v-card flat tile width="100%" class="text-center" color="primary"> | |||
<v-card-text v-if="$donations"> | |||
dERokevAZEZVJ2N7o39VH81BXBqX9ojtncnPTDMyiVbmYiTXQY93AUCLcor9xsWCKWhYy25ja89ikZWXWab9kXRB7LYfUmbQyS | |||
</v-card-text> | |||
<v-divider></v-divider> | |||
<v-card-text> | |||
{{ new Date().getFullYear() }} — <strong>{{ $name }}</strong> | |||
</v-card-text> | |||
</v-card> | |||
</v-footer> | |||
</v-app> | |||
</div> | |||
</template> | |||
<script> | |||
import Main from './views/Main' | |||
export default { | |||
name: 'app', | |||
components: { | |||
Main | |||
}, | |||
mounted() { | |||
if (localStorage.theme) { | |||
this.$vuetify.theme.dark = localStorage.theme == "dark" | |||
} else { | |||
this.$vuetify.theme.dark = true | |||
} | |||
}, | |||
methods: { | |||
changeTheme() { | |||
this.$vuetify.theme.dark = !this.$vuetify.theme.dark | |||
if (this.$vuetify.theme.dark) { | |||
localStorage.theme = "dark" | |||
} else { | |||
localStorage.theme = "light" | |||
} | |||
} | |||
} | |||
} | |||
</script> | |||
<style> | |||
#app { | |||
color: white; | |||
text-align: center; | |||
background: var(--v-anchor-base); | |||
} | |||
* { | |||
transition: background-color 200ms ease, color 150ms ease-in-out; | |||
} | |||
</style> |
@@ -0,0 +1,37 @@ | |||
import Vue from 'vue' | |||
import App from './App.vue' | |||
import Vuetify from 'vuetify' | |||
import 'vuetify/dist/vuetify.min.css' | |||
Vue.config.productionTip = false | |||
Vue.prototype.$name = "DERO Mining Stats" | |||
Vue.prototype.$api = "http://localhost:8080/api" | |||
Vue.prototype.$donations = false | |||
Vue.use(Vuetify) | |||
new Vue({ | |||
render: h => h(App), | |||
vuetify: new Vuetify({ | |||
icons: { | |||
iconfont: 'md', | |||
}, | |||
theme: { | |||
options: { | |||
customProperties: true, | |||
}, | |||
themes: { | |||
dark: { | |||
primary: '#383B3E', | |||
secondary: '#2A2D2F', | |||
anchor: '#303030', | |||
}, | |||
light: { | |||
primary: '#DCDCDC', | |||
secondary: '#DCDCDC', | |||
anchor: '#B0C4DE', | |||
} | |||
}, | |||
dark: true | |||
}, | |||
}), | |||
}).$mount('#app') |
@@ -0,0 +1,218 @@ | |||
<template> | |||
<div id="main"> | |||
<div class="first-line"> | |||
<v-card class="elevation-5 infos" color="primary"> | |||
<v-card-title class="center-text"> | |||
<h2>Daemon Infos</h2> | |||
</v-card-title> | |||
<v-card-text> | |||
<ul> | |||
<li><strong>Current Height:</strong> {{ formatValue(infos.height) }}</li> | |||
<li><strong>Topo Height:</strong> {{ formatValue(infos.topoheight) }}</li> | |||
<li><strong>Hashrate:</strong> {{ formatValue((infos.difficulty/(infos.target*1000*1000)).toFixed(2)) }} MH/s</li> | |||
<li><strong>Average Block Time:</strong> {{ infos.averageblocktime50 }}s</li> | |||
<li><strong>Difficulty:</strong> {{ formatValue(infos.difficulty) }} </li> | |||
<li><strong>Median Block Size:</strong> {{ formatValue(infos.median_block_size/1000) }} kB</li> | |||
<li><strong>Current Supply:</strong> {{ formatValue(infos.total_supply) }} DERO</li> | |||
</ul> | |||
</v-card-text> | |||
</v-card> | |||
<v-card class="elevation-5 blocks" :loading="loading" color="primary"> | |||
<v-card-title> | |||
<h2>Blocks Found</h2> | |||
<v-spacer></v-spacer> | |||
<v-text-field class="search" v-model="blocksSearch" append-icon="magnify" label="Search" single-line hide-details></v-text-field> | |||
</v-card-title> | |||
<v-data-table v-model="selected" show-select no-data-text="No blocks found" :search="blocksSearch" multi-sort :headers="blocksHeaders" :items="blocks" :items-per-page="5"> | |||
</v-data-table> | |||
</v-card> | |||
</div> | |||
<v-card class="elevation-5 miners" :loading="loading" color="primary"> | |||
<v-card-title> | |||
<h2>Connected Miners</h2> | |||
<v-spacer></v-spacer> | |||
<v-text-field class="search" v-model="minersSearch" no-data-text="No miners connected" append-icon="magnify" label="Search" single-line hide-details></v-text-field> | |||
</v-card-title> | |||
<v-data-table v-model="selected" show-select :search="minersSearch" multi-sort :headers="headers" :items="miners" :items-per-page="5"> | |||
<template v-slot:item.lastJobRequest="{ item }"> | |||
<span>{{ formatTime(item.lastJobRequest) }}</span> | |||
</template> | |||
<template v-slot:item.lastBlockSent="{ item }"> | |||
<span>{{ item.lastBlockSent != -1 ? formatTime(item.lastBlockSent) : "No block found yet" }}</span> | |||
</template> | |||
<!--<template v-slot:item.alive="{ item }"> | |||
<span :style="'color:' + (item.alive ? 'green' : 'red')">{{ item.alive }}</span> | |||
</template>--> | |||
</v-data-table> | |||
<v-card-text v-show="!this.loading" style="text-align: center;"><strong>Currently mining on:</strong> {{ infos.walletAddress }}</v-card-text> | |||
</v-card> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
data() { | |||
return { | |||
loading: true, | |||
minersSearch: "", | |||
blocksSearch: "", | |||
selected: [], | |||
headers: [ | |||
{ | |||
text: "IP", | |||
align: "start", | |||
value: "ip" | |||
}, | |||
{ | |||
text: "Last Job Request", | |||
value: "lastJobRequest" | |||
}, | |||
{ | |||
text: "Last Block Found", | |||
value: "lastBlockSent" | |||
}, | |||
{ | |||
text: "Blocks Found", | |||
value: "blocksFound" | |||
}, | |||
{ | |||
text: "Connected", | |||
value: "alive" | |||
} | |||
], | |||
blocksHeaders: [ | |||
{ | |||
text: "Miner", | |||
align: "start", | |||
value: "minerIp" | |||
}, | |||
{ | |||
text: "Block Height", | |||
value: "height" | |||
}, | |||
{ | |||
text: "Hash", | |||
value: "blid" | |||
}, | |||
{ | |||
text: "Status", | |||
value: "status" | |||
} | |||
], | |||
miners: [], | |||
infos: {}, | |||
blocks: [] | |||
} | |||
}, | |||
mounted() { | |||
this.update() | |||
setInterval(() => this.update(), 5000) | |||
}, | |||
methods: { | |||
update() { | |||
fetch(this.$api + "/info").then(result => result.json()).then(json => { | |||
this.infos = json | |||
}) | |||
fetch(this.$api + "/blocks").then(result => result.json()).then(json => { | |||
json.reverse() | |||
this.blocks = json | |||
}) | |||
fetch(this.$api + "/miners").then(result => result.json()).then(json => { | |||
if (this.loading) { | |||
this.loading = false | |||
} | |||
this.miners = json | |||
}) | |||
}, | |||
formatTime(timestamp) { | |||
let date = new Date(timestamp); | |||
return ("00" + (date.getMonth() + 1)).slice(-2) + "/" + | |||
("00" + date.getDate()).slice(-2) + "/" + | |||
date.getFullYear() + " " + | |||
("00" + date.getHours()).slice(-2) + ":" + | |||
("00" + date.getMinutes()).slice(-2) + ":" + | |||
("00" + date.getSeconds()).slice(-2); | |||
}, | |||
formatValue(value) { | |||
if (!value) return 0 | |||
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ") | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped> | |||
#main { | |||
margin: 15%; | |||
margin-top: 5%; | |||
margin-bottom: 5%; | |||
} | |||
.theme--dark.v-data-table { | |||
background-color: var(--v-primary-base); | |||
} | |||
.theme--light.v-data-table { | |||
background-color: var(--v-primary-base); | |||
} | |||
.first-line { | |||
display: flex; | |||
justify-items: center; | |||
align-items: flex-start; | |||
} | |||
.infos { | |||
width: 30%; | |||
text-align: left; | |||
} | |||
ul li { | |||
list-style: none; | |||
padding: 0; | |||
margin-bottom: 2%; | |||
} | |||
.blocks { | |||
margin: auto; | |||
margin-left: 5%; | |||
width: 100%; | |||
padding-left: 1%; | |||
padding-right: 1%; | |||
} | |||
.miners { | |||
margin-top: 5%; | |||
padding-left: 1%; | |||
padding-right: 1%; | |||
width: 100%; | |||
} | |||
.center-text { | |||
text-align: center; | |||
} | |||
@media screen and (max-width: 1280px) | |||
{ | |||
#main { | |||
margin: 5%; | |||
} | |||
.first-line { | |||
margin: auto; | |||
flex-direction: column; | |||
} | |||
.infos { | |||
margin: auto; | |||
width: auto; | |||
} | |||
.blocks { | |||
margin: auto; | |||
margin-top: 5%; | |||
margin-bottom: 5%; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,5 @@ | |||
distributionBase=GRADLE_USER_HOME | |||
distributionPath=wrapper/dists | |||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4-bin.zip | |||
zipStoreBase=GRADLE_USER_HOME | |||
zipStorePath=wrapper/dists |
@@ -0,0 +1,188 @@ | |||
#!/usr/bin/env sh | |||
# | |||
# Copyright 2015 the original author or authors. | |||
# | |||
# Licensed under the Apache License, Version 2.0 (the "License"); | |||
# you may not use this file except in compliance with the License. | |||
# You may obtain a copy of the License at | |||
# | |||
# http://www.apache.org/licenses/LICENSE-2.0 | |||
# | |||
# Unless required by applicable law or agreed to in writing, software | |||
# distributed under the License is distributed on an "AS IS" BASIS, | |||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
# See the License for the specific language governing permissions and | |||
# limitations under the License. | |||
# | |||
############################################################################## | |||
## | |||
## Gradle start up script for UN*X | |||
## | |||
############################################################################## | |||
# Attempt to set APP_HOME | |||
# Resolve links: $0 may be a link | |||
PRG="$0" | |||
# Need this for relative symlinks. | |||
while [ -h "$PRG" ] ; do | |||
ls=`ls -ld "$PRG"` | |||
link=`expr "$ls" : '.*-> \(.*\)$'` | |||
if expr "$link" : '/.*' > /dev/null; then | |||
PRG="$link" | |||
else | |||
PRG=`dirname "$PRG"`"/$link" | |||
fi | |||
done | |||
SAVED="`pwd`" | |||
cd "`dirname \"$PRG\"`/" >/dev/null | |||
APP_HOME="`pwd -P`" | |||
cd "$SAVED" >/dev/null | |||
APP_NAME="Gradle" | |||
APP_BASE_NAME=`basename "$0"` | |||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | |||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | |||
# Use the maximum available, or set MAX_FD != -1 to use that value. | |||
MAX_FD="maximum" | |||
warn () { | |||
echo "$*" | |||
} | |||
die () { | |||
echo | |||
echo "$*" | |||
echo | |||
exit 1 | |||
} | |||
# OS specific support (must be 'true' or 'false'). | |||
cygwin=false | |||
msys=false | |||
darwin=false | |||
nonstop=false | |||
case "`uname`" in | |||
CYGWIN* ) | |||
cygwin=true | |||
;; | |||
Darwin* ) | |||
darwin=true | |||
;; | |||
MINGW* ) | |||
msys=true | |||
;; | |||
NONSTOP* ) | |||
nonstop=true | |||
;; | |||
esac | |||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | |||
# Determine the Java command to use to start the JVM. | |||
if [ -n "$JAVA_HOME" ] ; then | |||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | |||
# IBM's JDK on AIX uses strange locations for the executables | |||
JAVACMD="$JAVA_HOME/jre/sh/java" | |||
else | |||
JAVACMD="$JAVA_HOME/bin/java" | |||
fi | |||
if [ ! -x "$JAVACMD" ] ; then | |||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | |||
Please set the JAVA_HOME variable in your environment to match the | |||
location of your Java installation." | |||
fi | |||
else | |||
JAVACMD="java" | |||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | |||
Please set the JAVA_HOME variable in your environment to match the | |||
location of your Java installation." | |||
fi | |||
# Increase the maximum file descriptors if we can. | |||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then | |||
MAX_FD_LIMIT=`ulimit -H -n` | |||
if [ $? -eq 0 ] ; then | |||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then | |||
MAX_FD="$MAX_FD_LIMIT" | |||
fi | |||
ulimit -n $MAX_FD | |||
if [ $? -ne 0 ] ; then | |||
warn "Could not set maximum file descriptor limit: $MAX_FD" | |||
fi | |||
else | |||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" | |||
fi | |||
fi | |||
# For Darwin, add options to specify how the application appears in the dock | |||
if $darwin; then | |||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" | |||
fi | |||
# For Cygwin, switch paths to Windows format before running java | |||
if $cygwin ; then | |||
APP_HOME=`cygpath --path --mixed "$APP_HOME"` | |||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` | |||
JAVACMD=`cygpath --unix "$JAVACMD"` | |||
# We build the pattern for arguments to be converted via cygpath | |||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` | |||
SEP="" | |||
for dir in $ROOTDIRSRAW ; do | |||
ROOTDIRS="$ROOTDIRS$SEP$dir" | |||
SEP="|" | |||
done | |||
OURCYGPATTERN="(^($ROOTDIRS))" | |||
# Add a user-defined pattern to the cygpath arguments | |||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then | |||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" | |||
fi | |||
# Now convert the arguments - kludge to limit ourselves to /bin/sh | |||
i=0 | |||
for arg in "$@" ; do | |||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` | |||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option | |||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition | |||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` | |||
else | |||
eval `echo args$i`="\"$arg\"" | |||
fi | |||
i=$((i+1)) | |||
done | |||
case $i in | |||
(0) set -- ;; | |||
(1) set -- "$args0" ;; | |||
(2) set -- "$args0" "$args1" ;; | |||
(3) set -- "$args0" "$args1" "$args2" ;; | |||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;; | |||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; | |||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; | |||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; | |||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; | |||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; | |||
esac | |||
fi | |||
# Escape application args | |||
save () { | |||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done | |||
echo " " | |||
} | |||
APP_ARGS=$(save "$@") | |||
# Collect all arguments for the java command, following the shell quoting and substitution rules | |||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" | |||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong | |||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then | |||
cd "$(dirname "$0")" | |||
fi | |||
exec "$JAVACMD" "$@" |
@@ -0,0 +1,100 @@ | |||
@rem | |||
@rem Copyright 2015 the original author or authors. | |||
@rem | |||
@rem Licensed under the Apache License, Version 2.0 (the "License"); | |||
@rem you may not use this file except in compliance with the License. | |||
@rem You may obtain a copy of the License at | |||
@rem | |||
@rem http://www.apache.org/licenses/LICENSE-2.0 | |||
@rem | |||
@rem Unless required by applicable law or agreed to in writing, software | |||
@rem distributed under the License is distributed on an "AS IS" BASIS, | |||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
@rem See the License for the specific language governing permissions and | |||
@rem limitations under the License. | |||
@rem | |||
@if "%DEBUG%" == "" @echo off | |||
@rem ########################################################################## | |||
@rem | |||
@rem Gradle startup script for Windows | |||
@rem | |||
@rem ########################################################################## | |||
@rem Set local scope for the variables with windows NT shell | |||
if "%OS%"=="Windows_NT" setlocal | |||
set DIRNAME=%~dp0 | |||
if "%DIRNAME%" == "" set DIRNAME=. | |||
set APP_BASE_NAME=%~n0 | |||
set APP_HOME=%DIRNAME% | |||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | |||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" | |||
@rem Find java.exe | |||
if defined JAVA_HOME goto findJavaFromJavaHome | |||
set JAVA_EXE=java.exe | |||
%JAVA_EXE% -version >NUL 2>&1 | |||
if "%ERRORLEVEL%" == "0" goto init | |||
echo. | |||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | |||
echo. | |||
echo Please set the JAVA_HOME variable in your environment to match the | |||
echo location of your Java installation. | |||
goto fail | |||
:findJavaFromJavaHome | |||
set JAVA_HOME=%JAVA_HOME:"=% | |||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe | |||
if exist "%JAVA_EXE%" goto init | |||
echo. | |||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% | |||
echo. | |||
echo Please set the JAVA_HOME variable in your environment to match the | |||
echo location of your Java installation. | |||
goto fail | |||
:init | |||
@rem Get command-line arguments, handling Windows variants | |||
if not "%OS%" == "Windows_NT" goto win9xME_args | |||
:win9xME_args | |||
@rem Slurp the command line arguments. | |||
set CMD_LINE_ARGS= | |||
set _SKIP=2 | |||
:win9xME_args_slurp | |||
if "x%~1" == "x" goto execute | |||
set CMD_LINE_ARGS=%* | |||
:execute | |||
@rem Setup the command line | |||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | |||
@rem Execute Gradle | |||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% | |||
:end | |||
@rem End local scope for the variables with windows NT shell | |||
if "%ERRORLEVEL%"=="0" goto mainEnd | |||
:fail | |||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | |||
rem the _cmd.exe /c_ return code! | |||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 | |||
exit /b 1 | |||
:mainEnd | |||
if "%OS%"=="Windows_NT" endlocal | |||
:omega |
@@ -0,0 +1,10 @@ | |||
/* | |||
* This file was generated by the Gradle 'init' task. | |||
* | |||
* The settings file is used to specify which projects to include in your build. | |||
* | |||
* Detailed information about configuring a multi-project build in Gradle can be found | |||
* in the user manual at https://docs.gradle.org/5.4/userguide/multi_project_builds.html | |||
*/ | |||
rootProject.name = 'dero-mining' |
@@ -0,0 +1,57 @@ | |||
package fr.slixe.mining; | |||
import java.util.Timer; | |||
import java.util.TimerTask; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import com.google.inject.Inject; | |||
import fr.litarvan.paladin.OnStart; | |||
import fr.litarvan.paladin.OnStop; | |||
import fr.litarvan.paladin.PaladinApp; | |||
import fr.litarvan.paladin.PaladinConfig; | |||
import fr.slixe.mining.service.DaemonService; | |||
import spark.Spark; | |||
@PaladinApp(name = "DERO Mining", version = App.VERSION, author = "Slixe") | |||
public class App | |||
{ | |||
public static final String VERSION = "1.0.0"; | |||
private static final Logger log = LoggerFactory.getLogger("DERO Mining"); | |||
private final Timer timer = new Timer(); | |||
@Inject | |||
private DaemonService daemon; | |||
@Inject | |||
private PaladinConfig config; | |||
@OnStart | |||
public void start() | |||
{ | |||
TimerTask task = new TimerTask() { | |||
@Override | |||
public void run() | |||
{ | |||
daemon.updateInfo(); | |||
daemon.updateJob(); | |||
} | |||
}; | |||
this.timer.schedule(task, 0, config.get("job-interval", long.class)); | |||
} | |||
@OnStop | |||
public void stop() | |||
{ | |||
this.timer.cancel(); | |||
log.info("Shutting down http service..."); | |||
Spark.stop(); | |||
} | |||
} |
@@ -0,0 +1,12 @@ | |||
package fr.slixe.mining; | |||
import java.lang.annotation.ElementType; | |||
import java.lang.annotation.Retention; | |||
import java.lang.annotation.RetentionPolicy; | |||
import java.lang.annotation.Target; | |||
@Target(ElementType.FIELD) | |||
@Retention(RetentionPolicy.RUNTIME) | |||
public @interface JsonIgnore | |||
{ | |||
} |
@@ -0,0 +1,61 @@ | |||
package fr.slixe.mining; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.security.GeneralSecurityException; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import fr.litarvan.paladin.Paladin; | |||
import fr.litarvan.paladin.PaladinBuilder; | |||
import fr.litarvan.paladin.PaladinConfig; | |||
import fr.slixe.mining.http.SparkHttpServer; | |||
import fr.slixe.mining.service.SessionManager; | |||
public class Main | |||
{ | |||
private static final Logger log = LoggerFactory.getLogger("Main"); | |||
public static void main(String[] args) | |||
{ | |||
Paladin paladin = PaladinBuilder.create(App.class) | |||
.addModule(new MyModule()) | |||
.loadCommandLineArguments(args) | |||
.setSessionManager(new SessionManager()) | |||
.build(); | |||
PaladinConfig config = paladin.getConfig(); | |||
SparkHttpServer httpServer = new SparkHttpServer(paladin, config.get("port", int.class)); | |||
if (config.get("enableSSL", boolean.class)) | |||
{ | |||
log.info("Enabling SSL..."); | |||
String filePath = config.get("keystoreFile"); | |||
char[] secret = config.get("keystorePassword").toCharArray(); | |||
File file = new File(filePath); | |||
if (!file.exists()) { | |||
log.error(String.format("Keystore file at '%s' not found. Skipping SSL...", file.getAbsolutePath())); | |||
} | |||
else { | |||
try { | |||
httpServer.loadSSLCert(file, secret); | |||
log.info("SSL is now enabled!"); | |||
} catch (GeneralSecurityException | IOException e) { | |||
log.error(e.getLocalizedMessage()); | |||
} | |||
} | |||
} | |||
log.info(config.get("walletAddress")); | |||
if ("dERokevAZEZVJ2N7o39VH81BXBqX9ojtncnPTDMyiVbmYiTXQY93AUCLcor9xsWCKWhYy25ja89ikZWXWab9kXRB7LYfUmbQyS".contentEquals(config.get("walletAddress"))) | |||
{ | |||
log.warn("You're running using the default DERO wallet address!"); | |||
log.warn("If you want to change it: edit the 'config.json' file."); | |||
} | |||
paladin.start(httpServer); | |||
} | |||
} |
@@ -0,0 +1,26 @@ | |||
package fr.slixe.mining; | |||
import com.google.gson.ExclusionStrategy; | |||
import com.google.gson.FieldAttributes; | |||
import com.google.gson.Gson; | |||
import com.google.gson.GsonBuilder; | |||
import com.google.inject.AbstractModule; | |||
public class MyModule extends AbstractModule { | |||
@Override | |||
protected void configure() { | |||
bind(Gson.class).toInstance( | |||
new GsonBuilder().setPrettyPrinting().addSerializationExclusionStrategy(new ExclusionStrategy() { | |||
@Override | |||
public boolean shouldSkipField(FieldAttributes f) { | |||
return f.getAnnotation(JsonIgnore.class) != null; | |||
} | |||
@Override | |||
public boolean shouldSkipClass(Class<?> clazz) { | |||
return false; | |||
} | |||
}).create()); | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
package fr.slixe.mining.http; | |||
import fr.litarvan.paladin.http.routing.RequestException; | |||
public class InvalidParameterException extends RequestException | |||
{ | |||
public InvalidParameterException(String message) | |||
{ | |||
super("Invalid parameter : " + message); | |||
} | |||
} |
@@ -0,0 +1,188 @@ | |||
package fr.slixe.mining.http; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.UnsupportedEncodingException; | |||
import java.net.InetAddress; | |||
import java.net.URLDecoder; | |||
import java.net.URLEncoder; | |||
import java.net.UnknownHostException; | |||
import java.nio.charset.Charset; | |||
import java.security.GeneralSecurityException; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import fr.litarvan.paladin.Paladin; | |||
import fr.litarvan.paladin.http.Cookie; | |||
import fr.litarvan.paladin.http.Header; | |||
import fr.litarvan.paladin.http.HeaderPair; | |||
import fr.litarvan.paladin.http.HttpMethod; | |||
import fr.litarvan.paladin.http.Request; | |||
import fr.litarvan.paladin.http.Response; | |||
import fr.litarvan.paladin.http.server.PaladinHttpServer; | |||
import spark.Spark; | |||
public class SparkHttpServer implements PaladinHttpServer | |||
{ | |||
private Paladin paladin; | |||
private int port; | |||
private boolean isRunning; | |||
public SparkHttpServer(Paladin paladin, int port) | |||
{ | |||
this.paladin = paladin; | |||
this.port = port; | |||
} | |||
@Override | |||
public void start() throws IOException | |||
{ | |||
Spark.port(port); | |||
Spark.threadPool(15); | |||
Spark.staticFileLocation("public"); | |||
Spark.before((request, originalResponse) -> { | |||
Response response = new Response(); | |||
paladin.execute(createRequest(request, response), response); | |||
applyResponse(originalResponse, response); | |||
Spark.halt(); | |||
}); | |||
Spark.awaitInitialization(); | |||
this.isRunning = true; | |||
} | |||
protected Request createRequest(spark.Request request, Response response) | |||
{ | |||
String[] headerNames = request.headers().toArray(new String[0]); | |||
Header[] headers = new Header[headerNames.length]; | |||
for (int i = 0; i < headerNames.length; i++) { | |||
headers[i] = new Header(headerNames[i], request.headers(headerNames[i])); | |||
} | |||
String[] queryParams = request.queryParams().toArray(new String[0]); | |||
Map<String, String> params = new HashMap<>(request.params()); | |||
for (String queryParam : queryParams) { | |||
params.put(queryParam, request.queryParams(queryParam)); | |||
} | |||
String[] cookieNames = request.cookies().keySet().toArray(new String[0]); | |||
Cookie[] cookies = new Cookie[cookieNames.length]; | |||
for (int i = 0; i < cookieNames.length; i++) { | |||
cookies[i] = new Cookie(cookieNames[i], request.cookie(cookieNames[i])); | |||
} | |||
Header contentType = null; | |||
for (Header header : headers) | |||
{ | |||
if (header.getName().equalsIgnoreCase(Header.CONTENT_TYPE)) | |||
{ | |||
contentType = header; | |||
break; | |||
} | |||
} | |||
if (contentType != null && Header.CONTENT_TYPE_FORM_URL_ENCODED.equals(contentType.getValue())) | |||
{ | |||
extractParams(params, new String(request.bodyAsBytes(), Charset.defaultCharset())); | |||
} | |||
return new Request(paladin, request.ip(), HttpMethod.valueOf(request.requestMethod()), request.uri(), headers, request.bodyAsBytes(), params, cookies, response); | |||
} | |||
protected void extractParams(Map<String, String> params, String query) | |||
{ | |||
for (String param : query.split("&")) | |||
{ | |||
String[] split = param.split("="); | |||
try | |||
{ | |||
params.put(URLDecoder.decode(split[0], Charset.defaultCharset().name()), split.length > 1 ? URLDecoder.decode(split[1], Charset.defaultCharset().name()) : ""); | |||
} | |||
catch (UnsupportedEncodingException e) | |||
{ | |||
// Can't happen | |||
} | |||
} | |||
} | |||
protected void applyResponse(spark.Response sparkResponse, Response response) | |||
{ | |||
for (Header header : response.getHeaders()) { | |||
String value = ""; | |||
if (header.getValue() != null && !header.getValue().isEmpty()) | |||
{ | |||
value = header.getValue(); | |||
} | |||
else if (header.getPairs() != null) | |||
{ | |||
for (int i1 = 0; i1 < header.getPairs().size(); i1++) | |||
{ | |||
HeaderPair pair = header.getPairs().get(i1); | |||
try | |||
{ | |||
value += pair.getName() + "=" + URLEncoder.encode(pair.getValue(), Charset.defaultCharset().name()) + (i1 + 1 < header.getPairs().size() ? "; " : ""); | |||
} | |||
catch (UnsupportedEncodingException ignored) | |||
{ | |||
// Can't happen | |||
} | |||
} | |||
} | |||
sparkResponse.header(header.getName(), value); | |||
} | |||
for (Cookie cookie : response.getCookies()) { | |||
// TODO: Cookie params ? (& Dans Paladin) | |||
sparkResponse.cookie(cookie.getName(), cookie.getValue()); | |||
} | |||
sparkResponse.status(response.getCode()); | |||
try { | |||
sparkResponse.raw().getOutputStream().write(response.getContent()); | |||
} catch (IOException e) { | |||
e.printStackTrace(); | |||
} | |||
} | |||
@Override | |||
public void loadSSLCert(File file, char[] secret) throws GeneralSecurityException, IOException | |||
{ | |||
Spark.secure(file.getPath(), new String(secret), null, null); | |||
} | |||
@Override | |||
public synchronized void waitFor() throws InterruptedException | |||
{ | |||
while (this.isRunning) { | |||
this.wait(); | |||
} | |||
} | |||
@Override | |||
public void shutdown() | |||
{ | |||
this.isRunning = false; | |||
Spark.stop(); | |||
} | |||
@Override | |||
public String getAddress() | |||
{ | |||
try { | |||
return InetAddress.getLocalHost().getHostAddress() + ":" + port; | |||
} catch (UnknownHostException e) { | |||
return ":" + port; | |||
} | |||
} | |||
} |
@@ -0,0 +1,91 @@ | |||
package fr.slixe.mining.http.controller; | |||
import org.slf4j.Logger | |||
import org.slf4j.LoggerFactory | |||
import com.google.gson.Gson; | |||
import com.google.gson.JsonElement | |||
import com.google.gson.JsonObject | |||
import com.google.gson.JsonParser | |||
import com.google.inject.Inject; | |||
import fr.litarvan.paladin.http.Controller; | |||
import fr.litarvan.paladin.http.Request | |||
import fr.litarvan.paladin.http.routing.JsonBody | |||
import fr.slixe.mining.http.InvalidParameterException | |||
import fr.slixe.mining.service.DaemonService | |||
import fr.slixe.mining.service.StatsService | |||
import fr.slixe.mining.structures.FakeRPCResult | |||
import fr.slixe.mining.structures.Miner | |||
public class MainController extends Controller { | |||
private static final Logger log = LoggerFactory.getLogger("Controller"); | |||
@Inject | |||
private Gson gson | |||
@Inject | |||
private DaemonService daemon | |||
@Inject | |||
private StatsService stats | |||
@JsonBody(parse = false) | |||
def jsonRpc(Request request) | |||
{ | |||
String content = request.getParam("__jBdy"); | |||
JsonElement jsonElement = new JsonParser().parse(content) | |||
if (jsonElement == null || !jsonElement.isJsonObject()) { | |||
throw new InvalidParameterException("Invalid body") | |||
} | |||
JsonObject json = jsonElement.getAsJsonObject() | |||
String method = json.get("method").getAsString() | |||
Optional<Miner> optMiner = stats.findMiner(request.ip) | |||
Miner miner = optMiner.isPresent() ? optMiner.get() : stats.createMiner(request.ip) | |||
miner.markAlive() | |||
switch (method) { | |||
case "get_info": | |||
return new FakeRPCResult(daemon.info) | |||
case "getblocktemplate": | |||
miner.lastJobRequest = System.currentTimeMillis() | |||
return new FakeRPCResult(daemon.blockTemplate) | |||
case "submitblock": | |||
miner.lastBlockSent = System.currentTimeMillis() | |||
def jsonArray = json.get("params").getAsJsonArray() | |||
def x = new String[2]; | |||
x[0] = jsonArray.get(0).getAsString(); | |||
x[1] = jsonArray.get(1).getAsString(); | |||
return new FakeRPCResult(daemon.submitBlock(miner, x)) | |||
} | |||
[ | |||
message: "Invalid method" | |||
] | |||
} | |||
def job() | |||
{ | |||
daemon.blockTemplate | |||
} | |||
def info() | |||
{ | |||
daemon.info | |||
} | |||
def miners() | |||
{ | |||
stats.miners | |||
} | |||
def blocks() | |||
{ | |||
stats.blocks | |||
} | |||
} |
@@ -0,0 +1,154 @@ | |||
package fr.slixe.mining.service; | |||
import java.math.BigInteger; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import org.json.JSONObject; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import com.google.gson.Gson; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonParser; | |||
import com.google.inject.Inject; | |||
import com.google.inject.Singleton; | |||
import com.mashape.unirest.http.HttpResponse; | |||
import com.mashape.unirest.http.Unirest; | |||
import com.mashape.unirest.http.exceptions.UnirestException; | |||
import com.mashape.unirest.request.body.RequestBodyEntity; | |||
import fr.litarvan.paladin.PaladinConfig; | |||
import fr.slixe.mining.structures.BlockTemplate; | |||
import fr.slixe.mining.structures.Info; | |||
import fr.slixe.mining.structures.Miner; | |||
import fr.slixe.mining.structures.SubmitBlock; | |||
@Singleton | |||
public class DaemonService { | |||
private static final Logger log = LoggerFactory.getLogger("Daemon Service"); | |||
private final String daemonURL; | |||
private final RequestBodyEntity requestJob; | |||
private final RequestBodyEntity requestInfo; | |||
private final String walletAddress; | |||
private BlockTemplate blockTemplate; | |||
private Info info; | |||
@Inject | |||
private Gson gson; | |||
@Inject | |||
private StatsService stats; | |||
@Inject | |||
public DaemonService(PaladinConfig config) | |||
{ | |||
this.daemonURL = config.get("daemonURL") + "/json_rpc"; | |||
this.walletAddress = config.get("walletAddress"); | |||
int reserveSize = config.get("reserveSize", int.class); | |||
final Map<String, Object> params = new HashMap<>(); | |||
params.put("wallet_address", walletAddress); | |||
params.put("reserve_size", reserveSize); | |||
JSONObject body = body("getblocktemplate", params); | |||
this.requestJob = Unirest.post(daemonURL).header("Content-Type", "application/json").body(body); | |||
body = body("get_info"); | |||
this.requestInfo = Unirest.post(daemonURL).header("Content-Type", "application/json").body(body); | |||
} | |||
public void updateInfo() | |||
{ | |||
Info info = call(requestInfo, Info.class); | |||
if (info != null) { | |||
info.setWalletAddress(walletAddress); | |||
this.info = info; | |||
} | |||
} | |||
public void updateJob() | |||
{ | |||
BlockTemplate blockTemplate = call(requestJob, BlockTemplate.class); | |||
if (blockTemplate != null) { | |||
this.blockTemplate = blockTemplate; | |||
} | |||
} | |||
public SubmitBlock submitBlock(Miner miner, String[] x) | |||
{ | |||
final BigInteger height = getCurrentJob().getHeight(); | |||
JSONObject body = body("submitblock", x); | |||
RequestBodyEntity request = Unirest.post(this.daemonURL).header("Content-Type", "application/json").body(body); | |||
SubmitBlock block = call(request, SubmitBlock.class); | |||
if (block != null) { | |||
block.setMinerIp(miner.getIp()); | |||
block.setHeight(height); | |||
block.setTimestamp(System.currentTimeMillis()); | |||
stats.addSubmitBlock(block); | |||
miner.addBlocksFound(1); | |||
updateJob(); | |||
} | |||
return block; | |||
} | |||
private JSONObject body(String method) | |||
{ | |||
return body(method, null); | |||
} | |||
private JSONObject body(String method, Object params) | |||
{ | |||
JSONObject json = new JSONObject().put("jsonrpc", "2.0").put("id", 1).put("method", method); | |||
if (params != null) { | |||
json.put("params", params); | |||
} | |||
return json; | |||
} | |||
private <T> T call(RequestBodyEntity body, Class<T> clazz) | |||
{ | |||
HttpResponse<String> response; | |||
try { | |||
response = body.asString(); | |||
} catch (UnirestException e) { | |||
log.error("An error has occured while retrieving data from daemon!"); | |||
e.printStackTrace(); | |||
return null; | |||
} | |||
T data = null; | |||
String result = response.getBody(); | |||
if (result != null && !result.isEmpty()) | |||
{ | |||
JsonObject jsonResponse = new JsonParser().parse(result).getAsJsonObject(); | |||
if (jsonResponse.has("result")) | |||
{ | |||
data = gson.fromJson(jsonResponse.get("result").getAsJsonObject(), clazz); | |||
} else { | |||
log.warn("Error while retrieving data: {}", jsonResponse); | |||
} | |||
} | |||
return data; | |||
} | |||
public BlockTemplate getCurrentJob() | |||
{ | |||
return blockTemplate; | |||
} | |||
public Info getInfo() | |||
{ | |||
return info; | |||
} | |||
} |
@@ -0,0 +1,21 @@ | |||
package fr.slixe.mining.service; | |||
import fr.litarvan.paladin.ISessionManager; | |||
import fr.litarvan.paladin.Session; | |||
import fr.litarvan.paladin.http.Request; | |||
import fr.litarvan.paladin.http.Response; | |||
public class SessionManager implements ISessionManager { | |||
@Override | |||
public Session get(Request request, Response response) { | |||
return null; | |||
} | |||
@Override | |||
public long getExpirationDelay() { | |||
return 0; | |||
} | |||
@Override | |||
public void setExpirationDelay(long expirationDelay) {}} |
@@ -0,0 +1,48 @@ | |||
package fr.slixe.mining.service; | |||
import java.util.Collection; | |||
import java.util.HashMap; | |||
import java.util.LinkedHashSet; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import java.util.Set; | |||
import com.google.inject.Singleton; | |||
import fr.slixe.mining.structures.Miner; | |||
import fr.slixe.mining.structures.SubmitBlock; | |||
@Singleton | |||
public class StatsService { | |||
private final Set<SubmitBlock> blocks = new LinkedHashSet<>(); | |||
private final Map<String, Miner> miners = new HashMap<>(); | |||
public void addSubmitBlock(SubmitBlock block) | |||
{ | |||
this.blocks.add(block); | |||
} | |||
public Miner createMiner(String ip) | |||
{ | |||
Miner miner = new Miner(ip); | |||
miners.put(ip, miner); | |||
return miner; | |||
} | |||
public Optional<Miner> findMiner(String ip) | |||
{ | |||
return Optional.ofNullable(miners.get(ip)); | |||
} | |||
public Collection<SubmitBlock> getBlocks() | |||
{ | |||
return blocks; | |||
} | |||
public Collection<Miner> getMiners() | |||
{ | |||
return miners.values(); | |||
} | |||
} |
@@ -0,0 +1,79 @@ | |||
package fr.slixe.mining.structures; | |||
import java.math.BigInteger; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
import com.google.gson.annotations.SerializedName; | |||
public class BlockTemplate { | |||
@SerializedName("blocktemplate_blob") //used by Gson | |||
@JsonProperty("blocktemplate_blob") //used by Jackson | |||
private String templateBlob; | |||
@SerializedName("blockhashing_blob") | |||
@JsonProperty("blockhashing_blob") | |||
private String hashingBlob; | |||
@SerializedName("prev_hash") | |||
@JsonProperty("prev_hash") | |||
private String prevHash; | |||
@SerializedName("expected_reward") | |||
@JsonProperty("expected_reward") | |||
private BigInteger expectedReward; | |||
@SerializedName("reserved_offset") | |||
@JsonProperty("reserved_offset") | |||
private BigInteger reservedOffset; | |||
private BigInteger difficulty; | |||
private BigInteger height; | |||
private BigInteger epoch; | |||
private String status; | |||
public String getTemplateBlob() | |||
{ | |||
return templateBlob; | |||
} | |||
public String getHashingBlob() | |||
{ | |||
return hashingBlob; | |||
} | |||
public String getPrevHash() | |||
{ | |||
return prevHash; | |||
} | |||
public BigInteger getExpectedReward() | |||
{ | |||
return expectedReward; | |||
} | |||
public BigInteger getReservedOffset() | |||
{ | |||
return reservedOffset; | |||
} | |||
public BigInteger getDifficulty() | |||
{ | |||
return difficulty; | |||
} | |||
public BigInteger getHeight() | |||
{ | |||
return height; | |||
} | |||
public BigInteger getEpoch() | |||
{ | |||
return epoch; | |||
} | |||
public String getStatus() | |||
{ | |||
return status; | |||
} | |||
} |
@@ -0,0 +1,16 @@ | |||
package fr.slixe.mining.structures; | |||
public class FakeRPCResult<T> { | |||
private T result; | |||
public FakeRPCResult(T data) | |||
{ | |||
this.result = data; | |||
} | |||
public T getResult() | |||
{ | |||
return result; | |||
} | |||
} |
@@ -0,0 +1,140 @@ | |||
package fr.slixe.mining.structures; | |||
import java.math.BigInteger; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
import com.google.gson.annotations.SerializedName; | |||
public class Info { | |||
@SerializedName("alt_blocks_count") | |||
@JsonProperty("alt_blocks_count") | |||
private BigInteger altBlocksCount; | |||
private BigInteger difficulty; | |||
private BigInteger height; | |||
@SerializedName("stableheight") | |||
@JsonProperty("stableheight") | |||
private BigInteger stableHeight; | |||
@SerializedName("topoheight") | |||
@JsonProperty("topoheight") | |||
private BigInteger topoHeight; | |||
@SerializedName("averageblocktime50") | |||
@JsonProperty("averageblocktime50") | |||
private double averageBlockTime50; | |||
private BigInteger target; | |||
@SerializedName("target_height") | |||
@JsonProperty("target_height") | |||
private BigInteger targetHeight; | |||
private boolean testnet; | |||
@SerializedName("top_block_hash") | |||
@JsonProperty("top_block_hash") | |||
private String topBlockHash; | |||
@SerializedName("tx_count") | |||
@JsonProperty("tx_count") | |||
private BigInteger txCount; | |||
@SerializedName("tx_pool_size") | |||
@JsonProperty("tx_pool_size") | |||
private BigInteger txPoolSize; | |||
@SerializedName("dynamic_fee_per_kb") | |||
@JsonProperty("dynamic_fee_per_kb") | |||
private BigInteger dynamicFeePerKb; | |||
@SerializedName("total_supply") | |||
@JsonProperty("total_supply") | |||
private BigInteger totalSupply; | |||
@SerializedName("median_block_size") | |||
@JsonProperty("median_block_size") | |||
private BigInteger medianBlockSize; | |||
private String version; | |||
public BigInteger getAltBlocksCount() | |||
{ | |||
return altBlocksCount; | |||
} | |||
public BigInteger getDifficulty() | |||
{ | |||
return difficulty; | |||
} | |||
public BigInteger getHeight() | |||
{ | |||
return height; | |||
} | |||
public BigInteger getStableHeight() | |||
{ | |||
return stableHeight; | |||
} | |||
public BigInteger getTopoHeight() | |||
{ | |||
return topoHeight; | |||
} | |||
public double getAverageBlockTime50() | |||
{ | |||
return averageBlockTime50; | |||
} | |||
public BigInteger getTarget() | |||
{ | |||
return target; | |||
} | |||
public BigInteger getTargetHeight() | |||
{ | |||
return targetHeight; | |||
} | |||
public boolean isTestnet() | |||
{ | |||
return testnet; | |||
} | |||
public String getTopBlockHash() | |||
{ | |||
return topBlockHash; | |||
} | |||
public BigInteger getTxCount() | |||
{ | |||
return txCount; | |||
} | |||
public BigInteger getTxPoolSize() | |||
{ | |||
return txPoolSize; | |||
} | |||
public BigInteger getDynamicFeePerKb() | |||
{ | |||
return dynamicFeePerKb; | |||
} | |||
public BigInteger getTotalSupply() | |||
{ | |||
return totalSupply; | |||
} | |||
public BigInteger getMedianBlockSize() | |||
{ | |||
return medianBlockSize; | |||
} | |||
public String getVersion() | |||
{ | |||
return version; | |||
} | |||
private String walletAddress; | |||
public String getWalletAddress() | |||
{ | |||
return walletAddress; | |||
} | |||
public void setWalletAddress(String walletAddress) | |||
{ | |||
this.walletAddress = walletAddress; | |||
} | |||
} |
@@ -0,0 +1,81 @@ | |||
package fr.slixe.mining.structures; | |||
public class Miner { | |||
private String ip; | |||
private String workerName; | |||
private long lastJobRequest; | |||
private long lastBlockSent; | |||
private int blocksFound; | |||
private long lastUpdate; | |||
public Miner() {} | |||
public Miner(String ip) | |||
{ | |||
this.ip = ip; | |||
this.lastJobRequest = System.currentTimeMillis(); | |||
this.lastBlockSent = -1; | |||
this.blocksFound = 0; | |||
} | |||
public String getIp() | |||
{ | |||
return ip; | |||
} | |||
public String getWorkerName() | |||
{ | |||
return workerName; | |||
} | |||
public void setWorkerName(String workerName) | |||
{ | |||
this.workerName = workerName; | |||
} | |||
public long getLastJobRequest() | |||
{ | |||
return lastJobRequest; | |||
} | |||
public long getLastBlockSent() | |||
{ | |||
return lastBlockSent; | |||
} | |||
public int getBlocksFound() | |||
{ | |||
return blocksFound; | |||
} | |||
public void setLastJobRequest(long lastJobRequest) | |||
{ | |||
this.lastJobRequest = lastJobRequest; | |||
} | |||
public void setLastBlockSent(long lastBlockSent) | |||
{ | |||
this.lastBlockSent = lastBlockSent; | |||
} | |||
public void setBlockFounds(int blocksFound) | |||
{ | |||
this.blocksFound = blocksFound; | |||
} | |||
public void addBlocksFound(int blocks) | |||
{ | |||
this.blocksFound += blocks; | |||
} | |||
public void markAlive() | |||
{ | |||
this.lastUpdate = System.currentTimeMillis(); | |||
} | |||
public boolean isAlive() | |||
{ | |||
return this.lastUpdate + 30 * 1000 > System.currentTimeMillis(); | |||
} | |||
} |
@@ -0,0 +1,52 @@ | |||
package fr.slixe.mining.structures; | |||
import java.math.BigInteger; | |||
public class SubmitBlock { | |||
private String blid; | |||
private String status; | |||
private String minerIp; | |||
private BigInteger height; | |||
private long timestamp; | |||
public String getBlid() | |||
{ | |||
return blid; | |||
} | |||
public String getStatus() | |||
{ | |||
return status; | |||
} | |||
public String getMinerIp() | |||
{ | |||
return minerIp; | |||
} | |||
public void setMinerIp(String ip) | |||
{ | |||
this.minerIp = ip; | |||
} | |||
public BigInteger getHeight() | |||
{ | |||
return height; | |||
} | |||
public void setHeight(BigInteger height) | |||
{ | |||
this.height = height; | |||
} | |||
public long getTimestamp() | |||
{ | |||
return timestamp; | |||
} | |||
public void setTimestamp(long timestamp) | |||
{ | |||
this.timestamp = timestamp; | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
{ | |||
"port": 8080, | |||
"enableSSL": false, | |||
"keystoreFile": "", | |||
"keystorePassword": "", | |||
"daemonURL": "https://wallet.dero.io:443", | |||
"walletAddress": "dERokevAZEZVJ2N7o39VH81BXBqX9ojtncnPTDMyiVbmYiTXQY93AUCLcor9xsWCKWhYy25ja89ikZWXWab9kXRB7LYfUmbQyS", | |||
"reserveSize": 10, | |||
"job-interval": 200 | |||
} |
@@ -0,0 +1,29 @@ | |||
package config; | |||
import java.util.concurrent.TimeUnit | |||
import fr.litarvan.paladin.http.AcceptCrossOriginRequestsMiddleware | |||
import fr.slixe.mining.http.controller.MainController | |||
[ | |||
sessionDuration: TimeUnit.DAYS.toMillis(2), // 2 days | |||
/** | |||
* The app controllers, call them whatever you want to | |||
*/ | |||
controllers: [ | |||
main: MainController | |||
], | |||
routeMiddlewares: | |||
[ | |||
cors: AcceptCrossOriginRequestsMiddleware | |||
], | |||
/** | |||
* Global middlewares (applied on all routes) | |||
*/ | |||
globalMiddlewares: [ | |||
AcceptCrossOriginRequestsMiddleware | |||
] | |||
] |
@@ -0,0 +1,12 @@ | |||
package config; | |||
group '/api', { | |||
get '/job' | |||
get '/info' | |||
get '/miners' | |||
get '/blocks' | |||
}, [ | |||
action: "main" | |||
] | |||
post '/json_rpc', 'main:jsonRpc' |