Tema 2 - Implementarea în CUDA a arborelui Merkle și a algoritmului de consens Proof of Work din cadrul Bitcoin
Deadline soft: 18 Mai 2025, ora 23:55 11 Mai 2025, ora 23:55. Primiți un bonus de 10% din punctajul obținut pentru trimiterea temei înainte de 8 Mai 2025, ora 23:55. Veți primi o depunctare de 10% din punctajul maxim al temei pentru fiecare zi de întârziere, până la maxim 7 zile, adică până pe 18 Mai 2025, ora 23:55.
Deadline hard: 18 Mai 2025, ora 23:55.
-
Enunț
Implementarea în CUDA a arborelui Merkle și a algoritmului de consens Proof of Work din cadrul Bitcoin.
Disclaimer: Această temă a pornit de la algoritmul Bitcoin descris în
whitepaper, însă nu replică în totalitate algoritmul și structurile de date.
Obiectivele temei
Utilizarea eficientă a limbajului CUDA pentru programarea GPU-urilor, învățat la laborator;
Înțelegerea conceptelor de bază ale blockchain-ului;
Participarea la algoritmul de consens din perspectiva unui nod;
Introducere
Blockchain-ul este o tehnologie care a devenit cunoscută odată cu apariția criptomonedelor, precum Bitcoin-ul, Ethereum, etc. Este o bază de date descentralizată și distribuită a tranzacțiilor și datelor, care funcționează pe baza unui lanț de blocuri. Fiecare bloc conține un set de tranzacții și este legat de blocurile anterioare printr-un proces de criptare și verificare, cunoscut sub numele de “hashing”, formând astfel o structură sigură, imutabilă și transparentă. Dacă cineva ar încerca să modifice un bloc, ar trebui să modifice toate blocurile următoare, lucru extrem de dificil datorită mecanismelor criptografice.
Descentralizare: Datele sunt stocate și gestionate de o rețea descentralizată de noduri, eliminând astfel nevoia de o autoritate centrală.
Imutabilitate: Datele înregistrate în blocurile blockchain-ului sunt imutabile și nu pot fi modificate sau șterse ulterior.
Transparență: Tranzacțiile și înregistrările sunt publice și transparente, iar oricine poate să le verifice.
Securitate: Criptografia și algoritmii de consens asigură securitatea și integritatea datelor stocate în blockchain.
Algoritmul de consens
Calculatoarele participante la consens se numesc noduri (sau mineri în cazul PoW). Aceste noduri nu se cunosc și nu au încredere unele în altele.
Scopul unui mecanism de consens este de a aduce toate nodurile în acord, adică de a avea încredere unul în celălalt, într-un mediu în care nodurile nu au încredere unul în celălalt.
Minerii (aceștia veți fi voi) efectuează lucrări de calcul în rezolvarea unei probleme matematice complexe. Demonstrarea rezolvării problemei aduce de la sine și încredere.
Problema matematică poate deveni mai complexă, în funcție de numărul de participanți la consens.
Minerul care a rezolvat primul problema, va propaga răspunsul în rețea, iar acest va fi validat de către ceilalți participanți la rețea. Astfel, problema are 2 proprietăți:
Minerul care a rezolvat primul problema va fi recompensat. În cazul vostru, veți primi punctaj 😁
Structura unui bloc
Un bloc este format din:
1. Hash-ul blocului anterior, care creează conexiunea cu blocurile anterioare și asigură continuitatea și securitatea lanțului;
2. Root hash-ul tranzacțiilor (Merkle root), adică hash-ul rezultat dintr-un arbore Merkle construit pe baza tranzacțiilor din bloc. Permite validarea rapidă a tranzacțiilor fără a le parcurge individual. Algoritmul este următorul:
a. Se face hash-ul fiecărei tranzacții individuale.
b. Se grupează hash-urile câte două și se calculează un nou hash pentru fiecare pereche.
c. Se repetă procesul de grupare și hashing până când rămâne un singur hash - Merkle root-ul.
Dacă există un număr impar de hash-uri într-un nivel, ultimul hash se dublează pentru a forma o pereche.
Click pentru a vedea un exemplu de calcul Merkle root
Click pentru a vedea un exemplu de calcul Merkle root
Nivel 0:
Tx1: "VBJJJUBSYWLBPLUN" -- SHA256 --> "dad1020a77b640cad9a44b80e689f0b467e42e67e24a2bd23e10d10bc513dc20"
Tx2: "UBXGGEAYZTXLXKAL" -- SHA256 --> "9a64594a9bee37f5378ffa87bcc44b1412eedd542e007200af4af598f5c14429"
Tx3: "UILARBVQAAOWYDKV" -- SHA256 --> "5853c3fbf9099e07e1668e110f0c8f37a26e8706be1aa3b964593819c359bb5e"
Tx4: "NAHPEHOTTNBOJQHR" -- SHA256 --> "43dab3e61ac812e9c0b7e299d4ea603884eb1023d39a0f0568f0e69bb04e36b6"
Tx5: "VRKAKNASZPTJUVMQ" -- SHA256 --> "f80412748b9c56d59e9ca23d45f9dd28daab3e36fd110e2490dbeca485c0efaf"
Nivel 1:
Tx12: "dad1020a77b640cad9a44b80e689f0b467e42e67e24a2bd23e10d10bc513dc209a64594a9bee37f5378ffa87bcc44b1412eedd542e007200af4af598f5c14429" -- SHA256 --> "e5f4ed1fb64870a7a3ee6bf9c31ba3e1eb1887d3afa5c92482896b060323cf80"
Tx34: "5853c3fbf9099e07e1668e110f0c8f37a26e8706be1aa3b964593819c359bb5e43dab3e61ac812e9c0b7e299d4ea603884eb1023d39a0f0568f0e69bb04e36b6" -- SHA256 --> "06962284b21218613fdfb4146a775fc6586eff41c3834a15173ac83d53f2c40c"
Tx55: "f80412748b9c56d59e9ca23d45f9dd28daab3e36fd110e2490dbeca485c0efaff80412748b9c56d59e9ca23d45f9dd28daab3e36fd110e2490dbeca485c0efaf" -- SHA256 --> "fbc56527df5f7b9d33e1af001c04a559c84d588cc4199038a2735467ab830f66"
Nivel 2:
Tx1234: "e5f4ed1fb64870a7a3ee6bf9c31ba3e1eb1887d3afa5c92482896b060323cf8006962284b21218613fdfb4146a775fc6586eff41c3834a15173ac83d53f2c40c" -- SHA256 --> "5e74a2c7bbb58a67ad0a816f42e3dc10a1c2c82778f83f5ad73d5ad5d2301036"
Tx5555: "fbc56527df5f7b9d33e1af001c04a559c84d588cc4199038a2735467ab830f66fbc56527df5f7b9d33e1af001c04a559c84d588cc4199038a2735467ab830f66" -- SHA256 --> "e2d8d4a8b90fa567df034d02b2a0ca30e7fbe1ac651cffbc2cd08282a8131bb7"
Nivel 3:
Tx12345555 (Merkle root): "5e74a2c7bbb58a67ad0a816f42e3dc10a1c2c82778f83f5ad73d5ad5d2301036e2d8d4a8b90fa567df034d02b2a0ca30e7fbe1ac651cffbc2cd08282a8131bb7" -- SHA256 --> "a660484fcf1c3b54d470e172c0472e68eadadfc2c6078b98519703586fd5eaed"
3. Nonce (Number used only once) - un număr întreg random, pozitiv, pe 32 biți, pe care minerii încearcă să îl găsească, astfel încât aplicarea funcției de hashing, cum ar fi SHA-256, asupra block-ului rezultat prin includerea nonce-ului la finalul blocului, să fie mai mic decât o dificultate threshold (un alt hash, ales în funcție de numărul de 0-uri consecutive din prefix). Munca medie necesară pentru găsirea nonce-ului este exponențială în funcție de numărul de biți zero necesari și poate fi verificată prin executarea unui singur hash. Nonce-ul trebuie să îl găsiți prin metoda trial-and-error, pornind de la 0 și incrementând la fiecare iterație, pana la valoarea maximă pe 32 biți, i.e. uint32_max.
Click pentru a vedea un exemplu de generare a hash-ului unui bloc, în urma găsirii unui nonce valid
Click pentru a vedea un exemplu de generare a hash-ului unui bloc, în urma găsirii unui nonce valid
Fie dificultatea aleasă 4, i.e. "0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".
Hash-ul blocului anterior: "000000000000000000034158a91c1876f5fc2add1e69641e908956ac9de45b93".
Merkle root: "a660484fcf1c3b54d470e172c0472e68eadadfc2c6078b98519703586fd5eaed".
Nonce-ul găsit: 2429400
Hash-ul blocului rezultat: "000000000000000000034158a91c1876f5fc2add1e69641e908956ac9de45b93a660484fcf1c3b54d470e172c0472e68eadadfc2c6078b98519703586fd5eaed2429400" -- SHA256 --> "0000aea01a49077c7843be9428d087b1f3b02de9cdbd6f30464413ea0b1628b2" < "0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".
Hash-ul blocului rezultat în urma găsirii unui nonce valid va deveni “hash-ul blocului anterior” pentru următorul bloc de tranzacții. De aici vine denumirea de blockchain: se creează un lanț de blocuri, iar fiecare bloc depinde de cel anterior.
Nonce-ul valid găsit nu e unic. Pot exista mai multe nonce-uri care să genereze un hash al blocului mai mic decât dificultatea aleasă.
[1]
Descrierea temei
În cadrul acestei teme, veți participa ca nod într-un blockchain, unde procesarea tranzacțiilor se va face pe GPU. Veți lucra pe baza unui cod existent, în C, care simulează procesul de minare a blocurilor Bitcoin. În varianta inițială, algoritmul rulează în întregime serial, pe CPU. Scopul vostru este să eficientizați timpul de execuție, paralelizând două funcții esențiale, calcularea Merkle root-ului și găsirea nonce-ului, folosind CUDA, astfel încât să obțineți un speedup al acestor funcții, rulate pe GPU.
Fișierul de intrare
miner.cpp citește un fișier de forma <TEST_NAME>.in, ce conține:
Pe primul rând, în această ordine:
number_of_transactions – Numărul de tranzacții din fișier;
prev_block_hash – Hash-ul blocului anterior;
difficulty_zeros – Numărul de zerouri al dificultății;
max_nonce – Numărul maxim de nonce-uri de verificat;
max_transactions_in_a_block – Numărul maxim de tranzacții într-un bloc;
transaction_size – Dimensiunea, în bytes, a fiecărei tranzacții, numărând și caracterul nul, de la finalul șirului de caractere.
Urmează apoi, începând cu al doilea rând, tranzacțiile propriu-zise, în număr de number_of_transactions, fiecare pe câte un rând, de dimensiune transaction_size.
Click pentru a vedea un exemplu de fișier de input
Click pentru a vedea un exemplu de fișier de input
<test4.in>
100000,000000000000000000034158a91c1876f5fc2add1e69641e908956ac9de45b93,4,99999999,10000,226
NWLRBBMQBHCDARZOWKKYHIDDQSCDXRJMOWFRXSJYBLDBEFSARCBYNECDYGGXXPKLORELLNMPAPQFWKHOPKMCOQHNWNKUEWHSQMGBBUQCLJJIVSWMDKQTBXIXMVTRRBLJPTNSNFWZQFJMAFADRRWSOFSBCNUVQHFFBSAQXWPQCACEHCHZVFRKMLNOZJKPQPXRJXKITZYXACBHHKICQCOENDTOMFGDWDWFC
GPXIQVKUYTDLCGDEWHTACIOHORDTQKVWCSGSPQOQMSBOAGUWNNYQXNZLGDGWPBTRWBLNSADEUGUUMOQCDRUBETOKYXHOACHWDVMXXRDRYXLMNDQTUKWAGMLEJUUKWCIBXUBUMENMEYATDRMYDIAJXLOGHIQFMZHLVIHJOUVSUYOYPAYULYEIMUOTEHZRIICFSKPGGKBBIPZZRZUCXAMLUDFYKGRUOWZGI
OOOBPPLEQLWPHAPJNADQHDCNVWDTXJBMYPPPHAUXNSPUSGDHIIXQMBFJXJCVUDJSUYIBYEBMWSIQYOYGYXYMZEVYPZVJEGEBEOCFUFTSXDIXTIGSIEEHKCHZDFLILRJQFNXZTQRSVBSPKYHSENBPPKQTPDDBUOTBBQCWIVRFXJUJJDDNTGEIQVDGAIJVWCYAUBWEWPJVYGEHLJXEPBPIWUQZDZUBDUBZV
...
Procesarea datelor
Se citesc tranzacțiile și se grupează în blocuri. La fiecare max_transactions_in_a_block tranzacții, se face câte un bloc nou. Așadar, ultimul bloc minat conține un număr de tranzacții egal, sau mai mic decât această valoare.
Pentru fiecare grup:
Se construiește merkle root-ul tranzacțiilor.
Se formează blocul prin concatenarea prev_block_hash și merkle_root.
Se caută un nonce astfel încât hash-ul rezultat să aibă difficulty_zeros zerouri la început.
Dacă nu se găsește un nonce valid până la max_nonce, blocul e invalidat.
În realitate, blocul include mai multe date, precum un timestamp Unix, ceea cea oferă nondeterminism hash-ului blocului. Dacă un nonce nu e găsit, la următoarea încercare, timestamp-ul va fi diferit, ceea ce face din nou căutarea unui nonce o opțiune validă. Pentru o testare ușoară, nu vom include un astfel de parametru în tema noastră. De asemenea, vom lucra cu fișiere de input care asigură existența a cel puțin un nonce.
Fișierul de ieșire
Rezultatele se scriu în fișierul <TEST_NAME>.out, în următorul format, pentru fiecare bloc
BLOCK_ID,NONCE,BLOCK_HASH,TIME_FOR_MERKLE_ROOT_COMPUTATION,TIME_FOR_NONCE_COMPUTATION,TIME_SUM
, unde TIME_SUM = TIME_FOR_MERKLE_ROOT_COMPUTATION + TIME_FOR_NONCE_COMPUTATION
După procesarea tuturor tranzacțiilor, se scriu pe ultimul rând timpii totali pentru fiecare din cele 3 coloane de timpi, adică pentru toate blocurile găsite. Timpii sunt aproximați la cinci zecimale.
TOTAL_TIME_FOR_MERKLE_ROOT_COMPUTATION,TOTAL_TIME_FOR_NONCE_COMPUTATION,TOTAL_TIME_SUM
Click pentru a vedea un exemplu de fișier de ieșire
Click pentru a vedea un exemplu de fișier de ieșire
1,33865,0000517db16db1b0a4b3c2a1eee14ebc3c6beaae6420dc90a0643d23a03c167a,0.10294,0.14677,0.24970
2,4050,00007ddf6566d4687489b21cd2764bd286e6958dd7d365fbe30de7ca4b9bbb50,0.09830,0.01761,0.11592
3,166127,00002663dafd560b4a42eca2fba9ee2c4692318b271a40e087af715c238d879d,0.09814,0.65140,0.74954
.....
10,158592,0000f5e98829b6fc7d24b890e621b5ca64a6c7c4e4d097a126eae066b54b8de0,0.07127,0.49897,0.57024
0.79865,2.10661,2.90526
Ce aveți de făcut
Înțelegerea algoritmului pe CPU: Veți porni de la directorul cpu_miner, ce conține implementarea deja făcută serial, pe CPU. Acesta nu face parte din rezolvarea temei. Scopul codului este de a înțelege funcționalitatea pe CPU, ca apoi să optimizați căutarea nonce-ului si generarea Merkle root-ului pe GPU, folosind CUDA.
Implementarea algoritmului pe GPU: Veți porni de la directorul gpu_miner, în care veți realiza implementarea în CUDA a logicii funcțiilor discutate anterior. Pentru a vă ajută, aveți deja implementate funcții ajutătoare în utils.cu. Vă recomandăm să va folosiți de ele în implementarea voastră.
Lucrați DOAR în fișierul utils.cu. La încărcarea pe Moodle, veți încărca DOAR fișierul utils.cu și un fișier README.md cu descrierea temei și eventuale observații. Restul fișierelor vor fi ignorate, fiind suprascrise la testarea automată a temei!
Compilarea și rularea temei se vor face EXCLUSIV pe coada xl (testarea automată va fi, de asemenea, făcută pe xl). Nu este nevoie să modificați nimic în Makefile, regulile sunt deja făcute. Tot ce trebuie să faceți este să apelați make, make run și make clean, ca la laborator:
To compile: make
To run: make run TEST=<TEST_NAME> (example: make run TEST=test4)
To clean: make clean
Tema va fi verificată automat, folosind infrastructura de testare, pe baza unei suite de teste private.
Arhiva temei va fi încărcată pentru testare și validare pe
Moodle. Aceasta va avea denumirea <NUME_STUDENT>_<PRENUME_STUDENT>_<GRUPA>_ASC_TEMA2.zip și va cuprinde obligatoriu fișierele:
Repository-ul pe care îl folosiți în procesul de implementare este necesar să fie privat.
Vă recomandăm să folosiți template-ul de README de
aici (includerea unui link către un repo de git este opțională).
10 pct EXPLICAȚII:
Implementarea descrisă în README, alături de rezultate și o discuție asupra lor.
Programul compilează, codul nu are disfuncționalități majore.
Punctajul la testarea automată (
Moodle) se va acorda în funcție de performanța (durata de rulare) a celor două funcții modificate, pentru o suită de teste private, generate de noi, în felul urmator:
Fiecare test (asociat unui fișier de intrare) va fi rulat de 5 ori.
Se vor selecta timpii minimi obținuți pentru fiecare funcție, independent unul de altul (de exemplu, timpul minim pentru generarea Merkle root-ului poate proveni din a 3-a rulare, iar timpul minim pentru găsirea nonce-ului valid din a 4-a) și vor fi aproximați la 2 zecimale (exemplu: 0.044 se aproximeaza la 0.04, 0.045 la 0.05, si 0.046 la 0.05).
Timpii rezultați vor fi comparați cu threshold-urile de timp aferente fiecărui test, pentru stabilirea punctajului.
Pentru a testa local eficiența implementării, înainte de încărcarea pe
Moodle pentru testarea automată, vă puteți ajuta de următoarele treshold-uri de timp în cazul parametrilor predefiniți din testul 4 (
test4.in):
Observații
Temele vor fi testate împotriva plagiatului. Orice tentativă de copiere va fi depunctată conform
regulamentului.
Rezultatele notării automate este orientativă și poate fi afectată de corectarea manuală.
Bonus
Temele care obțin punctaj maxim pentru EXPLICAȚII și IMPLEMENTARE vor fi eligibile pentru un bonus de 10% din punctajul total.
Bonusul se acordă pentru o implementare deosebit de eficientă.
Acordarea bonusului rămâne la discreția evaluatorului, pe baza calității și inovației soluției.
Resurse necesare realizării temei
Pentru a clona repo-ul și a accesa resursele temei 2:
student@fep8:~$ git clone https://gitlab.cs.pub.ro/asc/asc-public.git
student@fep8:~$ cd asc-public/assignments/2-cuda_proof_of_work
Suport, întrebări și clarificări
Pentru întrebări sau nelămuriri legate de temă folosiți forumul temei.
E recomandat ca orice întrebare să conțină o descriere cât mai clară a eventualei probleme. Întrebări de forma: “Nu merge X. De ce?” fără o descriere mai amănunțită vor primi un răspuns mai greu.
ATENȚIE să nu postați imagini cu părți din soluția voastră pe forumul pus la dispoziție sau orice alt canal public de comunicație. Dacă veți face acest lucru, vă asumați răspunderea dacă veți primi copiat pe temă.
Bibliografie