Table of Contents

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.

  • Dată publicare: 27 Aprilie 2025

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

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.

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:

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

Î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

To compile:  make
To run:      make run TEST=<TEST_NAME> (example: make run TEST=test4)
To clean:    make clean

Evaluarea performanței și notarea

Tema va fi verificată automat, folosind infrastructura de testare, pe baza unei suite de teste private.

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

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

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

[1]Bitcoin Whitepaper