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 @@ [![Build status](https://ci.appveyor.com/api/projects/status/github/minernl/miningcore?branch=master&svg=true)](https://ci.appveyor.com/project/minernl/miningcore) [![license](https://img.shields.io/github/license/mashape/apistatus.svg)]() -MinerNL - Miningcore 2.0 Stratum Pool +Miningcore 2.0 Stratum Pool ================================= ![Miningcore running ubuntu](http://i.imgur.com/sYF5s2c.jpg) @@ -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