2015-09-12 28 views
14

Sto provando a scrivere un semplice cercatore di nonce-proof in python.Python interrompe più processi quando si restituisce un risultato?

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 

Ora sto cercando di fare questo multiprocessed, quindi è possibile utilizzare tutti i core della CPU e trovare il nonce più veloce. La mia idea è quella di utilizzare multiprocessing.Pool ed eseguire la funzione proof_of_work più volte, passando due params num_of_cpus_running e this_cpu_id in questo modo:

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 

Quindi, se ci sono 4 core, ognuno calcolerà nonce come questo:

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 ... 

Quindi, devo riscrivere proof_of_work così quando qualcuno dei processi trova un nonce, tutti gli altri smettono di cercare i nonces, tenendo conto che il nonce trovato deve essere il valore più basso possibile per cui i byte richiesti sono 0. Se una CPU accelera per qualche motivo e restituisce un n valido una volta superiore al più basso nonce valido, la prova di lavoro non è valida.

L'unica cosa che non so come fare è la parte in cui un processo A si interrompe solo se il processo B trova un nonce che è inferiore al nonce che viene calcolato in quel momento dal processo A. Se il suo più in alto, A continua a calcolare (per ogni evenienza) fino a quando arriva al nonce fornito da B.

Spero di essermi spiegato correttamente. Inoltre, se c'è un'implementazione più veloce di tutto ciò che ho scritto, mi piacerebbe sentirne parlare. Grazie mille!

+0

@MaartenBodewes Ho paura di non capire il tuo punto, forse sono ancora troppo programmatore nex xD. Cpu_id è solo un numero che assegno ad ogni processo. Per prima cosa ho il numero di core cpu 'x = multiprocessing.cpu_count()' e quindi avvio x processi, ognuno con un ID diverso che incrementa di un solo 1. Spero di averlo capito correttamente. – mesafria

+0

Scusate, è stato solo un fraintendimento da parte mia. Non ho mai esaminato a fondo queste prove dei protocolli di lavoro. Disprezzo cose come il bitcoin: non mi piacciono i protocolli che convertono l'energia in denaro, preferisco il contrario. –

+1

@MaartenBodewes la prova di lavoro non è solo utilizzata in bitcoin. Lo uso per prevenire gli attacchi DDoS. –

risposta

4

Un'opzione facile è utilizzare i micro-batch e verificare se è stata trovata una risposta. I lotti troppo piccoli comportano un sovraccarico derivante dall'avvio di processi paralleli, mentre dimensioni troppo grandi fanno sì che altri processi eseguano un lavoro supplementare mentre un processo ha già trovato una risposta. Ogni lotto dovrebbe richiedere da 1 a 10 secondi per essere efficiente.

codice di esempio:

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)) 

uscita (un computer portatile con 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 

Con single core ci sono voluti 76.468 secondi. Comunque questo non è di gran lunga il modo più efficace per trovare una soluzione, ma funziona. Ad esempio se lo salt è lungo, lo stato SHA-256 potrebbe essere precalcolato dopo che il sale è stato assorbito e continuare la ricerca di forza bruta da lì. Anche l'array di byte potrebbe essere più efficiente dello hexdigest().

+0

Spettacolo eccellente di parallelismo. Tuttavia, risponde alla domanda dell'OP su come interrompere gli altri thread una volta che un thread ha trovato una risposta? – RobertB

+0

È vero, non risponde al 100% alla domanda originale, ma risolve il problema sapendo che nel peggiore dei casi altri thread eseguono alcuni secondi di lavoro extra. – NikoNyrh

6

Un metodo generale per fare questo è quello di:

  1. pensare di pacchetti di lavoro, ad esempio per eseguire il calcolo di un particolare gamma, una gamma non dovrebbe richiedere molto tempo, dire 0,1 secondi per una seconda
  2. hanno qualche manager distribuire i pacchetti di lavoro al lavoratore
  3. dopo un pacchetto di lavoro è stato concluso, dire la Gestisci il risultato e richiedi un nuovo pacchetto di lavoro
  4. se il lavoro è stato eseguito e un risultato è stato trovato accetta i risultati dai lavoratori e dai loro un segnale che non devono essere eseguiti più lavori - i lavoratori possono ora terminare in sicurezza

In questo modo non è necessario verificare con il gestore ogni iterazione (w che potrebbe rallentare tutto), o fare cose brutte come fermare un thread a metà sessione. Inutile dire che il manager deve essere sicuro.

Questo si adatta perfettamente al modello, in quanto sono necessari i risultati degli altri lavoratori, anche se è stato trovato un risultato.


Si noti che nel modello, potrebbe essere che un thread potrebbe non essere sincronizzato con gli altri thread, in ritardo. Non vuoi fare altri milioni di calcoli una volta trovato un risultato. Sto solo ribadendo questo dalla domanda perché penso che la modella sia sbagliata. È necessario correggere il modello anziché correggere l'implementazione.

+2

Errore precedente: attribuire al gestore una priorità bassa. Dato che il manager non sta facendo nulla per la maggior parte del tempo, dovrebbe avere una priorità * almeno altrettanto elevata * dei lavoratori, ma preferibilmente superiore. Altrimenti un lavoratore potrebbe dover aspettare per sempre un nuovo pacchetto. E sì, io ero quel novizio una volta :) –

+1

Nota: non sono un esperto di Python. Se qualcuno pubblica un'implementazione affidabile di cui sopra in Python, sentiti libero di accettarlo. –

+2

Anche molto più flessibile. Aggiungi/rimuovi lavoratori, modifica la dimensione del pacchetto di lavoro, aggiungi un'opzione all'utente per indicare quando fermarsi, ecc. Ecc. –

3

È possibile utilizzare multiprocessing.Queue(). Avere una coda per CPU/processo. Quando un processo trova un nonce, lo mette sulla coda di altri processi.Altri processi controllare la loro coda (non bloccante) in ogni iterazione del ciclo while e se c'è qualcosa su di esso, decidono di continuare o interrompere in base al valore nella coda:

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 è una lista di code (ogni coda = multiprocessing.Queue()) appartenente ad altri processi.

Se si decide di utilizzare le code come suggerito, si dovrebbe essere in grado di scrivere un'implementazione migliore/migliore dell'approccio precedente.