Mining Sicherheit

Im Prinzip hat @lerpy die Frage schon komplett beantwortet. Ich war mir allerdings auch nie ganz sicher, was genau vom Pool vorgegeben und vom Miner zurückübermittelt wird.

Deshalb habe ich mir mal grobe Beschreibungen des Stratum V1 Protokolls angeschaut. Ich denke das wird aktuell noch von nahezu allen Pools verwendet (s.a. Artikel zu Stratum V2).


Registrierung beim Pool und Erhalten von Mining Jobs

Nachdem sich ein Miner beim Pool registriert, authorisiert und auf Updates subscribed hat, sendet der Pool dem Miner regelmäßig Mining Jobs über mining.notify(…), wobei folgende Parameter übertragen werden:

  • Job ID job_id
  • Vorheriger Block Hash prevhash
  • Alle notwendigen Infos für die merkle_root:
    o Coinbase Transaktion, ohne Extranonce
    o Merkle Branches der vorgegebenen Transaktionen im Block
  • Block Version version
  • Block Difficulty nbits
  • Timestamp ntime
  • Clean Jobs (Hilfsparameter)

Außerdem teilt der Pool dem Miner zu Beginn, sowie später nach Bedarf über mining.set_difficulty(difficulty) die interne Difficulty für die Shares im Pool, sowie über mining.set_extranonce(extranonce1, extranonce2_size) Infos über die Extranonce mit.

Die Coinbase Transaktion ist die Transaktion, über welche die Rewards an eine Adresse des Pools ausgezahlt werden (Subsidy + Transaktionsgebühren). Entsprechend wird diese Transaktion bis auf einen variablen Anteil vom Pool vorgegeben.

Die extranonce ist ein Teil der Coinbase Transaktion, in die ein beliebiger Inhalt geschrieben werden kann. Sie wird von den Minern als variabler Parameter, zusätzlich zur eigentlichen nonce genutzt, um im Mining Prozess verschiedene Blockkandidaten zu generieren.

Die extranonce wird aus den Teilen extranonce1, extranonce2 und evtl. folgenden Nullen zusammengesetzt.
Der erste Teil extranonce1 wird vom Pool spezifisch für jeden Miner vorgegeben, damit keine zwei Miner an denselben Blockkandidaten arbeiten.
Der zweite Teil extranonce2 kann vom Miner im Rahmen der freigegebenen Größe extranonce2_size als variabler Laufparameter genutzt werden.

Anschließend kann der Miner nun einen gültigen Block suchen, indem er nonce und extranonce2, sowie nach Belieben auch den Timestamp nTime in gewissem Rahmen nach Vorgabe des Pools variiert.


Mining Vorgang

Es werden laufend neue Blockkandidaten gebildet und gehashed, um das Difficulty Target zu unterbieten. Genauer gesagt wird der Block Header gehashed.

Alle Transaktionen des Blocks werden mittels eines Merkle Trees zu einem einzigen Hash, der merkle_root, verwurstet, die ein Teil des Block Headers ist. Ein Vor-Verwursten übernimmt bereits der Pool, welcher dem Miner am Ende nur die Coinbase Transaktion und die vorgehashten anderen Zweige des Merkle Trees bereitstellt (Merkle Branches).

Zur Bildung eines Block Header Kandidaten wird also die vom Pool erhaltene Coinbase Transaktion um die extranonce1 und den aktuellen Wert der extranonce2 ergänzt, und zusammen mit den erhaltenen Merkle Branches zur merkle_root gehashed.
Anschließend wird aus der merkle_root, den aktuellen Werten für nonce und ntime, sowio den vom Pool erhaltenen version, prevhash und nbits der Block Header gebildet und gehashed.

Sollte der Hash über dem Target liegen ist der Block wertlos. Es wird also die nonce um eins hochgezählt und der Header erneut gehashed.
Nach jedem erfolglosen kompletten Durchlauf des nonce-Wertebereichs und eventueller Variation von ntime, wird die extranonce2 um eins hochgezählt. Da sich durch die extranonce2 auch die Coinbase Transaktion ändert, muss die merkle_root neu berechnet werden. Anschließend wird dann erst einmal wieder nur die nonce beginnend bei 0 hochgezählt.


Finden eines innerhalb des Pools gültigen Blocks (Share)

Zum Konzept der „Shares“ siehe den Beitrag von @DasPie, oder z.B. hier: Mining: Blocks vs. Shares

Jeder gefundene Block, der das Pool-interne Difficult Target unterschreitet, wird dem Pool vom Miner mitgeteilt. Das macht der Miner mittels mining.submit(username, job_id, extranonce2, ntime, nonce).

Es wird also nicht der komplette gefundene Block an den Pool übermittelt, sondern nur die erfolgreichen Werte von nonce, extranonce2 und ntime. Die restlichen Block-Parameter kennt der Pool anhand der job_id. Der Pool kann also einfach selbst überprüfen ob der Block gültig ist.


Block einfach selbst claimen, oder Shares an mehrere Pools übermitteln?

Zur eigentlichen Frage hier im Thread:

oder aus einem anderen Thread:

Die Fragen beantworten sich denke ich nun von selbst. Der Pool gibt die Coinbase Transaktion vor, in der eine Reward-Auszahlungs-Adresse des Pools eingetragen ist. Sollte man einen gültigen Block finden, könnte man ihn zwar selbst broadcasten, aber der Reward würde damit trotzdem an den Pool gehen.

Und findet man einen gültigen Share, hat man gar nicht die Möglichkeit diesen an mehrere Pools zu übermitteln. Schließlich teilt man den Pools nur die gefunden Werte von nonce, extranonce2 und ntime zu einer job_id mit. Die anderen Pools würden bei der Überprüfung merken, dass diese Werte zusammen mit den vorgegebenen Mining Job Parametern keinen gültigen Share ergeben.

Noch unabhängig vom Thema hier:
Ein Miner kann übrigens auch bei Stratum V1 zumindest mittels mining.get_transactions(„job id“) alle Transaktionen des aktuellen Jobs bzw. Block-Kandidaten beim Pool abfragen. Man hätte so die Möglichkeit zu prüfen, ob der Pool, für den man mined, zensiert. Ich vermute aber, dass die meisten Mining Clients das nicht machen.


Quellen:
Past and future of bitcoin mining protocols: Stratum V2 overview | Braiins
Stratum mining protocol - Bitcoin Wiki

6 „Gefällt mir“