15

Python で単純なプルーフ オブ ワーク ナンス ファインダーを作成しようとしています。

def proof_of_work(b, nBytes):
    nonce = 0
    # while the first nBytes of hash(b + nonce) are not 0
    while sha256(b + uint2bytes(nonce))[:nBytes] != bytes(nBytes):
        nonce = nonce + 1
    return nonce

今、私はこれをマルチプロセスで実行しようとしているので、すべての CPU コアを使用して nonce をより速く見つけることができます。私の考えはmultiprocessing.Pool、関数proof_of_workを複数回使用して実行し、2つのパラメーターを渡すnum_of_cpus_runningことthis_cpu_idです。

def proof_of_work(b, nBytes, num_of_cpus_running, this_cpu_id):
    nonce = this_cpu_id
    while sha256(b + uint2bytes(nonce))[:nBytes] != bytes(nBytes):
        nonce = nonce + num_of_cpus_running
    return nonce

したがって、4 つのコアがある場合、すべてのコアが次のように nonce を計算します。

core 0: 0, 4, 8, 16, 32 ...
core 1: 1, 5, 9, 17, 33 ...
core 2: 2, 6, 10, 18, 34 ...
core 3: 3, 7, 15, 31, 38 ...

そのため、プロセスのいずれかがナンスを見つけたときに、他のすべてのプロセスがナンスの検索を停止するように書き直すproof_of_work必要があります。見つかったナンスは、必要なバイト数が 0 である可能な最小値でなければならないことを考慮してください。CPU が高速化する場合何らかの理由で有効なナンスよりも高い有効なナンスを返す場合、プルーフ オブ ワークは有効ではありません。

方法がわからない唯一のことは、プロセス A が現在計算しているノンスよりも低いノンスをプロセス B が見つけた場合にのみプロセス A が停止する部分です。 Bによって提供されたナンスに到達するまで(念のため)計算します。

私は自分自身を正しく説明したことを願っています。また、私が書いたもののより高速な実装があれば、それについて聞きたいです。どうもありがとうございました!

4

4 に答える 4

7

簡単なオプションの 1 つは、マイクロバッチを使用して、答えが見つかったかどうかを確認することです。バッチが小さすぎると、並列ジョブの開始によるオーバーヘッドが発生します。サイズが大きすぎると、1 つのプロセスが既に回答を見つけている間に、他のプロセスが余分な作業を行うことになります。各バッチが効率的になるには、1 ~ 10 秒かかります。

サンプルコード:

from multiprocessing import Pool
from hashlib import sha256
from time import time


def find_solution(args):
    salt, nBytes, nonce_range = args
    target = '0' * nBytes

    for nonce in xrange(nonce_range[0], nonce_range[1]):
        result = sha256(salt + str(nonce)).hexdigest()

        #print('%s %s vs %s' % (result, result[:nBytes], target)); sleep(0.1)

        if result[:nBytes] == target:
            return (nonce, result)

    return None


def proof_of_work(salt, nBytes):
    n_processes = 8
    batch_size = int(2.5e5)
    pool = Pool(n_processes)

    nonce = 0

    while True:
        nonce_ranges = [
            (nonce + i * batch_size, nonce + (i+1) * batch_size)
            for i in range(n_processes)
        ]

        params = [
            (salt, nBytes, nonce_range) for nonce_range in nonce_ranges
        ]

        # Single-process search:
        #solutions = map(find_solution, params)

        # Multi-process search:
        solutions = pool.map(find_solution, params)

        print('Searched %d to %d' % (nonce_ranges[0][0], nonce_ranges[-1][1]-1))

        # Find non-None results
        solutions = filter(None, solutions)

        if solutions:
            return solutions

        nonce += n_processes * batch_size


if __name__ == '__main__':
    start = time()
    solutions = proof_of_work('abc', 6)
    print('\n'.join('%d => %s' % s for s in solutions))
    print('Solution found in %.3f seconds' % (time() - start))

出力 (Core i7 を搭載したラップトップ):

Searched 0 to 1999999
Searched 2000000 to 3999999
Searched 4000000 to 5999999
Searched 6000000 to 7999999
Searched 8000000 to 9999999
Searched 10000000 to 11999999
Searched 12000000 to 13999999
Searched 14000000 to 15999999
Searched 16000000 to 17999999
Searched 18000000 to 19999999
Searched 20000000 to 21999999
Searched 22000000 to 23999999
Searched 24000000 to 25999999
Searched 26000000 to 27999999
Searched 28000000 to 29999999
Searched 30000000 to 31999999
Searched 32000000 to 33999999
Searched 34000000 to 35999999
Searched 36000000 to 37999999
37196346 => 000000f4c9aee9d427dc94316fd49192a07f1aeca52f6b7c3bb76be10c5adf4d
Solution found in 20.536 seconds

シングルコアで76.468秒かかりました。とにかく、これは解決策を見つける最も効率的な方法ではありませんが、機能します。たとえば、saltが長い場合SHA-256、salt が吸収された後に状態を事前に計算し、そこからブルート フォース検索を続行できます。また、バイト配列はhexdigest().

于 2015-10-08T12:45:16.007 に答える
6

これを行う一般的な方法は次のとおりです。

  1. たとえば、特定の範囲の計算を実行する場合、範囲は 0.1 秒から 1 秒など、長くかかるべきではありません。
  2. マネージャに作業パケットをワーカーに配布してもらいます
  3. ワーク パケットが終了した後、マネージャーに結果を伝え、新しいワーク パケットを要求します。
  4. 作業が完了し、結果が見つかった場合は、ワーカーからの結果を受け入れ、これ以上作業を実行しないという合図を送ります - ワーカーは安全に終了できます

この方法では、反復ごとにマネージャーに確認する必要がなく (すべてが遅くなります)、セッションの途中でスレッドを停止するなどの厄介なことを行う必要もありません。言うまでもなく、マネージャーはスレッドセーフである必要があります。

結果が見つかったとしても、他のワーカーの結果が必要なため、これはモデルに完全に適合します。


モデルでは、スレッドが他のスレッドと同期しなくなり、遅れる可能性があることに注意してください。結果が見つかったら、別の百万回の計算を実行したくありません。モデルが間違っていると思うので、質問からこれを繰り返しています。実装を修正するのではなく、モデルを修正する必要があります。

于 2015-09-12T13:53:17.900 に答える
3

multiprocessing.Queue() を使用できます。CPU/プロセスごとにキューを用意します。プロセスが nonce を見つけると、それを他のプロセスのキューに入れます。他のプロセスは、while ループの反復ごとにキュー (非ブロッキング) をチェックし、キューに何かがある場合は、キューの値に基づいて続行または終了を決定します。

def proof_of_work(b, nBytes, num_of_cpus_running, this_cpu_id, qSelf, qOthers):
    nonce = this_cpu_id
    while sha256(b + uint2bytes(nonce))[:nBytes] != bytes(nBytes):
        nonce = nonce + num_of_cpus_running
        try:
            otherNonce = qSelf.get(block=False)
            if otherNonce < nonce:
                return
        except:
            pass
    for q in qOthers:
        q.put(nonce)
    return nonce

qOthers は、他のプロセスに属するキュー (各 queue=multiprocessing.Queue() ) のリストです。

私が提案したようにキューを使用することにした場合は、上記のアプローチのより優れた/より優れた実装を作成できるはずです。

于 2015-10-05T21:09:43.930 に答える