Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
a41ea1a
add payment feature
c0mm4nd Oct 29, 2020
cb35a52
add payment feature
c0mm4nd Oct 31, 2020
c8cbc12
Merge master into payment: adapt payment feature to the post-#13 Daem…
c0mm4nd Aug 14, 2026
6dec44b
payments: real PROP payout processor + fork configurability
c0mm4nd Aug 14, 2026
77eed98
e2e: add payout processor test (payment.sh) — mine → mature coinbase …
c0mm4nd Aug 14, 2026
7b33702
daemons: parse modern getaddressinfo (labels changed from objects to …
c0mm4nd Aug 14, 2026
0413c0d
payments: configurable payMode (prop | pplns | solo)
c0mm4nd Aug 14, 2026
94176e9
payments: add pps payMode + cover every mode in E2E
c0mm4nd Aug 14, 2026
3b29c20
e2e payment.sh: setmocktime per mode so 100-block coinbase maturity d…
c0mm4nd Aug 14, 2026
d12f637
e2e payment.sh: drop setmocktime, use minConfirmations=1 + wall-clock…
c0mm4nd Aug 14, 2026
ee75ff5
payments: make minConfirmations the real maturity gate; fix pps e2e rate
c0mm4nd Aug 14, 2026
efa3a18
payments: bound the insufficient-funds back-off; fix pps e2e rate
c0mm4nd Aug 14, 2026
c606874
payments: address codex review — pplns fallback, daemon index, batch …
c0mm4nd Aug 14, 2026
725140f
payments: credit assigned share difficulty; harden wallet RPC + magni…
c0mm4nd Aug 14, 2026
6e14fe6
stratum: bring engine vardiff to parity with the GBT path
c0mm4nd Aug 14, 2026
582b122
stratum: persist engine-mode shares for stats and accounting
c0mm4nd Aug 14, 2026
00e15fa
payments: wire kawpow/Ravencoin payout through the shared payment pro…
c0mm4nd Aug 14, 2026
7860dab
payments: wire equihash/Zcash-family payout through the shared processor
c0mm4nd Aug 14, 2026
e8ad86a
e2e: add engine-coin payout leg (Ravencoin/kawpow)
c0mm4nd Aug 14, 2026
373f518
e2e: cover all payModes in the engine-coin payout leg (RVNPAY)
c0mm4nd Aug 14, 2026
c78a73d
e2e: add isolated Flux/ZelHash engine-payout leg
c0mm4nd Aug 14, 2026
69a7db7
e2e(flux): pass -regtest to flux-cli, extend startup wait + diagnostics
c0mm4nd Aug 14, 2026
072d929
e2e(flux): name the node config flux.conf so flux-cli finds it
c0mm4nd Aug 14, 2026
02893f5
e2e(flux): assert equihash-engine-vs-live-fluxd; fix time-too-old
c0mm4nd Aug 14, 2026
0948d49
e2e(flux): fetch zk params from z.cash CDN with http1.1 + retry/resume
c0mm4nd Aug 14, 2026
22c65d2
e2e(flux): add time/GBT diagnostic before pool start
c0mm4nd Aug 14, 2026
dd1dde1
e2e(flux): wait past tip MTP (getblockheader.mediantime) before GBT
c0mm4nd Aug 14, 2026
42d7632
e2e(flux): wait past tip block time (mediantime unavailable on Flux)
c0mm4nd Aug 14, 2026
817b20c
e2e(flux): drop debug diagnostic now that the leg is green
c0mm4nd Aug 14, 2026
08905cb
e2e(rvn): real mining instead of injected block
c0mm4nd Aug 15, 2026
9d89a78
e2e(rvn): dump ravend debug.log block-validity reason on no-land
c0mm4nd Aug 15, 2026
ac2d993
e2e(rvn): diagnose node header size + block format (kawpow activation…
c0mm4nd Aug 15, 2026
5879b23
e2e(rvn): probe ravend for a kawpow-activation option; shorten mine loop
c0mm4nd Aug 15, 2026
03639cb
e2e(rvn): enable KAWPOW on regtest via -kawpowactivationtime=1
c0mm4nd Aug 15, 2026
5339dc0
e2e(rvn): revert to inject — regtest cannot run kawpow
c0mm4nd Aug 15, 2026
2aa0542
e2e(rvn): real KAWPOW mining via setmocktime past the regtest activation
c0mm4nd Aug 16, 2026
18c2ba1
kawpow: block id is the progpow digest, not sha256d of the header
c0mm4nd Aug 16, 2026
bacefa8
e2e(gbt): RVN/kawpow now lands a real block (setmocktime activates KA…
c0mm4nd Aug 16, 2026
27be9c3
e2e(gbt): surface node reject reason on the Found-Block fallback (MONA)
c0mm4nd Aug 16, 2026
5a6d926
e2e(gbt): TEMP fail Found-Block fallback to surface MONA reject reason
c0mm4nd Aug 16, 2026
da66b5e
e2e(gbt): honest MONA label — lyra2rev2 lib mismatch, not "target pre…
c0mm4nd Aug 16, 2026
1ee91b0
e2e: land a real MONA block (lyra2rev2 activates at regtest height 60)
c0mm4nd Aug 16, 2026
269516b
payments: durable payout intent to prevent double-paying
c0mm4nd Aug 16, 2026
74e9690
storage: serialize share writes so round seals aren't raced
c0mm4nd Aug 16, 2026
16ce164
payments: retain uncredited PPS shares (trim by cursor, not rank)
c0mm4nd Aug 16, 2026
a441717
payments: don't orphan a block on a transient wallet miss
c0mm4nd Aug 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,21 @@ jobs:
run: docker build -t nomp-e2e -f scripts/e2e/Dockerfile .
- name: run e2e suite (all coins)
run: docker run --rm nomp-e2e

# Isolated engine-payout leg for a Zcash-family coin (Flux/ZelHash). Split from
# the main e2e job because fluxd needs ~775MB of zk params; keeping it separate
# means the main suite stays lean. Proves the equihash engine drives a live
# fluxd and the engine pool pays through the shared processor.
e2e-flux:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: free disk space
run: |
sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/.ghcup /opt/hostedtoolcache/CodeQL
df -h /
- name: build flux e2e image
run: docker build -t nomp-e2e-flux -f scripts/e2e/Dockerfile.flux .
- name: run flux payout leg
run: docker run --rm nomp-e2e-flux
42 changes: 42 additions & 0 deletions TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,48 @@ Most GPU algorithms only have C implementations. Put the C source under
from an `init()` behind a build tag, and build with `CGO_ENABLED=1 go build -tags
<algo> ./cmd/nomp`. `neoscrypt` (`github.com/sparkspay/go-neoscrypt`) is a worked example.

## Payments (configurable reward scheme, fork-configurable)

Set `disablePayment: false` and add a `payment` block to pay miners automatically.
When a share solves a block the current round is sealed; once the block's coinbase
reaches maturity its reward is attributed to miners per the selected `payMode` and
paid via `sendmany`. Balances below `minPayment` carry over.

```json
"disablePayment": false,
"payment": {
"interval": 600, // seconds between payout runs
"minPayment": 0.05, // min coin owed before a miner is paid (else carried over)
"daemon": 0, // index into daemons[] used for wallet RPC
"payMode": "prop", // "prop" | "pplns" | "solo" | "pps"
"pplnsWindow": 0, // pplns look-back as total share difficulty; 0 = the block's round
"ppsRate": 0, // pps price per share difficulty unit (coin); required for pps
"magnitude": 0, // base units per coin (1e8); 0 = auto-detect
"minConfirmations": 100, // coinbase maturity before a reward is paid
"addressCheckMethod": "getaddressinfo", // or "validateaddress" on older forks
"sendManyDummy": "", // leading sendmany arg (Bitcoin Core wants "")
"omitSendManyDummy": false // true if the fork's sendmany drops the leading arg
}
```

**`payMode`** selects the reward scheme:
- **`prop`** (default) — split the block reward proportionally to the shares of
the round that found it.
- **`pplns`** — split proportionally to the last `pplnsWindow` difficulty of
shares across rounds (a sliding window; resists pool-hopping). `pplnsWindow: 0`
falls back to the block's own round.
- **`solo`** — the miner who found the block takes the whole reward.
- **`pps`** — pay a fixed `ppsRate` per share difficulty **immediately** (each run
credits shares since a cursor and pays); found blocks refill the pool wallet
instead of being distributed, so the **pool** carries the luck variance.

The remaining knobs let one binary pay out across bitcoind-family **forks** whose
wallet RPC differs: coinbase maturity (`minConfirmations`), coin precision
(`magnitude`), the address-ownership check (`addressCheckMethod`), and the
`sendmany` shape (`sendManyDummy` / `omitSendManyDummy`). The payout wallet must
own `poolAddress` (verified at startup). Miners are paid to their worker name, so
they connect with their wallet address as the username.

## Add a category-C coin (an engine)

Category-C coins are already supported by pluggable engines — you don't write one,
Expand Down
35 changes: 35 additions & 0 deletions TUTORIAL_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,41 @@ go build ./cmd/nomp

---

## 3.5 付款(可配置奖励方案,可配置支持各币 fork)

设 `disablePayment: false` 并加 `payment` 配置块即可自动给矿工付款。share 打出块时封存当前轮,
待该块 coinbase 成熟后按 `payMode` 选定的方案分账、经 `sendmany` 发出;不足 `minPayment` 的余额结转下轮。

```json
"disablePayment": false,
"payment": {
"interval": 600, // 每轮付款间隔(秒)
"minPayment": 0.05, // 达到多少币才付(否则结转)
"daemon": 0, // 用 daemons[] 中第几个做钱包 RPC
"payMode": "prop", // "prop" | "pplns" | "solo" | "pps"
"pplnsWindow": 0, // pplns 回看窗口(按 share 总难度);0 = 用该块所在轮
"ppsRate": 0, // pps 每单位 share 难度的定价(币);pps 模式必填
"magnitude": 0, // 每币的最小单位数(1e8);0 = 自动探测
"minConfirmations": 100, // coinbase 成熟确认数(付款前需达到)
"addressCheckMethod": "getaddressinfo", // 老 fork 用 "validateaddress"
"sendManyDummy": "", // sendmany 的首个 dummy 参数(Bitcoin Core 用 "")
"omitSendManyDummy": false // 若 fork 的 sendmany 去掉了首参则设 true
}
```

**`payMode`** 选奖励方案:
- **`prop`**(默认)——按**出块那一轮**各矿工的 share 比例分;
- **`pplns`**——按跨轮的**最近 `pplnsWindow` 难度**的 share 滑动窗口比例分(抗跳池);`pplnsWindow: 0` 退化为该块所在轮;
- **`solo`**——打出块的矿工独得全部奖励;
- **`pps`**——按 `ppsRate` 对每个 share **即时**计费发放(每轮把游标之后的新 share 记账并发款),出块奖励用于回补矿池钱包、不再分账,**由矿池承担运气方差**。

其余开关让**一份二进制**适配各 bitcoind 系 fork 的钱包差异:成熟确认数
(`minConfirmations`)、币精度(`magnitude`)、地址归属校验方法(`addressCheckMethod`)、
`sendmany` 形态(`sendManyDummy` / `omitSendManyDummy`)。付款钱包必须拥有 `poolAddress`
(启动时校验);矿工按其 worker 名收款,故矿工连接时用户名填钱包地址。

---

## 4. 新增一个头哈希算法(A→B 类)

算法系统已重构为**可插拔注册表**(`algorithm/algorithm.go`)。你有两种方式加算法。
Expand Down
55 changes: 55 additions & 0 deletions algorithm/lyra2_vector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package algorithm

import (
"encoding/binary"
"encoding/hex"
"math/big"
"testing"

"github.com/mining-pool/not-only-mining-pool/utils"
)

// TestLyra2Rev2AgainstRealMonacoinBlock verifies the pool's Lyra2REv2 against a
// real accepted Monacoin block (2500000): its PoW hash MUST be <= the block
// target. If not, the library disagrees with monacoind.
func TestLyra2Rev2AgainstRealMonacoinBlock(t *testing.T) {
const (
version = uint32(536870912)
prevDisplay = "20f986555ce18dc5c5de9a69faf6d646026d7ba8da30350e2a93ae1912bbe690"
merkleDisplay = "4dabf61d902f3ca4b22f227287b693914f6287957485b39d68379fdd9d01a241"
ntime = uint32(1637217290)
bits = uint32(0x1a067960)
nonce = uint32(3183243202)
)
prev, _ := hex.DecodeString(prevDisplay)
merkle, _ := hex.DecodeString(merkleDisplay)

hdr := make([]byte, 0, 80)
var b [4]byte
binary.LittleEndian.PutUint32(b[:], version)
hdr = append(hdr, b[:]...)
hdr = append(hdr, utils.ReverseBytes(prev)...)
hdr = append(hdr, utils.ReverseBytes(merkle)...)
binary.LittleEndian.PutUint32(b[:], ntime)
hdr = append(hdr, b[:]...)
binary.LittleEndian.PutUint32(b[:], bits)
hdr = append(hdr, b[:]...)
binary.LittleEndian.PutUint32(b[:], nonce)
hdr = append(hdr, b[:]...)
if len(hdr) != 80 {
t.Fatalf("header len %d", len(hdr))
}

pow := Lyra2Rev2Hash(hdr)
powBig := new(big.Int).SetBytes(utils.ReverseBytes(pow))

exp := bits >> 24
target := new(big.Int).Lsh(big.NewInt(int64(bits&0xffffff)), uint(8*(exp-3)))

t.Logf("pow(reversed) = %x", utils.ReverseBytes(pow))
t.Logf("pow(as-is) = %x", pow)
t.Logf("target = %064x", target)
if powBig.Cmp(target) > 0 {
t.Errorf("POW > target — bitgoin/lyra2rev2 does NOT match monacoind (a real accepted block fails PoW)")
}
}
14 changes: 14 additions & 0 deletions config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@
"host": "0.0.0.0",
"port": 20000
},
"disablePayment": false,
"payment": {
"interval": 600,
"minPayment": 0.05,
"daemon": 0,
"payMode": "prop",
"pplnsWindow": 0,
"ppsRate": 0,
"magnitude": 0,
"minConfirmations": 100,
"addressCheckMethod": "getaddressinfo",
"sendManyDummy": "",
"omitSendManyDummy": false
},
"storage": {
"network": "tcp",
"host": "127.0.0.1",
Expand Down
2 changes: 1 addition & 1 deletion config/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type DaemonOptions struct {
}

func (d *DaemonOptions) String() string {
return d.User + ":" + d.Password + "@" + d.Host + strconv.FormatInt(int64(d.Port), 10)
return d.User + "@" + d.Host + ":" + strconv.FormatInt(int64(d.Port), 10)
}

func (d *DaemonOptions) URL() string {
Expand Down
15 changes: 8 additions & 7 deletions config/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ type Options struct {
EmitInvalidBlockHashes bool `json:"emitInvalidBlockHashes"`
TCPProxyProtocol bool `json:"tcpProxyProtocol"` // http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt

API *APIOptions `json:"api"`
Banning *BanningOptions `json:"banning"`
Ports map[int]*PortOptions `json:"ports"`
Daemons []*DaemonOptions `json:"daemons"`
P2P *P2POptions `json:"p2p"`
Storage *RedisOptions `json:"storage"`
Algorithm *AlgorithmOptions `json:"algorithm"`
API *APIOptions `json:"api"`
Banning *BanningOptions `json:"banning"`
Ports map[int]*PortOptions `json:"ports"`
Daemons []*DaemonOptions `json:"daemons"`
P2P *P2POptions `json:"p2p"`
Storage *RedisOptions `json:"storage"`
Algorithm *AlgorithmOptions `json:"algorithm"`
PaymentOptions *PaymentOptions `json:"payment"`
}

func (o *Options) TotalFeePercent() float64 {
Expand Down
73 changes: 73 additions & 0 deletions config/payment.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,76 @@ func (r *Recipient) GetScript() []byte {

return r.script
}

type PaymentOptions struct {
Interval int64 `json:"interval"` // seconds between payout runs (default 600)
MinPayment float64 `json:"minPayment"` // minimum coin owed before a miner is paid

Daemon int `json:"daemon"` // index into daemons[] used for wallet RPC (default 0)

// PayMode selects how miners are rewarded:
// "prop" (default) proportional to that block's round shares
// "pplns" proportional to the last-N-difficulty window of shares
// "solo" the miner who found the block takes the whole reward
// "pps" pay a fixed rate per share immediately; found blocks
// just refill the pool wallet (the pool bears variance)
PayMode string `json:"payMode"`
// PPLNSWindow is the pplns look-back as a total share difficulty; <=0 falls
// back to the block's own round (so pplns degrades to prop).
PPLNSWindow float64 `json:"pplnsWindow"`
// PPSRate is the pps price paid per unit of share difficulty (in coin). It
// must be set (>0) when payMode is "pps".
PPSRate float64 `json:"ppsRate"`

// --- coin-fork configurability ---

// Magnitude is the number of base units (satoshis) in one coin, e.g. 1e8 for
// Bitcoin. 0 auto-detects from the wallet's getbalance precision.
Magnitude float64 `json:"magnitude"`

// MinConfirmations is coinbase maturity: a found block's reward is only paid
// once its generation transaction has at least this many confirmations
// (default 100; some forks use 30/40/240).
MinConfirmations int64 `json:"minConfirmations"`

// AddressCheckMethod is the wallet RPC used to verify the pool owns its
// payout address: "getaddressinfo" (Bitcoin Core 0.18+, default) or
// "validateaddress" (older cores / forks that kept it).
AddressCheckMethod string `json:"addressCheckMethod"`

// SendManyDummy is the leading dummy/account argument to sendmany. Bitcoin
// Core requires it and keeps it as "" (the default).
SendManyDummy string `json:"sendManyDummy"`
// OmitSendManyDummy drops the leading argument entirely for forks whose
// sendmany takes only the {address:amount} map.
OmitSendManyDummy bool `json:"omitSendManyDummy"`
}

// WithDefaults returns a copy with unset fork knobs filled with the Bitcoin
// Core defaults, so a minimal config still works on mainstream forks.
func (o *PaymentOptions) WithDefaults() *PaymentOptions {
c := *o
if c.Interval <= 0 {
c.Interval = 600
}
if c.MinConfirmations <= 0 {
c.MinConfirmations = 100
}
if c.AddressCheckMethod == "" {
c.AddressCheckMethod = "getaddressinfo"
}
if c.PayMode == "" {
c.PayMode = PayModeProp
}
c.PayMode = strings.ToLower(c.PayMode)
// SendManyDummy defaults to "" (its zero value), which is what Bitcoin Core wants.
return &c
}

// Reward schemes selectable via PayMode.
const (
PayModeProp = "prop"
PayModePPLNS = "pplns"
PayModeSolo = "solo"
PayModePPS = "pps"
)
15 changes: 15 additions & 0 deletions daemons/getbalance.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
package daemons

import (
"encoding/json"
"fmt"
)

type GetBalance float64

func BytesToGetBalance(b []byte) (GetBalance, error) {
var getBalance GetBalance
err := json.Unmarshal(b, &getBalance)
if err != nil {
return 0.0, fmt.Errorf("unmashal getBalance response %s failed with error %s", b, err)
}

return getBalance, nil
}
3 changes: 3 additions & 0 deletions daemons/getblocktemplate.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ func (dm *DaemonManager) GetBlockTemplate() (getBlockTemplate *GetBlockTemplate,
instance, result, _ := dm.Cmd("getblocktemplate",
[]interface{}{map[string]interface{}{"capabilities": []string{"coinbasetxn", "workid", "coinbase/append"}, "rules": rules}},
)
Comment on lines 100 to 102
if err != nil {
return nil, err
}

if result.Error != nil {
return nil, errors.New(fmt.Sprint("getblocktemplate call failed for daemon instance ", instance, " with error ", result.Error))
Expand Down
41 changes: 41 additions & 0 deletions daemons/gettransaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package daemons

import (
"encoding/json"
"fmt"
)

type GetTransaction struct {
Amount float64 `json:"amount"`
Confirmations int `json:"confirmations"`
Generated bool `json:"generated"`
Blockhash string `json:"blockhash"`
Blockheight int `json:"blockheight"`
Blockindex int `json:"blockindex"`
Blocktime int `json:"blocktime"`
Txid string `json:"txid"`
Walletconflicts []interface{} `json:"walletconflicts"`
Time int `json:"time"`
Timereceived int `json:"timereceived"`
Bip125Replaceable string `json:"bip125-replaceable"`
Details []GetTransactionDetail `json:"details"`
Hex string `json:"hex"`
}

type GetTransactionDetail struct {
Address string `json:"address"`
Category string `json:"category"`
Amount float64 `json:"amount"`
Label string `json:"label"`
Vout int `json:"vout"`
}

func BytesToGetTransaction(b []byte) (*GetTransaction, error) {
var getTransaction GetTransaction
err := json.Unmarshal(b, &getTransaction)
if err != nil {
return nil, fmt.Errorf("getTransaction call failed with error %s", err)
}

return &getTransaction, nil
}
Loading
Loading