Venerito Gianmarco
Posted on January 9, 2023
Con le piattaforme tradizionali basate su server web2, tenere traccia dei dati e delle informazioni degli utenti è molto più facile che con le blockchain. Questo perché c'è un unico server centralizzato che memorizza lo stato degli account degli utenti. Non c'è bisogno di consenso o di risolvere le discrepanze perché c'è un solo luogo centrale che memorizza le informazioni.
Tuttavia, quando si passa a un sistema decentralizzato, il problema della memorizzazione dei saldi degli utenti diventa complicato. Le reti decentralizzate come Bitcoin ed Ethereum hanno bisogno di modelli specifici per tenere traccia dello stato degli utenti. Bitcoin utilizza il modello UTXO per tenere traccia dei saldi degli utenti. Ethereum e altre catene EVM utilizzano il modello di account per tenere traccia dei saldi degli utenti.
Approfondiamo... :)
UTXO è l'acronimo di "Unspent Transaction Output". Vedrete il termine UTXO usato un po' in questo post.
Bitcoin utilizza il modello UTXO, quindi ci piace includerlo perché ci aiuta a capire i compromessi nei modelli di archiviazione della blockchain e ci aiuta a fare un confronto con Ethereum e il suo uso del modello Account.
Le transazioni
Il miglior punto di partenza, prima di esaminare come le blockchain tengono traccia dei saldi degli utenti, è il luogo in cui i saldi degli utenti hanno inizio: la transazione. Esploriamo con la seguente domanda:
Di cosa abbiamo bisogno in una transazione?
Principalmente, di 3 cose:
1) importo: l'importo da inviare a qualcuno
2) pagante: la persona che invia l'importo del trasferimento
3) beneficiario: la persona che riceve l'importo del trasferimento.
Dal momento che lavoriamo in sistemi basati su una crittografia molto sicura, abbiamo bisogno di un'altra cosa per completare tutto ciò che è necessario per una transazione blockchain di successo: l'autorizzazione del pagante. Una sorta di autorizzazione non falsificabile fornita dall'iniziatore della transazione.
Questo quarto elemento finirebbe per essere la firma digitale, che è fondamentalmente un hash estremamente difficile da replicare se non si dispone degli input corretti - in questo caso, le chiavi private di un utente. Senza le chiavi private, non è possibile autorizzare un pagamento. L'unico modo per farlo sarebbe quello di "hackerare" la crittografia di base, cosa praticamente impossibile.
Qual è lo scopo di una transazione?
Cambiare lo stato di un utente! Se Alice invia a Bob 5 $DAI, il saldo $DAI di Alice dovrebbe andare a -5, quello di Bob a +5. La transazione di Alice è responsabile della modifica dello stato dei loro saldi. La modifica dello stato è estremamente importante nelle blockchain (che sono tipicamente reti basate sulle transazioni!), quindi tenetelo a mente!
Bitcoin, Ethereum e le normali banche si affidano a modelli basati sulle transazioni per tenere traccia dei saldi degli utenti. Diamo un'ulteriore occhiata qui di seguito...
Modello basato sul conto
Se avete un conto corrente bancario, conoscete bene questo modello per tenere traccia dei saldi degli utenti. Il modello basato sui conti è proprio questo: conti. Tiene traccia dei saldi degli utenti in base allo stato generale del loro conto, senza tenere traccia di ciò che costituisce il saldo stesso. In altre parole, un libro mastro basato sui conti segnerebbe una voce come questa:
Acct #12345 -> Nome: Rick Sanchez -> Saldo: 142,62 dollari
Notate come lo stato del conto sia mantenuto ad un livello molto alto? Il saldo del conto di Rick è un importo in dollari e centesimi e basta. Non vengono fornite ulteriori informazioni sulla ripartizione del saldo, ad esempio: 142,62 dollari corrispondono a una banconota da 100 dollari, una banconota da 20 dollari, due banconote da 10 dollari, otto quarti di dollaro, cinque monete, due monetine e due penny. Quando Rick si reca al bancomat e preleva dal suo saldo, lo riceve in qualsiasi banconota + spiccioli che la banca ha a disposizione, non negli spiccioli esatti che sono serviti per formare il saldo.
Come si presenta una transazione in un modello basato sul conto?
Alice ha un saldo totale di 60 dollari
Bob ha un saldo totale di 20 dollari
Bob invia ad Alice 5 dollari
Al saldo di Bob vengono sottratti 5 dollari, se il saldo rimanente è maggiore di 0 si procede, altrimenti si torna indietro
Il saldo di Alice viene sommato a 5 dollari
Il libro mastro viene contrassegnato da entrambe le parti per aggiornare i saldi totali e questa è la fine della transazione in un modello basato sui conti.
Questo potrebbe sembrare strano. Perché dovremmo tenere traccia di questi dettagli per qualcosa di così semplice come un saldo totale? Vediamo un modello per la conservazione dei saldi degli utenti che include questa funzione: il modello UTXO.
Modello basato su UTXO
Ethereum utilizza il modello basato sugli account, mentre Bitcoin utilizza gli UTXO (acronimo di Unspent Transaction Outputs) per tenere traccia dello stato e dei saldi degli utenti.
Il modello UTXO è molto diverso dal modello basato sui conti. È un po' più complesso, soprattutto perché non è un'interfaccia familiare come il modello del conto! Tuttavia, presenta alcune caratteristiche interessanti...
Note importanti sugli UTXO:
1) Tutti gli UTXO non sono fungibili (curiosità: la prima raccolta NFT in assoluto è stata... Bitcoin!).
2) Per spendere un UTXO, è necessario fare riferimento a quello specifico UTXO. Gli UTXO di un utente sono sparsi in blocchi.
3) Una volta che un UTXO viene "consumato", qualsiasi resto della transazione crea nuovi UTXO che rappresentano gli importi del cambiamento.
4) Un UTXO, spesso chiamato "moneta", può essere speso una sola volta. Non è possibile spendere due volte!
5) In Bitcoin, a ogni UTXO è associato uno script. Gli script sono sostanzialmente un codice programmato che ogni UTXO memorizza. Di solito contengono le condizioni per sbloccare l'UTXO per ulteriori spese.
Modello di conto vs UTXO
Conclusione
Decidere quale modello adottare è un gioco di compromessi progettuali. Ethereum utilizza il modello delle transazioni basato sugli account, che deve essere più flessibile per tenere conto dei numerosi elementi di stato in movimento nel sistema. Bitcoin utilizza gli UTXO, in quanto è una rete progettata appositamente per essere il più semplice e senza stato possibile.
(UTXO) - Uscite di transazione non spese
Bitcoin utilizza gli Output di transazione non spesi per gestire la proprietà delle monete da parte degli utenti. Ciò si contrappone al modello basato sugli account utilizzato da Ethereum, che tiene traccia dei saldi di determinati indirizzi.
Consideriamo un paio di esempi.
Esempio UTXO 1
Bob gestisce un minatore Bitcoin. Calcola con successo un blocco e si ricompensa con 12,5 BTC secondo le regole di emissione. Questo è un nuovissimo Unspent Transaction Output (UTXO) che Bob ha introdotto nel sistema.
Supponiamo ora che Bob voglia inviare ad Alice 6,0 BTC. Può farlo utilizzando il suo UTXO con 12,5 BTC. Ma, aspettate, Bob non vuole inviare ad Alice 12,5 BTC! Come gestire il resto?
Si scopre che Bitcoin consente di designare più uscite per ogni transazione. In questa particolare transazione, creeremo un UTXO di 6,0 BTC per Alice e un altro UTXO di 6,5 BTC per Bob (il resto). Quindi, contrassegneremo l'UTXO di 12,5 BTC come speso, poiché è stato utilizzato come ingresso per la transazione. Ottimo!
Esempio UTXO 2
Una cosa che può accadere spesso quando si utilizza questo modello è che gli utenti si ritrovino con molti piccoli UTXO. Man mano che Alice effettua transazioni con la rete, l'UTXO si frammenta in uscite più piccole, finché non le rimangono 3 UTXO del valore di 1,0 BTC, 1,5 BTC e 0,8 BTC.
Supponiamo che Alice voglia acquistare qualcosa per 3,0 BTC. Può farlo specificando diversi input alla transazione. Può inserire tutti e tre i suoi UTXO come input nella transazione per un totale di 3,3 BTC. Dopo l'esecuzione della transazione, riceverà un nuovo UTXO di 0,3 BTC e i suoi input precedenti saranno tutti contrassegnati come spesi.
Ok, per ora gli esempi sono sufficienti! Impariamo da soli con
alcuni esercizi di coding.
Ora è il momento di creare un oggetto per i Transaction Outputs (TXO).
Utilizzando un Bitcoin Block Explorer è possibile cercare i TXO sulla rete reale. Se volessimo cercare gli UTXO per un particolare indirizzo, per esempio:
https://blockchain.info/unspent?active=1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
Il valore dopo active= è un indirizzo. Questo particolare indirizzo è quello che Satoshi ha usato quando ha estratto il primo blocco di Bitcoin.
Completiamo il metodo constructor e i metodi di spesa (spend
) per la classe TXO nel file TXO.js.
Il costruttore deve memorizzare i valori che gli vengono passati nelle proprietà con lo stesso nome. Dovrebbe anche creare una proprietà spent e impostarla come predefinita su false
.
La funzione spend deve impostare la proprietà spent su true. Ad esempio:
class TXO {
constructor(owner, amount) {
this.owner = owner;
this.amount = amount;
this.spent = false;
}
spend() {
this.spent = true;
}
}
const txo = new TXO("1FNv3tXLkejPBYxDHDaZz6ENNz3zn3G4GM", 10);
console.log("Owner address: ", txo.owner); // uguale a 1FNv3tXLkejPBYxDHDaZz6ENNz3zn3G4GM
console.log("Amount: ", txo.amount); //uguale a 10
console.log("IsSpent", txo.spent); // uguale a false
txo.spend(); // funzione di spesa della somma
console.log("IsSpent", txo.spent); //uguale a true
module.exports = TXO;
Si noti come spent sia inizialmente falso quando creiamo il nuovo TXO. Dopo aver invocato la funzione spend, spent viene impostato a true.
Le transazioni
Le transazioni sulla rete Bitcoin possono avere molti ingressi e molte uscite.
Si può dare un'occhiata a questa transazione Bitcoin per vedere un esempio di transazione con molte uscite.
Questo, combinato con un sistema di scripting su ogni transazione, permette agli utenti Bitcoin di impegnarsi in accordi finanziari più complessi rispetto al semplice invio di denaro da parte di un individuo all'altro.
Per una transazione media, lo script richiede semplicemente che i nuovi UTXO possano essere spesi solo dall'indirizzo associato.
Obiettivo? Garantire che gli input siano UTXO.
Ora, introduciamo un nuovo file Transaction.js.
Nel costruttore della transazione vengono passati due argomenti: inputUTXO
e outputUTXO
. Entrambi gli oggetti sono array contenenti istanze di output della transazione.
Memorizziamo inputUTXO
e outputUTXO
nell'oggetto transazione.
Nella funzione execute
faremo solo una cosa per ora: assicurarci che nessuno degli inputUTXO
sia già stato speso. Non possiamo permettere che i TXO vengano spesi due volte!
Lanceremo un errore in execute
se un TXO di input è già stato speso.
La terminologia tra UTXO e TXO può talvolta creare confusione.
Ricordate che un TXO è solo la nomenclatura di un UTXO già speso!
class Transaction {
constructor(inputUTXOs, outputUTXOs) {
this.inputUTXOs = inputUTXOs;
this.outputUTXOs = outputUTXOs;
}
execute() {
for (let utxo of this.inputUTXOs) {
if (utxo.spent) {
throw "UTXO already spent";
}
}
}
}
module.exports = Transaction;
Ingressi e uscite
Con una moltitudine di UTXO di ingresso e di uscita consentiti in ogni transazione, esistono molte possibilità di scambio.
Il software del portafoglio Bitcoin a volte sceglie di includere molti UTXO di ingresso solo per aggregarli in un UTXO più grande da inviare al proprietario.
Ad esempio, se si dispone di cinque UTXO, ciascuno con un importo di 0,1 BTC, il portafoglio potrebbe scegliere di combinarli in 0,5 BTC nella transazione successiva. La magia dietro le quinte :)
La parte importante è assicurarsi che il valore totale degli UTXO in ingresso sia sufficiente a coprire l'importo totale degli UTXO in uscita.
Il nostro obiettivo: garantire un ingresso sufficiente.
Assicuriamoci che gli UTXO di input abbiano un valore totale sufficiente a coprire il valore totale degli UTXO di output.
Se il valore totale degli ingressi è inferiore al valore totale delle uscite, lanceremo un errore nella funzione execute
.
class Transaction {
constructor(inputUTXOs, outputUTXOs) {
this.inputUTXOs = inputUTXOs;
this.outputUTXOs = outputUTXOs;
}
execute() {
let totalInput = 0;
let totalOutput = 0;
for (let utxo of this.inputUTXOs) {
if (utxo.spent) {
throw "UTXO già spesi";
}
totalInput += utxo.amount;
}
for (let utxo of this.outputUTXOs) {
totalOutput += utxo.amount;
}
if (totalInput < totalOutput) {
throw new Error("Non hai abbastanza fondi da spendere");
}
}
}
module.exports = Transaction;
Riuscita delle transazioni
Quando una transazione ha successo e viene inserita nella blockchain, gli UTXO in uscita diventano nuovi TXO pronti per essere spesi. Gli UTXO in entrata devono essere contrassegnati come spesi, per garantire che non vengano spesi di nuovo :)
Dopotutto, lo scopo della blockchain è proprio quello di risolvere il problema della doppia spesa.
Qui il nostro obiettivo è contrassegnare gli input come spesi.
Se non vengono innescati errori durante la funzione di esecuzione della transazione, questa ha avuto successo.
class Transaction {
constructor(inputUTXOs, outputUTXOs) {
this.inputUTXOs = inputUTXOs;
this.outputUTXOs = outputUTXOs;
}
execute() {
let totalInput = 0;
let totalOutput = 0;
for (let utxo of this.inputUTXOs) {
if (utxo.spent) {
throw "UTXO già spesi";
}
totalInput += utxo.amount;
}
for (let utxo of this.outputUTXOs) {
totalOutput += utxo.amount;
}
if (totalInput < totalOutput) {
throw new Error("Non hai abbastanza fondi da spendere");
}
for (let utxo of this.inputUTXOs) {
utxo.spent = true;
}
}
}
module.exports = Transaction;
COMMISSIONI DI MINING
A questo punto ci chiediamo perché nel terzo step abbiamo richiesto solo che la quantità totale di input sia superiore alla quantità totale di output.
Non dovremmo mostrare un errore anche quando la quantità in uscita risulta inferiore?
No! In realtà, il resto viene dato al miner.
Questa è una scelta progettuale del sistema Bitcoin. Si tratta della cosiddetta tassa di transazione, o transaction fee.
La commissione di transazione può contribuire ad accelerare la richiesta. Un miner è molto più propenso a includere la vostra transazione nel blocco successivo se includete un bel premio da riscuotere!
Bitcoin ha un'offerta controllata. Per un periodo di tempo limitato, ogni blocco prevede una ricompensa per il miner. A un certo punto, questo si interromperà e l'unica ricompensa per il miner diventerà la commissione di transazione.
Il nostro obiettivo: calcolare la commissione!
Alla fine della funzione execute, calcoliamo la tariffa come somma di tutti gli input meno la somma di tutti gli output.
Dato che lanciamo un errore se gli input sono insufficienti, questo numero dovrebbe essere almeno pari a zero. Ogni volta che le uscite sono inferiori, il compenso deve essere positivo.
Memorizziamo perciò l'importo della tassa in una proprietà chiamata fee
sulla transazione stessa.
const minerFee = totalInput - totalOutput;
if (minerFee < 0) {
throw "Miner fee inferiore a zero. Il miner non pagherà per te lol";
}
this.fee = minerFee;
Nel prossimo post vedremo nel dettaglio le strutture dati ad albero e i principali metodi per interagire con queste strutture.
Posted on January 9, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.