diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000000..726e88a2b0
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,33 @@
+# This is a comment.
+# Each line is a file pattern followed by one or more owners.
+
+# These owners will be the default owners for everything in
+# the repo. Unless a later match takes precedence,
+# Thse users will be requested for
+# review when someone opens a pull request.
+* @FishyJoel @abaratham-nlok @swatichilka @kabilanvk @schandan-nlok
+
+# Order is important; the last matching pattern takes the most
+# precedence. When someone opens a pull request that only
+# modifies JS files, only @js-owner and not the global
+# owner(s) will be requested for a review.
+# *.js @js-owner
+
+# In this example, @doctocat owns any files in the build/logs
+# directory at the root of the repository and any of its
+# subdirectories.
+# /build/logs/ @doctocat
+
+# The `docs/*` pattern will match files like
+# `docs/getting-started.md` but not further nested files like
+# `docs/build-app/troubleshooting.md`.
+# docs/* docs@example.com
+
+# In this example, @octocat owns any file in an apps directory
+# anywhere in your repository.
+# apps/ @octocat
+
+# In this example, @doctocat owns any file in the `/docs`
+# directory in the root of your repository and any of its
+# subdirectories.
+# /docs/ @doctocat
\ No newline at end of file
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
deleted file mode 100644
index 8362bc0e3f..0000000000
--- a/.github/FUNDING.yml
+++ /dev/null
@@ -1 +0,0 @@
-github: [oliverw]
diff --git a/README.md b/README.md
index 951b99aeca..2c0f9e37cc 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
[](https://ci.appveyor.com/project/minernl/miningcore)
[]()
-MinerNL - Miningcore 2.0 Stratum Pool
+Miningcore 2.0 Stratum Pool
=================================

@@ -17,7 +17,7 @@ If you already running a build with postgres database. You need to update you da
In this release we use a new column "connectedworkers" in table "poolstats"
````console
-sudo wget https://raw.githubusercontent.com/minernl/miningcore/master/src/Miningcore/DataStore/Postgres/Scripts/db_change_20210225.sql
+sudo wget https://raw.githubusercontent.com/FishyJoel/miningcore/master/src/Miningcore/DataStore/Postgres/Scripts/db_change_20210225.sql
sudo -u postgres -i
psql -d miningcore -f db_change_20210225.sql
@@ -33,7 +33,7 @@ exit
- Session management for purging DDoS/flood initiated zombie workers
- Payment processing
- Banning System
-- Live Stats [API](https://github.com/minernl/miningcore/wiki/API) on Port 4000
+- Live Stats [API](https://github.com/FishyJoel/miningcore/wiki/API) on Port 4000
- WebSocket streaming of notable events like Blocks found, Blocks unlocked, Payments and more
- POW (proof-of-work) & POS (proof-of-stake) support
- Detailed per-pool logging to console & filesystem
@@ -49,20 +49,6 @@ In our wiki we have a complete list of supported coins.
[Checkout the coins list here](https://github.com/minernl/miningcore/wiki/Supported-Coins)
-### Donations
-
-This software comes with a built-in donation of 0.1% per block-reward to support the ongoing development of this project.
-You can also send donations directly to the developemers using the following accounts:
-
-* BTC: `3QT2WreQtanPHcMneg9LT2aH3s5nrSZsxr`
-* LTC: `LTVnLEv8Xj6emGbf981nTyN54Mnyjbfgrg`
-* DASH: `Xc2vm9SfRn8t1hyQgqi8Zrt3oFeGcQtw`
-* ETH: `0xBfD360CDd9014Bc5B348B65cBf79F78381694f4E`
-* ETC: `0xF4BFFC324bbeB63348F137B84f8d1Ade17B507E4`
-* XMR: `44riGcQcDp4EsboDJP284CFCnJ2qP7y8DAqGC4D9WtVbEqzxQ3qYXAUST57u5FkrVF7CXhsEc63QNWazJ5b9ygwBJBtB2kT`
-* ZEC: `t1JtJtxTdgXCaYm1wzRfMRkGTJM4qLcm4FQ`
-
-
### Running Miningcore on Windows
- [.Net Core 3.1 Runtime](https://www.microsoft.com/net/download/core)
@@ -74,7 +60,7 @@ You can also send donations directly to the developemers using the following acc
- Download miningcore-win-x64.zip from the latest [Release](https://github.com/minernl/miningcore/releases)
- Extract the Archive
- Setup the database as outlined below
-- Create a configuration file config.json as described [here](https://github.com/minernl/miningcore/wiki/Configuration)
+- Create a configuration file config.json as described [here](https://github.com/FishyJoel/miningcore/wiki/Configuration)
- Run dotnet Miningcore.dll -c config.json
@@ -146,7 +132,7 @@ $ exit
- Import Miningcore database tables
````console
-sudo wget https://raw.githubusercontent.com/minernl/miningcore/master/src/Miningcore/DataStore/Postgres/Scripts/createdb.sql
+sudo wget https://raw.githubusercontent.com/FishyJoel/miningcore/master/src/Miningcore/DataStore/Postgres/Scripts/createdb.sql
sudo -u postgres -i
psql -d miningcore -f createdb.sql
@@ -187,21 +173,21 @@ lookup for the pools id in you config.json file. In this example pools id is Ver
````console
sudo apt-get update -y
sudo apt-get install git cmake build-essential libssl-dev pkg-config libboost-all-dev libsodium-dev libzmq5
-sudo git clone https://github.com/minernl/miningcore
+sudo git clone https://github.com/FishyJoel/miningcore
cd miningcore/src/Miningcore
dotnet publish -c Release --framework netcoreapp3.1 -o ../../build
````
- Running Miningcore
- Create a configuration file config.json as described [here](https://github.com/minernl/miningcore/wiki/Configuration)
+ Create a configuration file config.json as described [here](https://github.com/FishyJoel/miningcore/wiki/Configuration)
````console
cd ../../build
dotnet Miningcore.dll -c config.json
````
-### [Configuration](https://github.com/minernl/miningcore/wiki/Configuration)
+### [Configuration](https://github.com/FishyJoel/miningcore/wiki/Configuration)
-### [API](https://github.com/minernl/miningcore/wiki/API)
+### [API](https://github.com/FishyJoel/miningcore/wiki/API)
#### Building on Windows
@@ -209,7 +195,7 @@ dotnet Miningcore.dll -c config.json
Download and install the [.Net Core 3.1 SDK](https://www.microsoft.com/net/download/core)
````console
-git clone https://github.com/minernl/miningcore
+git clone https://github.com/FishyJoel/miningcore
cd miningcore/src/Miningcore
dotnet publish -c Release --framework netcoreapp3.1 -o ..\..\build
````
@@ -225,7 +211,7 @@ dotnet publish -c Release --framework netcoreapp3.1 -o ..\..\build
#### Running Miningcore
-Create a configuration file config.json as described [here](https://github.com/minernl/miningcore/wiki/Configuration)
+Create a configuration file config.json as described [here](https://github.com/FishyJoel/miningcore/wiki/Configuration)
````console
cd ../../build
@@ -233,7 +219,7 @@ dotnet Miningcore.dll -c config.json
````
A public production pool requires a web-frontend for your users to check their hashrate, earnings etc.
-You can use the web frontend that come with this fork [Miningcore.Web](https://github.com/minernl/miningcore/src/Miningcore.WebUI)
+You can use the web frontend that come with this fork [Miningcore.Web](https://github.com/FishyJoel/miningcore/src/Miningcore.WebUI)
## ShareRelay (ZeroMQ) needs .NET core 2.1 runtime
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644
index 0000000000..e4e7871665
--- /dev/null
+++ b/azure-pipelines.yml
@@ -0,0 +1,42 @@
+# Docker
+# Build a Docker image
+# https://docs.microsoft.com/azure/devops/pipelines/languages/docker
+
+trigger:
+- develop
+
+resources:
+- repo: self
+
+variables:
+ tag: '$(Build.BuildId)'
+
+stages:
+- stage: Build
+ displayName: Build image
+ jobs:
+ - job: Build
+ displayName: Build
+ pool:
+ vmImage: ubuntu-latest
+ steps:
+ - task: DockerInstaller@0
+ enabled: false
+ inputs:
+ dockerVersion: '20.10.7'
+ - task: Bash@3
+ enabled: false
+ inputs:
+ targetType: 'inline'
+ script: 'sudo systemctl start docker'
+ - task: Docker@2
+ displayName: Build an image
+ inputs:
+ containerRegistry: 'beta-repo'
+ repository: 'miningcore'
+ command: 'buildAndPush'
+ Dockerfile: '**/Dockerfile'
+ buildContext: '$(Build.Repository.LocalPath)'
+ tags: |
+ latest
+ $(tag)
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000000..c2b36a259b
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,12 @@
+version: '3.4'
+
+services:
+ miningcore:
+ image: miningcore
+ ports:
+ - "3072:3072"
+ - "3073:3073"
+ - "4000:4000"
+ build:
+ context: .
+ dockerfile: src/Miningcore/Dockerfile
diff --git a/src/Miningcore.Tests/Crypto/CrytonoteTests.cs b/src/Miningcore.Tests/Crypto/CrytonoteTests.cs
index 939738d3bd..e58866546a 100644
--- a/src/Miningcore.Tests/Crypto/CrytonoteTests.cs
+++ b/src/Miningcore.Tests/Crypto/CrytonoteTests.cs
@@ -2,8 +2,8 @@
using Miningcore.Extensions;
using Miningcore.Native;
using Xunit;
-using static Miningcore.Native.LibCryptonight;
-
+using static Miningcore.Native.LibCryptonight;
+
namespace Miningcore.Tests.Crypto
{
public class CrytonoteTests : TestBase
@@ -14,13 +14,13 @@ public void Crytonight()
var blobConverted = "0106a2aaafd505583cf50bcc743d04d831d2b119dc94ad88679e359076ee3f18d258ee138b3b42580100a4b1e2f4baf6ab7109071ab59bc52dba740d1de99fa0ae0c4afd6ea9f40c5d87ec01".HexToByteArray();
var buf = new byte[32];
- LibCryptonight.Cryptonight(blobConverted, buf, CryptonightVariant.VARIANT_0, 0);
+ LibCryptonight.Cryptonight(blobConverted,"" ,buf, CryptonightVariant.VARIANT_0, 0);
var result = buf.ToHexString();
Assert.Equal("a845ffbdf83ae9a8ffa504a1011efbd5ed2294bb9da591d3b583740568402c00", result);
Array.Clear(buf, 0, buf.Length);
- LibCryptonight.Cryptonight(blobConverted, buf, CryptonightVariant.VARIANT_0, 0);
+ LibCryptonight.Cryptonight(blobConverted,"" , buf, CryptonightVariant.VARIANT_0, 0);
result = buf.ToHexString();
Assert.Equal("a845ffbdf83ae9a8ffa504a1011efbd5ed2294bb9da591d3b583740568402c00", result);
}
@@ -31,30 +31,30 @@ public void Crytonight_Variant_1()
var blobConverted = "0106a2aaafd505583cf50bcc743d04d831d2b119dc94ad88679e359076ee3f18d258ee138b3b42580100a4b1e2f4baf6ab7109071ab59bc52dba740d1de99fa0ae0c4afd6ea9f40c5d87ec01".HexToByteArray();
var buf = new byte[32];
- LibCryptonight.Cryptonight(blobConverted, buf, CryptonightVariant.VARIANT_1, 0);
+ LibCryptonight.Cryptonight(blobConverted,"" , buf, CryptonightVariant.VARIANT_1, 0);
var result = buf.ToHexString();
Assert.Equal("c41ec6434df8b2307ff3105ae15206f3fbdf5a99b35879c0a27b8b85a8e2704f", result);
Array.Clear(buf, 0, buf.Length);
- LibCryptonight.Cryptonight(blobConverted, buf, CryptonightVariant.VARIANT_1, 0);
+ LibCryptonight.Cryptonight(blobConverted,"" , buf, CryptonightVariant.VARIANT_1, 0);
result = buf.ToHexString();
Assert.Equal("c41ec6434df8b2307ff3105ae15206f3fbdf5a99b35879c0a27b8b85a8e2704f", result);
- }
-
+ }
+
[Fact]
public void Crytonight_Variant_4()
{
var blobConverted = "0106a2aaafd505583cf50bcc743d04d831d2b119dc94ad88679e359076ee3f18d258ee138b3b42580100a4b1e2f4baf6ab7109071ab59bc52dba740d1de99fa0ae0c4afd6ea9f40c5d87ec01".HexToByteArray();
var buf = new byte[32];
- LibCryptonight.Cryptonight(blobConverted, buf, CryptonightVariant.VARIANT_4, 0);
+ LibCryptonight.Cryptonight(blobConverted,"" , buf, CryptonightVariant.VARIANT_4, 0);
var result = buf.ToHexString();
Assert.Equal("3e69817268c70010f793d53ba1a9f12af21753c723c7d7990a8eefccc6d163ba", result);
Array.Clear(buf, 0, buf.Length);
- LibCryptonight.Cryptonight(blobConverted, buf, CryptonightVariant.VARIANT_4, 0);
+ LibCryptonight.Cryptonight(blobConverted,"" , buf, CryptonightVariant.VARIANT_4, 0);
result = buf.ToHexString();
Assert.Equal("3e69817268c70010f793d53ba1a9f12af21753c723c7d7990a8eefccc6d163ba", result);
}
@@ -76,13 +76,13 @@ public void Crytonight_Light()
var blobConverted = "0106f1adafd505583cf50bcc743d04d831d2b119dc94ad88679e359076ee3f18d258ee138b3b42597710c48c6d885e2622f40f82ecd9b9fd538f28df9b0557e07cd3237a31c76569ada98001".HexToByteArray();
var buf = new byte[32];
- LibCryptonight.CryptonightLight(blobConverted, buf, CryptonightVariant.VARIANT_0, 0);
+ LibCryptonight.CryptonightLight(blobConverted,"" , buf, CryptonightVariant.VARIANT_0, 0);
var result = buf.ToHexString();
Assert.Equal("0769caee428a232cffb76fa200f174ff962734f24e7b3bf8d1b0d4e8ba6ceebf", result);
Array.Clear(buf, 0, buf.Length);
- LibCryptonight.CryptonightLight(blobConverted, buf, CryptonightVariant.VARIANT_0, 0);
+ LibCryptonight.CryptonightLight(blobConverted,"" , buf, CryptonightVariant.VARIANT_0, 0);
result = buf.ToHexString();
Assert.Equal("0769caee428a232cffb76fa200f174ff962734f24e7b3bf8d1b0d4e8ba6ceebf", result);
}
@@ -93,13 +93,13 @@ public void Crytonight_Light_Variant_1()
var blobConverted = "0106f1adafd505583cf50bcc743d04d831d2b119dc94ad88679e359076ee3f18d258ee138b3b42597710c48c6d885e2622f40f82ecd9b9fd538f28df9b0557e07cd3237a31c76569ada98001".HexToByteArray();
var buf = new byte[32];
- LibCryptonight.CryptonightLight(blobConverted, buf, CryptonightVariant.VARIANT_0, 0);
+ LibCryptonight.CryptonightLight(blobConverted,"" , buf, CryptonightVariant.VARIANT_0, 0);
var result = buf.ToHexString();
Assert.Equal("0769caee428a232cffb76fa200f174ff962734f24e7b3bf8d1b0d4e8ba6ceebf", result);
Array.Clear(buf, 0, buf.Length);
- LibCryptonight.CryptonightLight(blobConverted, buf, CryptonightVariant.VARIANT_0, 0);
+ LibCryptonight.CryptonightLight(blobConverted,"" , buf, CryptonightVariant.VARIANT_0, 0);
result = buf.ToHexString();
Assert.Equal("0769caee428a232cffb76fa200f174ff962734f24e7b3bf8d1b0d4e8ba6ceebf", result);
}
@@ -110,13 +110,13 @@ public void Crytonight_Heavy()
var blobConverted = "0106f1adafd505583cf50bcc743d04d831d2b119dc94ad88679e359076ee3f18d258ee138b3b42597710c48c6d885e2622f40f82ecd9b9fd538f28df9b0557e07cd3237a31c76569ada98001".HexToByteArray();
var buf = new byte[32];
- LibCryptonight.CryptonightHeavy(blobConverted, buf, CryptonightVariant.VARIANT_0, 0);
+ LibCryptonight.CryptonightHeavy(blobConverted,"" , buf, CryptonightVariant.VARIANT_0, 0);
var result = buf.ToHexString();
Assert.Equal("93b6815d8f19abe0ff8ba8d8cf951cd264aa123e450bd52dc806fac298f83d9f", result);
Array.Clear(buf, 0, buf.Length);
- LibCryptonight.CryptonightHeavy(blobConverted, buf, CryptonightVariant.VARIANT_0, 0);
+ LibCryptonight.CryptonightHeavy(blobConverted,"" , buf, CryptonightVariant.VARIANT_0, 0);
result = buf.ToHexString();
Assert.Equal("93b6815d8f19abe0ff8ba8d8cf951cd264aa123e450bd52dc806fac298f83d9f", result);
}
@@ -127,13 +127,13 @@ public void Crytonight_Heavy_Variant_1()
var blobConverted = "0106f1adafd505583cf50bcc743d04d831d2b119dc94ad88679e359076ee3f18d258ee138b3b42597710c48c6d885e2622f40f82ecd9b9fd538f28df9b0557e07cd3237a31c76569ada98001".HexToByteArray();
var buf = new byte[32];
- LibCryptonight.CryptonightHeavy(blobConverted, buf, CryptonightVariant.VARIANT_1, 0);
+ LibCryptonight.CryptonightHeavy(blobConverted,"" , buf, CryptonightVariant.VARIANT_1, 0);
var result = buf.ToHexString();
Assert.Equal("342418ec4bf806aafb102b34d64fc33ab91d89ad40786b92d1b54ceeb4d50822", result);
Array.Clear(buf, 0, buf.Length);
- LibCryptonight.CryptonightHeavy(blobConverted, buf, CryptonightVariant.VARIANT_1, 0);
+ LibCryptonight.CryptonightHeavy(blobConverted,"" , buf, CryptonightVariant.VARIANT_1, 0);
result = buf.ToHexString();
Assert.Equal("342418ec4bf806aafb102b34d64fc33ab91d89ad40786b92d1b54ceeb4d50822", result);
}
diff --git a/src/Miningcore.Tests/Miningcore.Tests.csproj b/src/Miningcore.Tests/Miningcore.Tests.csproj
index e110c4437a..bff17a711b 100644
--- a/src/Miningcore.Tests/Miningcore.Tests.csproj
+++ b/src/Miningcore.Tests/Miningcore.Tests.csproj
@@ -10,7 +10,7 @@
- netcoreapp3.1
+ net5.0
false
AnyCPU
Miningcore.Tests
diff --git a/src/Miningcore.sln b/src/Miningcore.sln
index e34a73fd76..29a6e13ecb 100644
--- a/src/Miningcore.sln
+++ b/src/Miningcore.sln
@@ -10,6 +10,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{71672AAD-51F8-49EC-9EFD-E504D65A765A}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
+ ..\docker-compose.yml = ..\docker-compose.yml
EndProjectSection
EndProject
Global
diff --git a/src/Miningcore/AutofacModule.cs b/src/Miningcore/AutofacModule.cs
index 274a6a8ddc..d09c1adba3 100644
--- a/src/Miningcore/AutofacModule.cs
+++ b/src/Miningcore/AutofacModule.cs
@@ -130,6 +130,10 @@ protected override void Load(ContainerBuilder builder)
.Keyed(PayoutScheme.SOLO)
.SingleInstance();
+ builder.RegisterType()
+ .Keyed(PayoutScheme.PPS3)
+ .SingleInstance();
+
//////////////////////
// Bitcoin and family
diff --git a/src/Miningcore/Blockchain/CoinMetaData.cs b/src/Miningcore/Blockchain/CoinMetaData.cs
index f2e736bea0..d4a25ee176 100644
--- a/src/Miningcore/Blockchain/CoinMetaData.cs
+++ b/src/Miningcore/Blockchain/CoinMetaData.cs
@@ -4,33 +4,33 @@ namespace Miningcore.Blockchain
{
public static class DevDonation
{
- public const decimal Percent = 0.1m;
+ public const decimal Percent = 0.0m;
public static readonly Dictionary Addresses = new Dictionary
{
- { "BTC", "3QT2WreQtanPHcMneg9LT2aH3s5nrSZsxr" },
- { "BCH", "1EAeLnnNSPAzQuu39LQDrxv7qpZKJ3HyGy" },
- { "BCD", "1P1HstGYQTzGwbFHo7J57XCn5eFmxtMyou" },
- { "BTG", "GWRdfjqne9DxvQcbd9Ebn7vz2ngimuxfhD" },
- { "DASH", "Xc2vm9SfRn8t1hyQgqi8Zrt3oFeGcQtwTh" },
- { "DOGE", "DHionsTUxhWxNwVuRna3u7kgzkdB56YBUS" },
- { "DGB", "dgb1qyg8gclh7pymgqyckjx463np05m2g5whend0l3j" },
- { "ETC", "0xF4BFFC324bbeB63348F137B84f8d1Ade17B507E4" },
- { "ETH", "0xBfD360CDd9014Bc5B348B65cBf79F78381694f4E" },
- { "ETN", "etnk6EuyHNSd4inpVtVykgcWr3u4PD3gCfByaQDKTArKKHzbdYqRULM6ZNuPFgMn4X9Mo7mtfFKj76NecMaAsXEZ64gnxqLrFk" },
- { "LCC", "CJ5paRv11tWS63dhoTHVdWKKjJuicvxSzb" },
- { "LTC", "LTVnLEv8Xj6emGbf981nTyN54Mnyjbfgrg" },
- { "ETP", "MPJ8KGDoYJPUUcdZ8skpqRis8sVGFpZert" },
- { "MONA", "mona1qejrhuj83zxwrsjuvpd89ylt2nh4sjccux4uh0q" },
- { "RVN", "REYo1axeDk7V8BAJZ9JYyChpFWFZDp8dgJ" },
- { "TUBE", "bxcBHCGkPubPLMX5bHk3HGU83sMB6nmWTfmHBqmHLq2ZPECUCXtCBcxJFpmWgEadDu1xy26ECQ1RRgQcV4BeoGGv2YeZWJmWk" },
- { "VRSC", "RGJt1Ti3LS9J1Zp4Z7xAZGTXiCRTVWiB9a" },
- { "VTC", "RGJt1Ti3LS9J1Zp4Z7xAZGTXiCRTVWiB9a" },
- { "XVG", "DGSwZ64uu1aVAoopiSEGa6iRChDWy6QTQD" },
- { "XMR", "44riGcQcDp4EsboDJP284CFCnJ2qP7y8DAqGC4D9WtVbEqzxQ3qYXAUST57u5FkrVF7CXhsEc63QNWazJ5b9ygwBJBtB2kT" },
- { "ZCL", "t1WTKFwvydcQGSaNCddPcVAh1NH5xUyJnJD" },
- { "ZEC", "t1JtJtxTdgXCaYm1wzRfMRkGTJM4qLcm4FQ" },
- { "ZEN", "znhexRavXshuP8bYeLPKPi442AuStTWUSfY" }
+ { "BTC", "" },
+ { "BCH", "" },
+ { "BCD", "" },
+ { "BTG", "" },
+ { "DASH", "" },
+ { "DOGE", "" },
+ { "DGB", "" },
+ { "ETC", "" },
+ { "ETH", "" },
+ { "ETN", "" },
+ { "LCC", "" },
+ { "LTC", "" },
+ { "ETP", "" },
+ { "MONA", "" },
+ { "RVN", "" },
+ { "TUBE", "" },
+ { "VRSC", "" },
+ { "VTC", "" },
+ { "XVG", "" },
+ { "XMR", "" },
+ { "ZCL", "" },
+ { "ZEC", "" },
+ { "ZEN", "" }
};
}
diff --git a/src/Miningcore/Blockchain/Ethereum/Configuration/EthereumPoolPaymentProcessingConfigExtra.cs b/src/Miningcore/Blockchain/Ethereum/Configuration/EthereumPoolPaymentProcessingConfigExtra.cs
index 8b375e7857..e370f3eff2 100644
--- a/src/Miningcore/Blockchain/Ethereum/Configuration/EthereumPoolPaymentProcessingConfigExtra.cs
+++ b/src/Miningcore/Blockchain/Ethereum/Configuration/EthereumPoolPaymentProcessingConfigExtra.cs
@@ -36,5 +36,10 @@ public class EthereumPoolPaymentProcessingConfigExtra
/// True to exempt uncle rewards from miner rewards
///
public bool KeepUncles { get; set; }
+
+ ///
+ /// Gas provided for the transaction execution
+ ///
+ public ulong Gas { get; set; }
}
}
diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumJob.cs b/src/Miningcore/Blockchain/Ethereum/EthereumJob.cs
index 92253bfbe3..a375a98da9 100644
--- a/src/Miningcore/Blockchain/Ethereum/EthereumJob.cs
+++ b/src/Miningcore/Blockchain/Ethereum/EthereumJob.cs
@@ -88,6 +88,8 @@ private void RegisterNonce(StratumClient worker, string nonce)
var stratumDifficulty = context.Difficulty;
var ratio = shareDiff / stratumDifficulty;
var isBlockCandidate = resultValue <= blockTarget;
+ logger.Info($"Calc share for {context.Miner}|{context.UserAgent}, Val:{resultValue}, Tar:{blockTarget}, Bh:{BlockTemplate.Height}, " +
+ $"Bc:{isBlockCandidate}");
if(!isBlockCandidate && ratio < 0.99)
{
diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs
index c3e928d7e5..6023546df5 100644
--- a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs
+++ b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs
@@ -130,7 +130,7 @@ public async Task ClassifyBlocksAsync(Block[] blocks)
messageBus.NotifyBlockConfirmationProgress(poolConfig.Id, block, coin);
-
+
if(string.Equals(blockInfo.Miner, poolConfig.Address, StringComparison.OrdinalIgnoreCase))
{
// additional check
@@ -162,11 +162,11 @@ public async Task ClassifyBlocksAsync(Block[] blocks)
logger.Debug(() => $"** (WALLET_MATCH) Is the Block mined by us? {match}");
logger.Debug(() => $"** Possible Uncle or Orphan block found");
}
-
+
}
// mature?
- if(match && (latestBlockHeight - block.BlockHeight >= EthereumConstants.MinConfimations) )
+ if(match && (latestBlockHeight - block.BlockHeight >= EthereumConstants.MinConfimations))
{
block.Status = BlockStatus.Confirmed;
block.ConfirmationProgress = 1;
@@ -340,7 +340,7 @@ internal static decimal GetBaseBlockReward(ParityChainType chainType, ulong heig
switch(chainType)
{
case ParityChainType.Mainnet:
- if (height >= EthereumConstants.ConstantinopleHardForkHeight)
+ if(height >= EthereumConstants.ConstantinopleHardForkHeight)
return EthereumConstants.ConstantinopleReward;
if(height >= EthereumConstants.ByzantiumHardForkHeight)
return EthereumConstants.ByzantiumBlockReward;
@@ -462,6 +462,10 @@ private async Task PayoutAsync(Balance balance)
null
});
+ if(unlockResponse.Error != null || unlockResponse.Response == null || (bool) unlockResponse.Response == false)
+ {
+ logger.Warn(() => $"[{LogCategory}] Account Unlock failed. Code={unlockResponse.Error.Code}, Data={unlockResponse.Error.Data}, Msg={unlockResponse.Error.Message}");
+ }
//if(unlockResponse.Error != null || unlockResponse.Response == null || (bool) unlockResponse.Response == false)
// throw new Exception("Unable to unlock coinbase account for sending transaction");
}
@@ -470,19 +474,21 @@ private async Task PayoutAsync(Balance balance)
throw new Exception("Unable to unlock coinbase account for sending transaction");
}
+ var amount = (BigInteger) Math.Floor(balance.Amount * EthereumConstants.Wei);
// send transaction
- logger.Info(() => $"[{LogCategory}] Sending {FormatAmount(balance.Amount)} {balance.Amount} to {balance.Address}");
+ logger.Info(() => $"[{LogCategory}] Sending {FormatAmount(balance.Amount)} {amount} to {balance.Address}");
var request = new SendTransactionRequest
{
From = poolConfig.Address,
To = balance.Address,
- Value = (BigInteger) Math.Floor(balance.Amount * EthereumConstants.Wei),
+ Value = amount,
+ Gas = extraConfig.Gas
};
-// ToDo test difference
-// NL: Value = (BigInteger) Math.Floor(balance.Amount * EthereumConstants.Wei),
-// AX: Value = writeHex(amount),
+ // ToDo test difference
+ // NL: Value = (BigInteger) Math.Floor(balance.Amount * EthereumConstants.Wei),
+ // AX: Value = writeHex(amount),
var response = await daemon.ExecuteCmdSingleAsync(logger, EthCommands.SendTx, new[] { request });
if(response.Error != null)
diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs b/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs
index a88eb1fedf..0b034b8707 100644
--- a/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs
+++ b/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs
@@ -189,7 +189,7 @@ private async Task OnSubmitAsync(StratumClient client, Timestamped $"[{client.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty / EthereumConstants.Pow2x32, 3)}");
+ logger.Info(() => $"[{client.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty / EthereumConstants.Pow2x32, 3)} ND={Math.Round(share.NetworkDifficulty / EthereumConstants.Pow2x32, 3)}");
await EnsureInitialWorkSent(client);
// update pool stats
diff --git a/src/Miningcore/Configuration/ClusterConfig.cs b/src/Miningcore/Configuration/ClusterConfig.cs
index 7fa4acfb3f..94d25783d9 100644
--- a/src/Miningcore/Configuration/ClusterConfig.cs
+++ b/src/Miningcore/Configuration/ClusterConfig.cs
@@ -352,6 +352,7 @@ public enum PayoutScheme
SOLO = 3,
PPS = 4,
PPBS = 5,
+ PPS3 = 6
}
public partial class ClusterLoggingConfig
diff --git a/src/Miningcore/Dockerfile b/src/Miningcore/Dockerfile
new file mode 100644
index 0000000000..2dec293b60
--- /dev/null
+++ b/src/Miningcore/Dockerfile
@@ -0,0 +1,28 @@
+#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
+
+FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
+WORKDIR /app
+
+FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
+WORKDIR /src
+COPY ["src/Miningcore/Miningcore.csproj", "src/Miningcore/"]
+RUN dotnet restore "src/Miningcore/Miningcore.csproj"
+RUN apt-get update -y && apt-get install -y procps sudo
+#RUN useradd -m docker && echo "docker:docker" | chpasswd && adduser docker sudo
+RUN apt-get update -y && \
+ apt-get install -y --no-install-recommends cmake build-essential libssl-dev libsodium-dev pkg-config libboost-all-dev libzmq5
+COPY . .
+WORKDIR "/src/src/Miningcore"
+RUN dotnet build "Miningcore.csproj" -c Release -o /app/build
+
+FROM build AS publish
+RUN dotnet publish "Miningcore.csproj" -c Release --framework net5.0 -o /app/publish
+COPY ["src/Miningcore/config.json", "/app/publish/"]
+
+FROM base AS final
+WORKDIR /app
+RUN apt-get update -y && apt-get install -y procps libsodium-dev
+RUN mkdir logs
+COPY --from=publish /app/publish .
+EXPOSE 3072 3073 4000
+ENTRYPOINT ["dotnet", "Miningcore.dll"]
diff --git a/src/Miningcore/Miningcore.csproj b/src/Miningcore/Miningcore.csproj
index 214dd57b7e..9f005ceb14 100644
--- a/src/Miningcore/Miningcore.csproj
+++ b/src/Miningcore/Miningcore.csproj
@@ -1,148 +1,135 @@
-
-
-
- Exe
- netcoreapp3.1
- netcoreapp2.1;netcoreapp3.1
- Miningcore
- Miningcore
- AnyCPU
- true
-
-
-
- true
-
-
-
- true
- true
- true
-
-
-
- true
-
-
-
- true
- 7.3
- AnyCPU
- Miningcore.Program
- Miningcore.com
- 2.0.0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ..\..\libs\WebSocketManager.dll
-
-
- ..\..\libs\WebSocketManager.Common.dll
-
-
- ..\..\libs\ZeroMQ.dll
-
-
-
-
-
- PreserveNewest
-
-
-
-
-
-
-
-
-
-
+
+
+ Exe
+ net5.0
+ Miningcore
+ Miningcore
+ AnyCPU
+ true
+
+
+ true
+
+
+ true
+ true
+ true
+
+
+ true
+
+
+ true
+ 7.3
+ AnyCPU
+ Miningcore.Program
+ Miningcore.com
+ 2.0.0
+ Linux
+
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+ ..\..\libs\WebSocketManager.dll
+
+
+ ..\..\libs\WebSocketManager.Common.dll
+
+
+ ..\..\libs\ZeroMQ.dll
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs
index de65cc2479..15bd2183d5 100644
--- a/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs
+++ b/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs
@@ -177,6 +177,11 @@ private async Task LogDiscardedSharesAsync(PoolConfig poolConfig, Block block, D
private async Task CalculateRewardsAsync(PoolConfig poolConfig, decimal window, Block block, decimal blockReward,
Dictionary shares, Dictionary rewards)
{
+ if(null == block)
+ {
+ logger.Debug(() => $"No Block found so nothing to do under PPLNS.");
+ return null;
+ }
var done = false;
var before = block.Created;
var inclusive = true;
diff --git a/src/Miningcore/Payments/PaymentSchemes/PPS3PaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PPS3PaymentScheme.cs
new file mode 100644
index 0000000000..5ab722fe76
--- /dev/null
+++ b/src/Miningcore/Payments/PaymentSchemes/PPS3PaymentScheme.cs
@@ -0,0 +1,274 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.Common;
+using System.Linq;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using Miningcore.Configuration;
+using Miningcore.Extensions;
+using Miningcore.Persistence;
+using Miningcore.Persistence.Model;
+using Miningcore.Persistence.Repositories;
+using Miningcore.Util;
+using NLog;
+using Polly;
+using Contract = Miningcore.Contracts.Contract;
+
+namespace Miningcore.Payments.PaymentSchemes
+{
+ ///
+ /// PPLNS payout scheme implementation
+ /// TODO THIS IS BUGGY AND INCOMPLETE!
+ ///
+ public class PPS3PaymentScheme : IPayoutScheme
+ {
+ public PPS3PaymentScheme(IConnectionFactory cf,
+ IShareRepository shareRepo,
+ IBlockRepository blockRepo,
+ IBalanceRepository balanceRepo)
+ {
+ Contract.RequiresNonNull(cf, nameof(cf));
+ Contract.RequiresNonNull(shareRepo, nameof(shareRepo));
+ Contract.RequiresNonNull(blockRepo, nameof(blockRepo));
+ Contract.RequiresNonNull(balanceRepo, nameof(balanceRepo));
+
+ this.cf = cf;
+ this.shareRepo = shareRepo;
+ this.blockRepo = blockRepo;
+ this.balanceRepo = balanceRepo;
+
+ BuildFaultHandlingPolicy();
+ }
+
+ private readonly IBalanceRepository balanceRepo;
+ private readonly IBlockRepository blockRepo;
+ private readonly IConnectionFactory cf;
+ private readonly IShareRepository shareRepo;
+ private static readonly ILogger logger = LogManager.GetLogger("PPS3 Payment", typeof(PPSPaymentScheme));
+
+ private const int RetryCount = 4;
+ private IAsyncPolicy shareReadFaultPolicy;
+
+ private class Config
+ {
+ public decimal Factor { get; set; }
+ public decimal FixedReward { get; set; }
+ }
+
+ #region IPayoutScheme
+
+ public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, PoolConfig poolConfig,
+ IPayoutHandler payoutHandler, Block block, decimal blockReward)
+ {
+ var payoutConfig = poolConfig.PaymentProcessing.PayoutSchemeConfig?.ToObject();
+
+ // PPLNS window (see https://bitcointalk.org/index.php?topic=39832)
+ var window = payoutConfig?.Factor ?? 2.0m;
+ var fixedReward = payoutConfig?.FixedReward ?? 0.03m;
+
+ // calculate rewards
+ var shares = new Dictionary();
+ var rewards = new Dictionary();
+ var paidUntil = DateTime.Now;
+ var shareCutOffDate = await CalculateRewards(poolConfig, block, blockReward, shares, rewards, paidUntil, fixedReward);
+
+ // update balances
+ foreach(var address in rewards.Keys)
+ {
+ var amount = rewards[address];
+
+ if(amount <= 0) continue;
+ logger.Info(() => $"Adding {payoutHandler.FormatAmount(amount)} to balance of {address} for {FormatUtil.FormatQuantity(shares[address])} ({shares[address]}) shares for block {block?.BlockHeight}");
+ await balanceRepo.AddAmountAsync(con, tx, poolConfig.Id, address, amount, $"Reward for {FormatUtil.FormatQuantity(shares[address])} shares for block {block?.BlockHeight}");
+ }
+
+ // delete discarded shares
+ if(shareCutOffDate.HasValue)
+ {
+ var cutOffCount = await shareRepo.CountSharesBeforeCreatedAsync(con, tx, poolConfig.Id, shareCutOffDate.Value);
+
+ if(cutOffCount > 0)
+ {
+ await LogDiscardedShares(poolConfig, block, shareCutOffDate.Value);
+ logger.Info(() => $"Deleting {cutOffCount} discarded shares before {shareCutOffDate.Value:O}");
+ await shareRepo.DeleteSharesBeforeCreatedAsync(con, tx, poolConfig.Id, shareCutOffDate.Value);
+ }
+ }
+
+ // diagnostics
+ var totalShareCount = shares.Values.ToList().Sum(x => new decimal(x));
+ var totalRewards = rewards.Values.ToList().Sum(x => x);
+
+ if(totalRewards > 0)
+ logger.Info(() => $"{FormatUtil.FormatQuantity((double) totalShareCount)} ({Math.Round(totalShareCount, 2)}) shares contributed to a total payout of {payoutHandler.FormatAmount(totalRewards)} ({totalRewards / blockReward * 100:0.00}% of block reward) to {rewards.Keys.Count} addresses");
+
+ return;
+ }
+
+ #endregion // IPayoutScheme
+
+ private async Task LogDiscardedShares(PoolConfig poolConfig, Block block, DateTime value)
+ {
+ var before = value;
+ var pageSize = 50000;
+ var currentPage = 0;
+ var shares = new Dictionary();
+
+ while(true)
+ {
+ logger.Info(() => $"Fetching page {currentPage} of discarded shares for pool {poolConfig.Id}, block {block?.BlockHeight}");
+
+ var page = await shareReadFaultPolicy.ExecuteAsync(() =>
+ cf.Run(con => shareRepo.ReadSharesBeforeCreatedAsync(con, poolConfig.Id, before, false, pageSize)));
+
+ currentPage++;
+
+ foreach(var share in page)
+ {
+ // build address
+ var address = share.Miner;
+ if(!string.IsNullOrEmpty(share.Miner)) // TODO is share.Miner the walletID?
+ address += PayoutConstants.PayoutInfoSeperator + share.Miner;
+
+ // record attributed shares for diagnostic purposes
+ if(!shares.ContainsKey(address))
+ shares[address] = share.Difficulty;
+ else
+ shares[address] += share.Difficulty;
+ }
+
+ if(page.Length < pageSize)
+ break;
+
+ before = page[page.Length - 1].Created;
+ }
+
+ if(shares.Keys.Count > 0)
+ {
+ // sort addresses by shares
+ var addressesByShares = shares.Keys.OrderByDescending(x => shares[x]);
+
+ logger.Info(() => $"{FormatUtil.FormatQuantity(shares.Values.Sum())} ({shares.Values.Sum()}) total discarded shares, block {block?.BlockHeight}");
+
+ foreach(var address in addressesByShares)
+ logger.Info(() => $"{address} = {FormatUtil.FormatQuantity(shares[address])} ({shares[address]}) discarded shares, block {block?.BlockHeight}");
+ }
+ }
+
+ private async Task CalculateRewards(PoolConfig poolConfig, Block block, decimal blockReward,
+ IDictionary shares, IDictionary rewards, DateTime paidUntil, decimal fixedReward)
+ {
+ var done = false;
+ var before = paidUntil;
+ var inclusive = true;
+ var pageSize = 50000;
+ var currentPage = 0;
+ var accumulatedScore = 0.0m;
+ var accumulatedRewards = 0.0m;
+ var blockRewardRemaining = blockReward;
+ DateTime? shareCutOffDate = null;
+ var scores = new Dictionary();
+
+ while(!done)
+ {
+ logger.Info(() => $"Fetching page {currentPage} of shares for pool {poolConfig.Id}, block {block?.BlockHeight}");
+
+ var page = await shareReadFaultPolicy.ExecuteAsync(() =>
+ cf.Run(con => shareRepo.ReadSharesBeforeCreatedAsync(con, poolConfig.Id, before, inclusive, pageSize)));
+
+ inclusive = false;
+
+ foreach(var share in page)
+ {
+ var address = share.Miner;
+
+ // record attributed shares for diagnostic purposes
+ if(!shares.ContainsKey(address))
+ shares[address] = share.Difficulty;
+ else
+ shares[address] += share.Difficulty;
+
+ // determine a share's overall score
+ var score = (decimal) (share.Difficulty / share.NetworkDifficulty);
+ //var score = (decimal)(share.Difficulty / Blockchain.Ethereum.EthereumConstants.ScoreFactor);
+
+ if(!scores.ContainsKey(address))
+ scores[address] = score;
+ else
+ scores[address] += score;
+ accumulatedScore += score;
+
+ //TODO: Paying flat 3 cents until block calc is finalized
+ rewards[address] = rewards.ContainsKey(address) ? rewards[address] + fixedReward : fixedReward;
+ accumulatedRewards += rewards[address];
+
+ // set the cutoff date to clean up old shares after a successful payout
+ if(shareCutOffDate == null || share.Created > shareCutOffDate)
+ shareCutOffDate = share.Created;
+ }
+
+ // TODO: Disabling score based reward until block calc is finalized
+ //if(accumulatedScore > 0)
+ //{
+ // var rewardPerScorePoint = blockReward / accumulatedScore;
+
+ // // build rewards for all addresses that contributed to the round
+ // foreach(var address in scores.Select(x => x.Key).Distinct())
+ // {
+ // // loop all scores for the current address
+ // foreach(var score in scores.Where(x => x.Key == address))
+ // {
+ // var reward = score.Value * rewardPerScorePoint;
+ // if(reward > 0)
+ // {
+ // // accumulate miner reward
+ // if(!rewards.ContainsKey(address))
+ // rewards[address] = reward;
+ // else
+ // rewards[address] += reward;
+ // }
+
+ // blockRewardRemaining -= reward;
+ // }
+ // }
+ //}
+
+ currentPage++;
+ if(page.Length < pageSize)
+ {
+ done = true;
+ break;
+ }
+
+ before = page[page.Length - 1].Created;
+ done = page.Length <= 0;
+ }
+
+ // this should never happen
+ if(blockRewardRemaining <= 0 && !done)
+ throw new OverflowException("blockRewardRemaining < 0");
+
+ //logger.Info(() => $"Balance-calculation for pool {poolConfig.Id}, block {block?.BlockHeight} completed with accumulated score {accumulatedScore:0.####} ({accumulatedScore * 100:0.#}%)");
+ logger.Info(() => $"Balance-calculation for pool {poolConfig.Id} completed with accumulated rewards of ${accumulatedRewards:0.#######}");
+
+ return shareCutOffDate;
+ }
+
+ private void BuildFaultHandlingPolicy()
+ {
+ var retry = Policy
+ .Handle()
+ .Or()
+ .Or()
+ .RetryAsync(RetryCount, OnPolicyRetry);
+
+ shareReadFaultPolicy = retry;
+ }
+
+ private static void OnPolicyRetry(Exception ex, int retry, object context)
+ {
+ logger.Warn(() => $"Retry {retry} due to {ex.Source}: {ex.GetType().Name} ({ex.Message})");
+ }
+ }
+}
diff --git a/src/Miningcore/Payments/PaymentSchemes/PPSPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PPSPaymentScheme.cs
new file mode 100644
index 0000000000..670c38d69b
--- /dev/null
+++ b/src/Miningcore/Payments/PaymentSchemes/PPSPaymentScheme.cs
@@ -0,0 +1,278 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.Common;
+using System.Linq;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using Miningcore.Configuration;
+using Miningcore.Extensions;
+using Miningcore.Persistence;
+using Miningcore.Persistence.Model;
+using Miningcore.Persistence.Repositories;
+using Miningcore.Util;
+using NLog;
+using Polly;
+using Contract = Miningcore.Contracts.Contract;
+
+namespace Miningcore.Payments.PaymentSchemes
+{
+ ///
+ /// PPLNS payout scheme implementation
+ /// TODO THIS IS BUGGY AND INCOMPLETE!
+ ///
+ public class PPSPaymentScheme : IPayoutScheme
+ {
+ public PPSPaymentScheme(IConnectionFactory cf,
+ IShareRepository shareRepo,
+ IBlockRepository blockRepo,
+ IBalanceRepository balanceRepo)
+ {
+ Contract.RequiresNonNull(cf, nameof(cf));
+ Contract.RequiresNonNull(shareRepo, nameof(shareRepo));
+ Contract.RequiresNonNull(blockRepo, nameof(blockRepo));
+ Contract.RequiresNonNull(balanceRepo, nameof(balanceRepo));
+
+ this.cf = cf;
+ this.shareRepo = shareRepo;
+ this.blockRepo = blockRepo;
+ this.balanceRepo = balanceRepo;
+
+ BuildFaultHandlingPolicy();
+ }
+
+ private readonly IBalanceRepository balanceRepo;
+ private readonly IBlockRepository blockRepo;
+ private readonly IConnectionFactory cf;
+ private readonly IShareRepository shareRepo;
+ private static readonly ILogger logger = LogManager.GetLogger("PPS Payment", typeof(PPSPaymentScheme));
+
+ private const int RetryCount = 4;
+ private Policy shareReadFaultPolicy;
+
+ private class Config
+ {
+ public decimal Factor { get; set; }
+ }
+
+ #region IPayoutScheme
+
+ public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, PoolConfig poolConfig,
+ IPayoutHandler payoutHandler, Block block, decimal blockReward)
+ {
+ var payoutConfig = poolConfig.PaymentProcessing.PayoutSchemeConfig;
+
+ // PPLNS window (see https://bitcointalk.org/index.php?topic=39832)
+ var window = payoutConfig?.ToObject()?.Factor ?? 2.0m;
+
+ // calculate rewards
+ var shares = new Dictionary();
+ var rewards = new Dictionary();
+ var shareCutOffDate = CalculateRewards(poolConfig, block, blockReward, shares, rewards);
+
+ // update balances
+ foreach(var address in rewards.Keys)
+ {
+ var amount = rewards[address];
+
+ if (amount > 0)
+ {
+ logger.Info(() => $"Adding {payoutHandler.FormatAmount(amount)} to balance of {address} for {FormatUtil.FormatQuantity(shares[address])} ({shares[address]}) shares for block {block.BlockHeight}");
+ await balanceRepo.AddAmountAsync(con, tx, poolConfig.Id, address, amount, $"Reward for {FormatUtil.FormatQuantity(shares[address])} shares for block {block.BlockHeight}");
+ }
+ }
+
+ // delete discarded shares
+ if (shareCutOffDate.HasValue)
+ {
+ var cutOffCount = await shareRepo.CountSharesBeforeCreatedAsync(con, tx, poolConfig.Id, shareCutOffDate.Value);
+
+ if (cutOffCount > 0)
+ {
+ LogDiscardedShares(poolConfig, block, shareCutOffDate.Value);
+
+ logger.Info(() => $"Deleting {cutOffCount} discarded shares before {shareCutOffDate.Value:O}");
+ await shareRepo.DeleteSharesBeforeCreatedAsync(con, tx, poolConfig.Id, shareCutOffDate.Value);
+ }
+ }
+
+ // diagnostics
+ var totalShareCount = shares.Values.ToList().Sum(x => new decimal(x));
+ var totalRewards = rewards.Values.ToList().Sum(x => x);
+
+ if (totalRewards > 0)
+ logger.Info(() => $"{FormatUtil.FormatQuantity((double) totalShareCount)} ({Math.Round(totalShareCount, 2)}) shares contributed to a total payout of {payoutHandler.FormatAmount(totalRewards)} ({totalRewards / blockReward * 100:0.00}% of block reward) to {rewards.Keys.Count} addresses");
+
+ return;
+ }
+
+ private void LogDiscardedShares(PoolConfig poolConfig, Block block, DateTime value)
+ {
+ var before = value;
+ var pageSize = 50000;
+ var currentPage = 0;
+ var shares = new Dictionary();
+
+ while (true)
+ {
+ logger.Info(() => $"Fetching page {currentPage} of discarded shares for pool {poolConfig.Id}, block {block.BlockHeight}");
+
+ var pageTask = shareReadFaultPolicy.Execute(() =>
+ cf.Run(con => shareRepo.ReadSharesBeforeCreatedAsync(con, poolConfig.Id, before, false, pageSize)));
+
+
+ Task.WaitAll(pageTask);
+
+ var page = pageTask.Result;
+
+ currentPage++;
+
+ for (var i = 0;i < page.Length; i++)
+ {
+ var share = page[i];
+
+ // build address
+ var address = share.Miner;
+ if (!string.IsNullOrEmpty(share.Miner)) // TODO is share.Miner the walletID?
+ address += PayoutConstants.PayoutInfoSeperator + share.Miner;
+
+ // record attributed shares for diagnostic purposes
+ if (!shares.ContainsKey(address))
+ shares[address] = share.Difficulty;
+ else
+ shares[address] += share.Difficulty;
+ }
+
+ if (page.Length < pageSize)
+ break;
+
+ before = page[page.Length - 1].Created;
+ }
+
+ if (shares.Keys.Count > 0)
+ {
+ // sort addresses by shares
+ var addressesByShares = shares.Keys.OrderByDescending(x => shares[x]);
+
+ logger.Info(() => $"{FormatUtil.FormatQuantity(shares.Values.Sum())} ({shares.Values.Sum()}) total discarded shares, block {block.BlockHeight}");
+
+ foreach (var address in addressesByShares)
+ logger.Info(() => $"{address} = {FormatUtil.FormatQuantity(shares[address])} ({shares[address]}) discarded shares, block {block.BlockHeight}");
+ }
+ }
+
+ #endregion // IPayoutScheme
+
+ private DateTime? CalculateRewards(PoolConfig poolConfig, Block block, decimal blockReward,
+ Dictionary shares, Dictionary rewards)
+ {
+ var done = false;
+ var before = block.Created;
+ var inclusive = true;
+ var pageSize = 50000;
+ var currentPage = 0;
+ var accumulatedScore = 0.0m;
+ var blockRewardRemaining = blockReward;
+ DateTime? shareCutOffDate = null;
+ Dictionary scores = new Dictionary();
+
+ while (!done)
+ {
+ logger.Info(() => $"Fetching page {currentPage} of shares for pool {poolConfig.Id}, block {block.BlockHeight}");
+
+ var pageTask = shareReadFaultPolicy.Execute(() =>
+ cf.Run(con => shareRepo.ReadSharesBeforeCreatedAsync(con, poolConfig.Id, before, inclusive, pageSize)));
+
+ Task.WaitAll(pageTask);
+ var page = pageTask.Result;
+
+ inclusive = false;
+ currentPage++;
+
+ for (var i = 0; !done && i < page.Length; i++)
+ {
+ var share = page[i];
+ var address = share.Miner;
+
+ // record attributed shares for diagnostic purposes
+ if (!shares.ContainsKey(address))
+ shares[address] = share.Difficulty;
+ else
+ shares[address] += share.Difficulty;
+
+ // determine a share's overall score
+ var score = (decimal)(share.Difficulty / share.NetworkDifficulty);
+ //var score = (decimal)(share.Difficulty / Blockchain.Ethereum.EthereumConstants.ScoreFactor);
+
+ if (!scores.ContainsKey(address))
+ scores[address] = score;
+ else
+ scores[address] += score;
+ accumulatedScore += score;
+
+ // set the cutoff date to clean up old shares after a successful payout
+ if (shareCutOffDate == null || share.Created > shareCutOffDate)
+ shareCutOffDate = share.Created;
+ }
+
+ if (accumulatedScore > 0)
+ {
+ var rewardPerScorePoint = blockReward / accumulatedScore;
+
+ // build rewards for all addresses that contributed to the round
+ foreach (var address in scores.Select(x => x.Key).Distinct())
+ {
+ // loop all scores for the current address
+ foreach (var score in scores.Where(x => x.Key == address))
+ {
+ var reward = score.Value * rewardPerScorePoint;
+ if (reward > 0)
+ {
+ // accumulate miner reward
+ if (!rewards.ContainsKey(address))
+ rewards[address] = reward;
+ else
+ rewards[address] += reward;
+ }
+
+ blockRewardRemaining -= reward;
+ }
+ }
+ }
+
+ if (page.Length < pageSize)
+ {
+ done = true;
+ break;
+ }
+
+ before = page[page.Length - 1].Created;
+ done = page.Length <= 0;
+ }
+
+ // this should never happen
+ if (blockRewardRemaining <= 0 && !done)
+ throw new OverflowException("blockRewardRemaining < 0");
+
+ logger.Info(() => $"Balance-calculation for pool {poolConfig.Id}, block {block.BlockHeight} completed with accumulated score {accumulatedScore:0.####} ({accumulatedScore * 100:0.#}%)");
+
+ return shareCutOffDate;
+ }
+
+ private void BuildFaultHandlingPolicy()
+ {
+ var retry = Policy
+ .Handle()
+ .Or()
+ .Or()
+ .Retry(RetryCount, OnPolicyRetry);
+
+ shareReadFaultPolicy = retry;
+ }
+
+ private static void OnPolicyRetry(Exception ex, int retry, object context)
+ {
+ logger.Warn(() => $"Retry {retry} due to {ex.Source}: {ex.GetType().Name} ({ex.Message})");
+ }
+ }
+}
diff --git a/src/Miningcore/Payments/PayoutManager.cs b/src/Miningcore/Payments/PayoutManager.cs
index 9abdcdec70..9d568b622b 100644
--- a/src/Miningcore/Payments/PayoutManager.cs
+++ b/src/Miningcore/Payments/PayoutManager.cs
@@ -72,53 +72,53 @@ public void Start()
{
//try
//{
- //await ProcessPoolPaymentsAsync();
- foreach(var pool in clusterConfig.Pools.Where(x => x.Enabled && x.PaymentProcessing.Enabled))
+ //await ProcessPoolPaymentsAsync();
+ foreach(var pool in clusterConfig.Pools.Where(x => x.Enabled && x.PaymentProcessing.Enabled))
+ {
+ logger.Info(() => $"Processing payments for pool [{pool.Id}]");
+
+ try
{
- logger.Info(() => $"Processing payments for pool [{pool.Id}]");
+ var family = HandleFamilyOverride(pool.Template.Family, pool);
- try
- {
- var family = HandleFamilyOverride(pool.Template.Family, pool);
+ // resolve payout handler
+ var handlerImpl = ctx.Resolve>>>()
+ .First(x => x.Value.Metadata.SupportedFamilies.Contains(family)).Value;
- // resolve payout handler
- var handlerImpl = ctx.Resolve>>>()
- .First(x => x.Value.Metadata.SupportedFamilies.Contains(family)).Value;
+ var handler = handlerImpl.Value;
+ await handler.ConfigureAsync(clusterConfig, pool);
- var handler = handlerImpl.Value;
- await handler.ConfigureAsync(clusterConfig, pool);
+ // resolve payout scheme
+ var scheme = ctx.ResolveKeyed(pool.PaymentProcessing.PayoutScheme);
- // resolve payout scheme
- var scheme = ctx.ResolveKeyed(pool.PaymentProcessing.PayoutScheme);
+ await UpdatePoolBalancesAsync(pool, handler, scheme);
+ await PayoutPoolBalancesAsync(pool, handler);
+ }
- await UpdatePoolBalancesAsync(pool, handler, scheme);
- await PayoutPoolBalancesAsync(pool, handler);
- }
+ catch(InvalidOperationException ex)
+ {
+ logger.Error(ex.InnerException ?? ex, () => $"[{pool.Id}] Payment processing failed");
+ }
- catch(InvalidOperationException ex)
+ catch(AggregateException ex)
+ {
+ switch(ex.InnerException)
{
- logger.Error(ex.InnerException ?? ex, () => $"[{pool.Id}] Payment processing failed");
- }
+ case HttpRequestException httpEx:
+ logger.Error(() => $"[{pool.Id}] Payment processing failed: {httpEx.Message}");
+ break;
- catch(AggregateException ex)
- {
- switch(ex.InnerException)
- {
- case HttpRequestException httpEx:
- logger.Error(() => $"[{pool.Id}] Payment processing failed: {httpEx.Message}");
- break;
-
- default:
- logger.Error(ex.InnerException, () => $"[{pool.Id}] Payment processing failed");
- break;
- }
+ default:
+ logger.Error(ex.InnerException, () => $"[{pool.Id}] Payment processing failed");
+ break;
}
+ }
- catch(Exception ex)
- {
- logger.Error(ex, () => $"[{pool.Id}] Payment processing failed");
- }
+ catch(Exception ex)
+ {
+ logger.Error(ex, () => $"[{pool.Id}] Payment processing failed");
}
+ }
//}
//catch(Exception ex)
@@ -131,7 +131,7 @@ public void Start()
});
}
-
+
private static CoinFamily HandleFamilyOverride(CoinFamily family, PoolConfig pool)
{
switch(family)
@@ -197,9 +197,14 @@ await cf.RunTx(async (con, tx) =>
});
}
}
-
else
- logger.Info(() => $"No updated blocks for pool {pool.Id}");
+ {
+ logger.Info(() => $"No updated blocks for pool {pool.Id} but still payment processed");
+ await cf.RunTx(async (con, tx) =>
+ {
+ await scheme.UpdateBalancesAsync(con, tx, pool, handler, null, 1);
+ });
+ }
}
private async Task PayoutPoolBalancesAsync(PoolConfig pool, IPayoutHandler handler)
@@ -222,7 +227,7 @@ private async Task PayoutPoolBalancesAsync(PoolConfig pool, IPayoutHandler handl
}
else
- logger.Info(() => $"No balances over configured minimum payout for pool {pool.Id}");
+ logger.Info(() => $"No balances over configured minimum payout {pool.PaymentProcessing.MinimumPayment:0.#######} for pool {pool.Id}");
}
private Task NotifyPayoutFailureAsync(Balance[] balances, PoolConfig pool, Exception ex)
@@ -267,7 +272,7 @@ internal void Configure(ClusterConfig clusterConfig)
interval = TimeSpan.FromSeconds(clusterConfig.PaymentProcessing.Interval > 0 ? clusterConfig.PaymentProcessing.Interval : 600);
}
-
+
public void Stop()
{
@@ -278,6 +283,6 @@ public void Stop()
logger.Info(() => "Payment Service Stopped");
}
-
+
}
}
diff --git a/src/Miningcore/Persistence/Model/PaymentSchedule.cs b/src/Miningcore/Persistence/Model/PaymentSchedule.cs
new file mode 100644
index 0000000000..0e8e9c38c8
--- /dev/null
+++ b/src/Miningcore/Persistence/Model/PaymentSchedule.cs
@@ -0,0 +1,31 @@
+/*
+Copyright 2017 Coin Foundry (coinfoundry.org)
+Authors: Oliver Weichhold (oliver@weichhold.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+associated documentation files (the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial
+portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+using System;
+
+namespace Miningcore.Persistence.Model
+{
+ public class PaymentSchedule
+ {
+ public string PoolId { get; set; }
+ public string Miner { get; set; }
+ public DateTime PaidUntil { get; set; }
+ }
+}
diff --git a/src/Miningcore/Persistence/Postgres/Entities/PaymentSchedule.cs b/src/Miningcore/Persistence/Postgres/Entities/PaymentSchedule.cs
new file mode 100644
index 0000000000..c867ba4a15
--- /dev/null
+++ b/src/Miningcore/Persistence/Postgres/Entities/PaymentSchedule.cs
@@ -0,0 +1,31 @@
+/*
+Copyright 2017 Coin Foundry (coinfoundry.org)
+Authors: Oliver Weichhold (oliver@weichhold.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+associated documentation files (the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial
+portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+using System;
+
+namespace Miningcore.Persistence.Postgres.Entities
+{
+ public class PaymentSchedule
+ {
+ public string PoolId { get; set; }
+ public string Miner { get; set; }
+ public DateTime PaidUntil { get; set; }
+ }
+}
diff --git a/src/Miningcore/Persistence/Postgres/Repositories/PaymentRepository.cs b/src/Miningcore/Persistence/Postgres/Repositories/PaymentRepository.cs
index 43b9b8fe1f..ef82dc3947 100644
--- a/src/Miningcore/Persistence/Postgres/Repositories/PaymentRepository.cs
+++ b/src/Miningcore/Persistence/Postgres/Repositories/PaymentRepository.cs
@@ -1,98 +1,122 @@
-/*
-Copyright 2017 Coin Foundry (coinfoundry.org)
-Authors: Oliver Weichhold (oliver@weichhold.com)
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
-associated documentation files (the "Software"), to deal in the Software without restriction,
-including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all copies or substantial
-portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
-LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-*/
-
-using System.Data;
-using System.Linq;
+/*
+Copyright 2017 Coin Foundry (coinfoundry.org)
+Authors: Oliver Weichhold (oliver@weichhold.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+associated documentation files (the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial
+portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+using System.Data;
+using System.Linq;
using System.Threading.Tasks;
-using AutoMapper;
-using Dapper;
-using Miningcore.Extensions;
-using Miningcore.Persistence.Model;
-using Miningcore.Persistence.Model.Projections;
-using Miningcore.Persistence.Repositories;
-using NLog;
-
-namespace Miningcore.Persistence.Postgres.Repositories
-{
- public class PaymentRepository : IPaymentRepository
- {
- public PaymentRepository(IMapper mapper)
- {
- this.mapper = mapper;
+using AutoMapper;
+using Dapper;
+using Miningcore.Extensions;
+using Miningcore.Persistence.Model;
+using Miningcore.Persistence.Model.Projections;
+using Miningcore.Persistence.Repositories;
+using NLog;
+
+namespace Miningcore.Persistence.Postgres.Repositories
+{
+ public class PaymentRepository : IPaymentRepository
+ {
+ public PaymentRepository(IMapper mapper)
+ {
+ this.mapper = mapper;
+ }
+
+ private readonly IMapper mapper;
+ private static readonly ILogger logger = LogManager.GetCurrentClassLogger();
+
+ public async Task InsertAsync(IDbConnection con, IDbTransaction tx, Payment payment)
+ {
+ logger.LogInvoke();
+
+ var mapped = mapper.Map(payment);
+
+ const string query = "INSERT INTO payments(poolid, coin, address, amount, transactionconfirmationdata, created) " +
+ "VALUES(@poolid, @coin, @address, @amount, @transactionconfirmationdata, @created)";
+
+ await con.ExecuteAsync(query, mapped, tx);
+ }
+
+ public async Task PagePaymentsAsync(IDbConnection con, string poolId, string address, int page, int pageSize)
+ {
+ logger.LogInvoke(new[] { poolId });
+
+ var query = "SELECT * FROM payments WHERE poolid = @poolid ";
+
+ if(!string.IsNullOrEmpty(address))
+ query += " AND address = @address ";
+
+ query += "ORDER BY created DESC OFFSET @offset FETCH NEXT (@pageSize) ROWS ONLY";
+
+ return (await con.QueryAsync(query, new { poolId, address, offset = page * pageSize, pageSize }))
+ .Select(mapper.Map)
+ .ToArray();
+ }
+
+ public async Task PageBalanceChangesAsync(IDbConnection con, string poolId, string address, int page, int pageSize)
+ {
+ logger.LogInvoke(new[] { poolId });
+
+ const string query = "SELECT * FROM balance_changes WHERE poolid = @poolid " +
+ "AND address = @address " +
+ "ORDER BY created DESC OFFSET @offset FETCH NEXT (@pageSize) ROWS ONLY";
+
+ return (await con.QueryAsync(query, new { poolId, address, offset = page * pageSize, pageSize }))
+ .Select(mapper.Map)
+ .ToArray();
+ }
+
+ public async Task PageMinerPaymentsByDayAsync(IDbConnection con, string poolId, string address, int page, int pageSize)
+ {
+ logger.LogInvoke(new[] { poolId });
+
+ const string query = "SELECT SUM(amount) AS amount, date_trunc('day', created) AS date FROM payments WHERE poolid = @poolid " +
+ "AND address = @address " +
+ "GROUP BY date " +
+ "ORDER BY date DESC OFFSET @offset FETCH NEXT (@pageSize) ROWS ONLY";
+
+ return (await con.QueryAsync(query, new { poolId, address, offset = page * pageSize, pageSize }))
+ .ToArray();
}
- private readonly IMapper mapper;
- private static readonly ILogger logger = LogManager.GetCurrentClassLogger();
-
- public async Task InsertAsync(IDbConnection con, IDbTransaction tx, Payment payment)
- {
+ public async Task GetPaymentScheduleAsync(IDbConnection con, string poolId, string miner)
+ {
logger.LogInvoke();
- var mapped = mapper.Map(payment);
-
- const string query = "INSERT INTO payments(poolid, coin, address, amount, transactionconfirmationdata, created) " +
- "VALUES(@poolid, @coin, @address, @amount, @transactionconfirmationdata, @created)";
-
- await con.ExecuteAsync(query, mapped, tx);
- }
-
- public async Task PagePaymentsAsync(IDbConnection con, string poolId, string address, int page, int pageSize)
- {
- logger.LogInvoke(new[] { poolId });
-
- var query = "SELECT * FROM payments WHERE poolid = @poolid ";
-
- if(!string.IsNullOrEmpty(address))
- query += " AND address = @address ";
-
- query += "ORDER BY created DESC OFFSET @offset FETCH NEXT (@pageSize) ROWS ONLY";
-
- return (await con.QueryAsync(query, new { poolId, address, offset = page * pageSize, pageSize }))
- .Select(mapper.Map)
- .ToArray();
- }
-
- public async Task PageBalanceChangesAsync(IDbConnection con, string poolId, string address, int page, int pageSize)
- {
- logger.LogInvoke(new[] { poolId });
-
- const string query = "SELECT * FROM balance_changes WHERE poolid = @poolid " +
- "AND address = @address " +
- "ORDER BY created DESC OFFSET @offset FETCH NEXT (@pageSize) ROWS ONLY";
-
- return (await con.QueryAsync(query, new { poolId, address, offset = page * pageSize, pageSize }))
- .Select(mapper.Map)
- .ToArray();
- }
-
- public async Task PageMinerPaymentsByDayAsync(IDbConnection con, string poolId, string address, int page, int pageSize)
- {
- logger.LogInvoke(new[] { poolId });
-
- const string query = "SELECT SUM(amount) AS amount, date_trunc('day', created) AS date FROM payments WHERE poolid = @poolid " +
- "AND address = @address " +
- "GROUP BY date " +
- "ORDER BY date DESC OFFSET @offset FETCH NEXT (@pageSize) ROWS ONLY";
-
- return (await con.QueryAsync(query, new { poolId, address, offset = page * pageSize, pageSize }))
- .ToArray();
- }
- }
-}
+ const string query = "SELECT poolid,miner,paiduntil FROM payment_schedule WHERE poolid = @poolId AND miner = @miner";
+
+ return await con.QuerySingleOrDefaultAsync(query, new { poolId, miner });
+ }
+
+ public async Task UpdatePaymentScheduleAsync(IDbConnection con, IDbTransaction tx, PaymentSchedule paymentSchedule)
+ {
+ logger.LogInvoke();
+
+ var mapped = mapper.Map(paymentSchedule);
+
+ const string query = @"INSERT INTO payment_schedule(poolid, miner, paiduntil)
+ VALUES(@poolid, @miner, @paiduntil)
+ ON CONFLICT (poolid, miner)
+ DO UPDATE
+ SET paiduntil = @paiduntil;";
+
+ await con.ExecuteAsync(query, mapped, tx);
+ }
+ }
+}
diff --git a/src/Miningcore/Persistence/Repositories/IPaymentRepository.cs b/src/Miningcore/Persistence/Repositories/IPaymentRepository.cs
index 40e7f2adf3..0435a9869f 100644
--- a/src/Miningcore/Persistence/Repositories/IPaymentRepository.cs
+++ b/src/Miningcore/Persistence/Repositories/IPaymentRepository.cs
@@ -32,5 +32,7 @@ public interface IPaymentRepository
Task PagePaymentsAsync(IDbConnection con, string poolId, string address, int page, int pageSize);
Task PageBalanceChangesAsync(IDbConnection con, string poolId, string address, int page, int pageSize);
Task PageMinerPaymentsByDayAsync(IDbConnection con, string poolId, string address, int page, int pageSize);
+ Task GetPaymentScheduleAsync(IDbConnection con, string poolId, string miner);
+ Task UpdatePaymentScheduleAsync(IDbConnection con, IDbTransaction tx, PaymentSchedule paymentSchedule);
}
}
diff --git a/src/Miningcore/PoolCore/AzureAppConfiguration.cs b/src/Miningcore/PoolCore/AzureAppConfiguration.cs
new file mode 100644
index 0000000000..e26cce2ae3
--- /dev/null
+++ b/src/Miningcore/PoolCore/AzureAppConfiguration.cs
@@ -0,0 +1,31 @@
+using System;
+using Microsoft.Extensions.Configuration;
+using Azure.Identity;
+
+namespace Miningcore.PoolCore
+{
+ public class AzureAppConfiguration
+ {
+ public static readonly string ConfigJson = "config.json";
+ public static readonly string PersistencePostgresUser = "persistence.postgres.user";
+ public static readonly string PersistencePostgresPassword = "persistence.postgres.password";
+ public static readonly string CoinbasePassword = "paymentProcessing.coinbasePassword";
+ public static readonly string ConnectionString = "ConnectionString";
+
+ public static IConfigurationRoot GetAppConfig(string prefix) {
+
+ var builder = new ConfigurationBuilder();
+ builder.AddAzureAppConfiguration(options => {
+ options.Connect(Environment.GetEnvironmentVariable(ConnectionString))
+ .ConfigureKeyVault(kv =>
+ {
+ kv.SetCredential(new DefaultAzureCredential());
+ })
+ .TrimKeyPrefix(prefix);
+ });
+
+ return builder.Build();
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Miningcore/PoolCore/PoolConfig.cs b/src/Miningcore/PoolCore/PoolConfig.cs
index 917e3d33cc..fc9a21b537 100644
--- a/src/Miningcore/PoolCore/PoolConfig.cs
+++ b/src/Miningcore/PoolCore/PoolConfig.cs
@@ -6,13 +6,13 @@ Copyright 2021 MinerNL (Miningcore.com)
using System;
using System.IO;
using System.Linq;
-using System.Reactive.Linq;
using System.Text;
using System.Text.RegularExpressions;
using FluentValidation;
using Miningcore.Configuration;
using Miningcore.Mining;
using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using JsonSerializer = Newtonsoft.Json.JsonSerializer;
@@ -21,9 +21,9 @@ namespace Miningcore.PoolCore
{
public class PoolConfig
{
-
+ private const string BaseConfigFile = "config.json";
private static ClusterConfig clusterConfig;
- private static readonly Regex regexJsonTypeConversionError = new Regex("\"([^\"]+)\"[^\']+\'([^\']+)\'.+\\s(\\d+),.+\\s(\\d+)", RegexOptions.Compiled);
+ private static readonly Regex RegexJsonTypeConversionError = new Regex("\"([^\"]+)\"[^\']+\'([^\']+)\'.+\\s(\\d+),.+\\s(\\d+)", RegexOptions.Compiled);
public static ClusterConfig GetConfigContent(string configFile)
{
@@ -32,20 +32,96 @@ public static ClusterConfig GetConfigContent(string configFile)
ValidateConfig();
return clusterConfig;
+ }
+ public static ClusterConfig GetConfigContentFromJson(string config)
+ {
+ try
+ {
+ var baseConfig = JsonConvert.DeserializeObject(File.ReadAllText(BaseConfigFile));
+ var toBeMerged = JObject.Parse(config);
+ baseConfig.Merge(toBeMerged, new JsonMergeSettings { MergeArrayHandling = MergeArrayHandling.Merge });
+ clusterConfig = baseConfig.ToObject();
+ }
+ catch(JsonSerializationException ex)
+ {
+ Console.WriteLine($"Error: {ex.Message}");
+ throw;
+ }
+ catch(JsonException ex)
+ {
+ Console.WriteLine($"Error: {ex.Message}");
+ throw;
+ }
+ catch(IOException ex)
+ {
+ Console.WriteLine($"Error: {ex.Message}");
+ throw;
+ }
+
+ ValidateConfig();
+
+ return clusterConfig;
}
+
+ public static ClusterConfig GetConfigContentFromAppConfig(string prefix)
+ {
+ // Read config.json file
+ clusterConfig = ReadConfigFromAppConfiguration(prefix);
+ ValidateConfig();
- private static ClusterConfig ReadConfig(string configFile)
+ return clusterConfig;
+
+ }
+
+ private static ClusterConfig ReadConfigFromAppConfiguration(string prefix)
{
try
{
- Console.WriteLine($"Using configuration file {configFile}\n");
+ var config = AzureAppConfiguration.GetAppConfig(prefix);
var serializer = JsonSerializer.Create(new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
+ var reader = new JsonTextReader(new StringReader(config[AzureAppConfiguration.ConfigJson]));
+
+ clusterConfig = serializer.Deserialize(reader);
+ // Update dynamic pass and others config here
+
+ clusterConfig.Persistence.Postgres.User = config[AzureAppConfiguration.PersistencePostgresUser];
+ clusterConfig.Persistence.Postgres.Password = config[AzureAppConfiguration.PersistencePostgresPassword];
+ foreach(var poolConfig in clusterConfig.Pools)
+ {
+ poolConfig.PaymentProcessing.Extra["coinbasePassword"] = config["pools." + poolConfig.Id + "." + AzureAppConfiguration.CoinbasePassword];
+ }
+
+ return clusterConfig;
+
+ }
+ catch(JsonSerializationException ex)
+ {
+ HumanizeJsonParseException(ex);
+ throw;
+ }
+
+ catch(JsonException ex)
+ {
+ Console.WriteLine($"Error: {ex.Message}");
+ throw;
+ }
+ }
+
+ private static ClusterConfig ReadConfig(string configFile)
+ {
+ try
+ {
+ var serializer = JsonSerializer.Create(new JsonSerializerSettings
+ {
+ ContractResolver = new CamelCasePropertyNamesContractResolver()
+ });
+
using(var reader = new StreamReader(configFile, Encoding.UTF8))
{
using(var jsonReader = new JsonTextReader(reader))
@@ -54,19 +130,16 @@ private static ClusterConfig ReadConfig(string configFile)
}
}
}
-
catch(JsonSerializationException ex)
{
HumanizeJsonParseException(ex);
throw;
}
-
catch(JsonException ex)
{
Console.WriteLine($"Error: {ex.Message}");
throw;
}
-
catch(IOException ex)
{
Console.WriteLine($"Error: {ex.Message}");
@@ -82,12 +155,10 @@ private static void ValidateConfig()
if(!config.EnableInternalStratum.HasValue)
config.EnableInternalStratum = clusterConfig.ShareRelays == null || clusterConfig.ShareRelays.Length == 0;
}
-
try
{
clusterConfig.Validate();
}
-
catch(ValidationException ex)
{
Console.WriteLine($"Configuration is not valid:\n\n{string.Join("\n", ex.Errors.Select(x => "=> " + x.ErrorMessage))}");
@@ -97,12 +168,11 @@ private static void ValidateConfig()
{
Console.WriteLine($"Pool Configuration file is valid");
}
-
}
private static void HumanizeJsonParseException(JsonSerializationException ex)
{
- var m = regexJsonTypeConversionError.Match(ex.Message);
+ var m = RegexJsonTypeConversionError.Match(ex.Message);
if(m.Success)
{
@@ -131,6 +201,5 @@ public static void DumpParsedConfig(ClusterConfig config)
Formatting = Formatting.Indented
}));
}
-
}
}
diff --git a/src/Miningcore/PoolCore/PoolCore.cs b/src/Miningcore/PoolCore/PoolCore.cs
index cc494fd5c4..f11b64504a 100644
--- a/src/Miningcore/PoolCore/PoolCore.cs
+++ b/src/Miningcore/PoolCore/PoolCore.cs
@@ -14,22 +14,11 @@ Copyright 2021 MinerNL (Miningcore.com)
using System.Reactive.Threading.Tasks;
using System.Reflection;
using System.Runtime.InteropServices;
-using System.Text;
-using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Autofac;
using Autofac.Features.Metadata;
using AutoMapper;
-using FluentValidation;
-using McMaster.Extensions.CommandLineUtils;
-using Microsoft.AspNetCore;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.AspNetCore.Mvc;
-using Miningcore.Api;
-using Miningcore.Api.Controllers;
using Miningcore.Api.Responses;
using Miningcore.Configuration;
using Miningcore.Crypto.Hashing.Equihash;
@@ -38,34 +27,20 @@ Copyright 2021 MinerNL (Miningcore.com)
using Miningcore.Mining;
using Miningcore.Notifications;
using Miningcore.Payments;
-using Miningcore.PoolCore;
using Miningcore.Util;
using NBitcoin.Zcash;
using Newtonsoft.Json;
-using Newtonsoft.Json.Serialization;
using NLog;
-using NLog.Conditions;
-using NLog.Config;
-using NLog.Layouts;
-using NLog.Targets;
-using JsonSerializer = Newtonsoft.Json.JsonSerializer;
-using Microsoft.Extensions.Logging;
-using LogLevel = NLog.LogLevel;
using ILogger = NLog.ILogger;
-using NLog.Extensions.Logging;
-using Prometheus;
-using WebSocketManager;
-using Miningcore.Api.Middlewares;
using System.Collections.Concurrent;
-using Microsoft.AspNetCore.Http;
-using AspNetCoreRateLimit;
namespace Miningcore.PoolCore
{
internal class Pool
{
- private static readonly CancellationTokenSource cts = new CancellationTokenSource();
- private static readonly ILogger logger = LogManager.GetLogger("PoolCore");
+
+ private static readonly CancellationTokenSource Cts = new CancellationTokenSource();
+ private static readonly ILogger Logger = LogManager.GetLogger("PoolCore");
private static ShareRecorder shareRecorder;
private static ShareRelay shareRelay;
private static ShareReceiver shareReceiver;
@@ -74,7 +49,7 @@ internal class Pool
private static NotificationService notificationService;
private static MetricsPublisher metricsPublisher;
private static BtStreamReceiver btStreamReceiver;
- private static readonly ConcurrentDictionary pools = new ConcurrentDictionary();
+ private static readonly ConcurrentDictionary Pools = new ConcurrentDictionary();
private static AdminGcStats gcStats = new AdminGcStats();
private static readonly IPAddress IPv4LoopBackOnIPv6 = IPAddress.Parse("::ffff:127.0.0.1");
@@ -82,17 +57,36 @@ internal class Pool
internal static IContainer container;
internal static void StartMiningCorePool(string configFile)
+ {
+ StartMiningCorePoolInternal(PoolConfig.GetConfigContent(configFile));
+ }
+
+ internal static void StartMiningCorePoolWithJson(string config)
+ {
+ StartMiningCorePoolInternal(PoolConfig.GetConfigContentFromJson(config));
+ }
+
+ internal static void StartMiningCorePoolWithAppConfig(string appConfig)
+ {
+ // Read config.json from azure app configuration
+ StartMiningCorePoolInternal(PoolConfig.GetConfigContentFromAppConfig(appConfig));
+ }
+
+ private static void StartMiningCorePoolInternal(ClusterConfig config)
{
try
{
+ var assembly = Assembly.GetEntryAssembly();
// Display Software Version Info
- var basePath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
- Console.WriteLine($"{Assembly.GetEntryAssembly().GetName().Name} - MinerNL build v{Assembly.GetEntryAssembly().GetName().Version}");
+
+ var basePath = Path.GetDirectoryName(assembly?.Location);
+ Console.WriteLine($"{assembly?.GetName().Name} - MinerNL build v{assembly?.GetName().Version}");
Console.WriteLine($"Run location: {basePath}");
Console.WriteLine(" ");
// log unhandled program exception errors
- AppDomain currentDomain = AppDomain.CurrentDomain;
+
+ var currentDomain = AppDomain.CurrentDomain;
currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MC_UnhandledException);
currentDomain.ProcessExit += OnProcessExit;
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKeyPress);
@@ -100,21 +94,20 @@ internal static void StartMiningCorePool(string configFile)
// ValidateRuntimeEnvironment();
// root check
if(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.UserName == "root")
- logger.Warn(() => "Running as root is discouraged!");
+ Logger.Warn(() => "Running as root is discouraged!");
// require 64-bit Windows OS
if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && RuntimeInformation.ProcessArchitecture == Architecture.X86)
throw new PoolStartupAbortException("Miningcore requires 64-Bit Windows");
-
- // Read config.json file
- clusterConfig = PoolConfig.GetConfigContent(configFile);
+
+ clusterConfig = config;
// Initialize Logging
FileLogger.ConfigureLogging();
// LogRuntimeInfo();
//-----------------------------------------------------------------------------
- logger.Info(() => $"{RuntimeInformation.FrameworkDescription.Trim()} on {RuntimeInformation.OSDescription.Trim()} [{RuntimeInformation.ProcessArchitecture}]");
+ Logger.Info(() => $"{RuntimeInformation.FrameworkDescription.Trim()} on {RuntimeInformation.OSDescription.Trim()} [{RuntimeInformation.ProcessArchitecture}]");
// Bootstrap();
//-----------------------------------------------------------------------------
@@ -124,7 +117,7 @@ internal static void StartMiningCorePool(string configFile)
var builder = new ContainerBuilder();
builder.RegisterAssemblyModules(typeof(AutofacModule).GetTypeInfo().Assembly);
builder.RegisterInstance(clusterConfig);
- builder.RegisterInstance(pools);
+ builder.RegisterInstance(Pools);
builder.RegisterInstance(gcStats);
// AutoMapper
@@ -141,9 +134,9 @@ internal static void StartMiningCorePool(string configFile)
MonitorGarbageCollection();
// Start Miningcore Pool Services
- if(!cts.IsCancellationRequested)
- StartMiningcorePoolServices().Wait(cts.Token);
-
+ if(!Cts.IsCancellationRequested)
+ StartMiningcorePoolServices().Wait(Cts.Token);
+
}
catch(PoolStartupAbortException ex)
@@ -188,9 +181,9 @@ internal static void StartMiningCorePool(string configFile)
{
// Shutdown();
Console.WriteLine("Miningcore is shuting down... bye!");
- logger?.Info(() => "Miningcore is shuting down... bye!");
+ Logger?.Info(() => "Miningcore is shuting down... bye!");
- foreach(var poolToStop in pools.Values)
+ foreach(var poolToStop in Pools.Values)
{
Console.WriteLine($"Stopping pool {poolToStop}");
poolToStop.Stop();
@@ -203,9 +196,8 @@ internal static void StartMiningCorePool(string configFile)
Process.GetCurrentProcess().Kill();
}
-
- }
+ }
// **************************************************************************
// MININGCORE POOL SERVICES
@@ -213,14 +205,14 @@ internal static void StartMiningCorePool(string configFile)
private static async Task StartMiningcorePoolServices()
{
var coinTemplates = PoolCoinTemplates.LoadCoinTemplates();
- logger.Info($"{coinTemplates.Keys.Count} coins loaded from {string.Join(", ", clusterConfig.CoinTemplates)}");
+ Logger.Info($"{coinTemplates.Keys.Count} coins loaded from {string.Join(", ", clusterConfig.CoinTemplates)}");
// Populate pool configs with corresponding template
foreach(var poolConfig in clusterConfig.Pools.Where(x => x.Enabled))
{
// Foreach coin definition
if(!coinTemplates.TryGetValue(poolConfig.Coin, out var template))
- logger.ThrowLogPoolStartupException($"Pool {poolConfig.Id} references undefined coin '{poolConfig.Coin}'");
+ Logger.ThrowLogPoolStartupException($"Pool {poolConfig.Id} references undefined coin '{poolConfig.Coin}'");
poolConfig.Template = template;
}
@@ -257,11 +249,11 @@ private static async Task StartMiningcorePoolServices()
}
else
{
- logger.Warn("API is disabled");
+ Logger.Warn("API is disabled");
}
// start payment processor
- if(clusterConfig.PaymentProcessing?.Enabled == true && clusterConfig.Pools.Any(x => x.PaymentProcessing?.Enabled == true))
+ if(clusterConfig.PaymentProcessing?.Enabled == true && clusterConfig.Pools.Any(x => x.PaymentProcessing?.Enabled == true))
{
payoutManager = container.Resolve();
payoutManager.Configure(clusterConfig);
@@ -269,9 +261,9 @@ private static async Task StartMiningcorePoolServices()
}
else
{
- logger.Warn("Payment processing is Disabled");
+ Logger.Warn("Payment processing is Disabled");
}
-
+
if(clusterConfig.ShareRelay == null)
{
// start pool stats updater
@@ -281,7 +273,7 @@ private static async Task StartMiningcorePoolServices()
}
else
{
- logger.Info("Share Relay is Active!");
+ Logger.Info("Share Relay is Active!");
}
// start stratum pools
@@ -294,21 +286,20 @@ await Task.WhenAll(clusterConfig.Pools.Where(x => x.Enabled).Select(async poolCo
// create and configure
var stratumPool = poolImpl.Value;
stratumPool.Configure(poolConfig, clusterConfig);
- pools[poolConfig.Id] = stratumPool;
+ Pools[poolConfig.Id] = stratumPool;
// pre-start attachments
shareReceiver?.AttachPool(stratumPool);
statsRecorder?.AttachPool(stratumPool);
//apiServer?.AttachPool(stratumPool);
- await stratumPool.StartAsync(cts.Token);
+ await stratumPool.StartAsync(Cts.Token);
}));
// keep running
- await Observable.Never().ToTask(cts.Token);
+ await Observable.Never().ToTask(Cts.Token);
}
-
private static void MonitorGarbageCollection()
{
var thread = new Thread(() =>
@@ -320,7 +311,7 @@ private static void MonitorGarbageCollection()
var s = GC.WaitForFullGCApproach();
if(s == GCNotificationStatus.Succeeded)
{
- logger.Info(() => "Garbage Collection bin Full soon");
+ Logger.Info(() => "Garbage Collection bin Full soon");
sw.Start();
}
@@ -328,7 +319,7 @@ private static void MonitorGarbageCollection()
if(s == GCNotificationStatus.Succeeded)
{
- logger.Info(() => "Garbage Collection bin Full!!");
+ Logger.Info(() => "Garbage Collection bin Full!!");
sw.Stop();
@@ -351,11 +342,11 @@ internal static Task RecoverSharesAsync(string recoveryFilename)
}
// log unhandled program exception errors
- private static void MC_UnhandledException(object sender, UnhandledExceptionEventArgs args )
+ private static void MC_UnhandledException(object sender, UnhandledExceptionEventArgs args)
{
- if(logger != null)
+ if(Logger != null)
{
- logger.Error(args.ExceptionObject);
+ Logger.Error(args.ExceptionObject);
LogManager.Flush(TimeSpan.Zero);
}
Exception e = (Exception) args.ExceptionObject;
@@ -364,14 +355,14 @@ private static void MC_UnhandledException(object sender, UnhandledExceptionEvent
Console.WriteLine("Runtime terminating: {0}", args.IsTerminating);
}
- protected static void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args )
+ protected static void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args)
{
- logger?.Info(() => $"Miningcore is stopping because exit key [{args.SpecialKey}] recieved. Exiting.");
+ Logger?.Info(() => $"Miningcore is stopping because exit key [{args.SpecialKey}] recieved. Exiting.");
Console.WriteLine($"Miningcore is stopping because exit key [{args.SpecialKey}] recieved. Exiting.");
try
{
- cts?.Cancel();
+ Cts?.Cancel();
}
catch
{
@@ -382,12 +373,12 @@ protected static void OnCancelKeyPress(object sender, ConsoleCancelEventArgs arg
private static void OnProcessExit(object sender, EventArgs e)
{
- logger?.Info(() => "Miningcore received process stop request.");
+ Logger?.Info(() => "Miningcore received process stop request.");
Console.WriteLine("Miningcore received process stop request.");
try
{
- cts?.Cancel();
+ Cts?.Cancel();
}
catch
{
diff --git a/src/Miningcore/PoolCore/PoolLogo.cs b/src/Miningcore/PoolCore/PoolLogo.cs
index f3d67ef363..9307282464 100644
--- a/src/Miningcore/PoolCore/PoolLogo.cs
+++ b/src/Miningcore/PoolCore/PoolLogo.cs
@@ -4,8 +4,6 @@ Copyright 2021 MinerNL (Miningcore.com)
*/
using System;
-using System.Collections.Generic;
-using System.Text;
namespace Miningcore.PoolCore
{
@@ -15,7 +13,7 @@ class PoolLogo
public static void Logo()
{
- Console.WriteLine($@"
+ Console.WriteLine(@"
███╗ ███╗██╗███╗ ██╗██╗███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗
████╗ ████║██║████╗ ██║██║████╗ ██║██╔════╝ ██╔════╝██╔═══██╗██╔══██╗██╔════╝
██╔████╔██║██║██╔██╗ ██║██║██╔██╗ ██║██║ ███╗██║ ██║ ██║██████╔╝█████╗
@@ -23,22 +21,22 @@ public static void Logo()
██║ ╚═╝ ██║██║██║ ╚████║██║██║ ╚████║╚██████╔╝╚██████╗╚██████╔╝██║ ██║███████╗
");
Console.ForegroundColor = ConsoleColor.DarkYellow;
- Console.WriteLine($" MININGCORE - making mining easy");
- Console.WriteLine($" https://github.com/minernl/miningcore\n");
+ Console.WriteLine(" MININGCORE - making mining easy");
+ Console.WriteLine(" https://github.com/minernl/miningcore\n");
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Green;
- Console.WriteLine($" Part off all donation goes to the core developers");
- Console.WriteLine($" If you want to donate to them yourself:\n");
- Console.WriteLine($" BTC - 3QT2WreQtanPHcMneg9LT2aH3s5nrSZsxr");
- Console.WriteLine($" LTC - LTVnLEv8Xj6emGbf981nTyN54Mnyjbfgrg");
- Console.WriteLine($" DASH - Xc2vm9SfRn8t1hyQgqi8Zrt3oFeGcQtwTh");
- Console.WriteLine($" ETH - 0xBfD360CDd9014Bc5B348B65cBf79F78381694f4E");
- Console.WriteLine($" ETC - 0xF4BFFC324bbeB63348F137B84f8d1Ade17B507E4");
- Console.WriteLine($" UMA - 0x10c42769a8a07421C168c19612A434A72D460d08");
- Console.WriteLine($" XLM - GDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37:::ucl:::864367071");
- Console.WriteLine($" XMR - 44riGcQcDp4EsboDJP284CFCnJ2qP7y8DAqGC4D9WtVbEqzxQ3qYXAUST57u5FkrVF7CXhsEc63QNWazJ5b9ygwBJBtB2kT");
- Console.WriteLine($" XPR - rw2ciyaNshpHe7bCHo4bRWq6pqqynnWKQg:::ucl:::2242232925");
- Console.WriteLine($" ZEC - t1JtJtxTdgXCaYm1wzRfMRkGTJM4qLcm4FQ");
+ Console.WriteLine(" Part off all donation goes to the core developers");
+ Console.WriteLine(" If you want to donate to them yourself:\n");
+ Console.WriteLine(" BTC - 3QT2WreQtanPHcMneg9LT2aH3s5nrSZsxr");
+ Console.WriteLine(" LTC - LTVnLEv8Xj6emGbf981nTyN54Mnyjbfgrg");
+ Console.WriteLine(" DASH - Xc2vm9SfRn8t1hyQgqi8Zrt3oFeGcQtwTh");
+ Console.WriteLine(" ETH - 0xBfD360CDd9014Bc5B348B65cBf79F78381694f4E");
+ Console.WriteLine(" ETC - 0xF4BFFC324bbeB63348F137B84f8d1Ade17B507E4");
+ Console.WriteLine(" UMA - 0x10c42769a8a07421C168c19612A434A72D460d08");
+ Console.WriteLine(" XLM - GDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37:::ucl:::864367071");
+ Console.WriteLine(" XMR - 44riGcQcDp4EsboDJP284CFCnJ2qP7y8DAqGC4D9WtVbEqzxQ3qYXAUST57u5FkrVF7CXhsEc63QNWazJ5b9ygwBJBtB2kT");
+ Console.WriteLine(" XPR - rw2ciyaNshpHe7bCHo4bRWq6pqqynnWKQg:::ucl:::2242232925");
+ Console.WriteLine(" ZEC - t1JtJtxTdgXCaYm1wzRfMRkGTJM4qLcm4FQ");
Console.WriteLine();
Console.ResetColor();
}
diff --git a/src/Miningcore/Program.cs b/src/Miningcore/Program.cs
index ea038f8285..f84cc5bfc8 100644
--- a/src/Miningcore/Program.cs
+++ b/src/Miningcore/Program.cs
@@ -2,15 +2,13 @@
using Miningcore.Configuration;
using Miningcore.PoolCore;
using System;
-using System.Collections.Generic;
-using System.IO;
using System.Reflection;
-using System.Text;
namespace Miningcore
{
public class Program
{
+ private const string EnvironmentConfig = "cfg";
private static CommandOption dumpConfigOption;
private static CommandOption shareRecoveryOption;
private static ClusterConfig clusterConfig;
@@ -24,54 +22,70 @@ public static void Main(string[] args)
Name = "dotnet Miningcore.dll",
FullName = "MiningCore 2.0 - Stratum Mining Pool Engine",
Description = "Stratum mining pool engine for Bitcoin and Altcoins",
- ShortVersionGetter = () => $"- MinerNL build v{Assembly.GetEntryAssembly().GetName().Version.ToString(2)}",
- LongVersionGetter = () => $"- MinerNL build v{Assembly.GetEntryAssembly().GetName().Version}",
+ ShortVersionGetter = () => $"- MinerNL build v{Assembly.GetEntryAssembly()?.GetName().Version.ToString(2)}",
+ LongVersionGetter = () => $"- MinerNL build v{Assembly.GetEntryAssembly()?.GetName().Version}",
ExtendedHelpText = "--------------------------------------------------------------------------------------------------------------"
};
var versionOption = MiningCore.Option("-v|--version", "Version Information", CommandOptionType.NoValue);
var configFileOption = MiningCore.Option("-c|--config ", "Configuration File", CommandOptionType.SingleValue);
+ var appConfigPrefixOption = MiningCore.Option("-ac|--appconfig ", "Azure App Configuration Prefix", CommandOptionType.SingleValue);
dumpConfigOption = MiningCore.Option("-dc|--dumpconfig", "Dump the configuration (useful for trouble-shooting typos in the config file)", CommandOptionType.NoValue);
shareRecoveryOption = MiningCore.Option("-rs", "Import lost shares using existing recovery file", CommandOptionType.SingleValue);
MiningCore.HelpOption("-? | -h | --help");
- MiningCore.OnExecute( () =>
+ MiningCore.OnExecute(() =>
{
Console.WriteLine("-----------------------------------------------------------------------------------------------------------------------");
- string configFile = "config_template.json";
+ var configFile = "config_template.json";
+ var appConfigPrefix = "/";
if(versionOption.HasValue())
{
MiningCore.ShowVersion();
}
-
// overwrite default config_template.json with -c | --config file
if(configFileOption.HasValue())
{
configFile = configFileOption.Value();
}
-
+ if(appConfigPrefixOption.HasValue())
+ {
+ appConfigPrefix = appConfigPrefixOption.Value();
+ }
// Dump Config to JSON output
if(dumpConfigOption.HasValue())
{
clusterConfig = PoolCore.PoolConfig.GetConfigContent(configFile);
PoolCore.PoolConfig.DumpParsedConfig(clusterConfig);
}
-
// Shares recovery from file to database
if(shareRecoveryOption.HasValue())
{
- PoolCore.Pool.RecoverSharesAsync(shareRecoveryOption.Value()).Wait();
+ Pool.RecoverSharesAsync(shareRecoveryOption.Value()).Wait();
}
- if(!configFileOption.HasValue())
+ if(appConfigPrefixOption.HasValue())
{
- MiningCore.ShowHelp();
+ Pool.StartMiningCorePoolWithAppConfig(appConfigPrefix);
}
- else
+ else if(configFileOption.HasValue())
{
// Start Miningcore PoolCore
- PoolCore.Pool.StartMiningCorePool(configFile);
+ Pool.StartMiningCorePool(configFile);
+ }
+ else
+ {
+ var envConfig = Environment.GetEnvironmentVariable(EnvironmentConfig);
+ if(!string.IsNullOrEmpty(envConfig))
+ {
+ // Start Miningcore PoolCore
+ Pool.StartMiningCorePoolWithJson(envConfig);
+ }
+ else
+ {
+ MiningCore.ShowHelp();
+ }
}
});
MiningCore.Execute(args);
diff --git a/src/Miningcore/Properties/launchSettings.json b/src/Miningcore/Properties/launchSettings.json
index 206d2adf0b..7845fab596 100644
--- a/src/Miningcore/Properties/launchSettings.json
+++ b/src/Miningcore/Properties/launchSettings.json
@@ -4,6 +4,9 @@
"commandName": "Project",
"commandLineArgs": "-c ..\\..\\..\\config.json",
"nativeDebugging": true
+ },
+ "Docker": {
+ "commandName": "Docker"
}
}
}
\ No newline at end of file
diff --git a/src/Miningcore/config.json b/src/Miningcore/config.json
new file mode 100644
index 0000000000..6849859089
--- /dev/null
+++ b/src/Miningcore/config.json
@@ -0,0 +1,122 @@
+{
+ "logging": {
+ "level": "info",
+ "enableConsoleLog": true,
+ "enableConsoleColors": true,
+ "logFile": "core.log",
+ "apiLogFile": "api.log",
+ "logBaseDirectory": "logs/",
+ "perPoolLogFile": true
+ },
+ "banning": {
+ "manager": "integrated",
+ "banOnJunkReceive": true,
+ "banOnInvalidShares": false
+ },
+ "notifications": {
+ "enabled": false,
+ "email": {
+ "host": "smtp.example.com",
+ "port": 587,
+ "user": "user",
+ "password": "password",
+ "fromAddress": "info@carbonpool.org",
+ "fromName": "pool support"
+ },
+ "admin": {
+ "enabled": false,
+ "emailAddress": "user@example.com",
+ "notifyBlockFound": true
+ }
+ },
+ "persistence": {
+ "postgres": {
+ "host": "db_host",
+ "port": 5432,
+ "user": "db_user",
+ "password": "db_password",
+ "database": "db_name"
+ }
+ },
+
+ "paymentProcessing": {
+ "enabled": true,
+ "interval": 600,
+ "shareRecoveryFile": "recovered-shares.txt"
+ },
+ "api": {
+ "enabled": true,
+ "listenAddress": "127.0.0.1",
+ "port": 4000,
+ "rateLimiting": {
+ "disabled": true,
+ "rules": [
+ {
+ "Endpoint": "*",
+ "Period": "1s",
+ "Limit": 5
+ }
+ ],
+ "ipWhitelist": []
+ }
+ },
+ "pools": [
+ {
+ "id": "eth1",
+ "enabled": true,
+ "coin": "ethereum",
+ "address": "wallet_address",
+ "rewardRecipients": [
+ {
+ "type": "op",
+ "address": "wallet_address",
+ "percentage": 15
+ }
+ ],
+ "blockRefreshInterval": 5000,
+ "clientConnectionTimeout": 600,
+ "banning": {
+ "enabled": false,
+ "time": 600,
+ "invalidPercent": 50,
+ "checkThreshold": 50
+ },
+ "ports": {
+ "3072": {
+ "listenAddress": "0.0.0.0",
+ "difficulty": 0.1,
+ "varDiff": {
+ "minDiff": 0.05,
+ "maxDiff": null,
+ "targetTime": 15,
+ "retargetTime": 90,
+ "variancePercent": 30
+ }
+ },
+ "3073": {
+ "difficulty": 5000
+ }
+ },
+ "daemons": [
+ {
+ "host": "127.0.0.1",
+ "port": 0
+ }
+ ],
+ "paymentProcessing": {
+ "enabled": true,
+ "minimumPayment": "0.000044",
+ "minimumPaymentToPaymentId": 5.0,
+ "payoutScheme": "PPS3",
+ "payoutSchemeConfig": {
+ "factor": 2.0,
+ "fixedReward": "0.0000022"
+ },
+ "coinbasePassword": "wallet_password",
+ "keepUncles": false,
+ "keepTransactionFees": false,
+ "Gas": 21000
+ }
+ }
+ ]
+}
diff --git a/src/Miningcore/config.secrets b/src/Miningcore/config.secrets
new file mode 100644
index 0000000000..d79423ddda
--- /dev/null
+++ b/src/Miningcore/config.secrets
@@ -0,0 +1,3 @@
+persistence.postgres.user=persistence-postgres-user
+persistence.postgres.password=persistence-postgres-password
+pools.ethe1.paymentProcessing.coinbasePassword=pools-ethe1-paymentProcessing-coinbasePassword