Tutto ciò che devi sapere sui canali IBC (Parte 1)

blog originale: https://medium.com/the-interchain-foundation/everything-you-need-to-know-about-ibc-channels-part-1-6f37a3060a

I canali sono una parte integrante del protocollo di comunicazione tra blockchain (IBC) core. Servono come un condotto per il trasferimento di pacchetti tra un modulo/applicazione su una catena e un modulo su un'altra catena. Per gli sviluppatori di applicazioni, i canali rappresentano il livello più importante dell'astrazione IBC da conoscere.

Questo post del blog mira a spiegare i canali e a rispondere alle domande/comprese comuni che gli sviluppatori hanno sui canali IBC. Gli argomenti principali trattati includono l'ordinamento dei canali, gli handshakes, la chiusura, la riapertura, le proprietà di sicurezza dei canali e i timeout dei pacchetti.

C'è molto da coprire, quindi questa è la prima di una serie di due parti. La Parte 1 si concentra sui fondamenti dei canali IBC, mentre la Parte 2 affronta alcune delle domande frequenti dei developer sul canale.

Tipi di canali Un canale è un condotto per il trasferimento di pacchetti tra due diversi moduli/applicazioni su due diverse catene. Garantiscono che i pacchetti siano eseguiti solo una volta e consegnati solo al modulo corrispondente che possiede l'altro capo del canale.

Attualmente, ci sono due tipi principali di canali:

  1. Ordinati: in cui i pacchetti vengono consegnati (alla destinazione) esattamente nell'ordine in cui sono stati inviati (dalla sorgente).

  2. Non ordinati: in cui i pacchetti possono essere consegnati in qualsiasi ordine, indipendentemente dall'ordine in cui sono stati inviati.

L'ordinamento dei canali è garantito dall'uso di sequenze di pacchetti.

Ogni pacchetto ha una sequenza di pacchetti che corrisponde all'ordine di invio e ricezione dei pacchetti. Un pacchetto con un numero di sequenza 1 sarà ricevuto e elaborato alla destinazione prima di un pacchetto con un numero di sequenza 2 o superiore.

Un'estremità del canale mantiene tre diverse sequenze: nextSequenceSend, nextSequenceRecv e nextSequenceAck. Ogni volta che un canale invia, riceve o riconosce un pacchetto, i contatori nextSequenceSend, nextSequenceRecv e nextSequenceAck vengono incrementati, rispettivamente.

Nei canali ordinati, un pacchetto viene ricevuto e processato solo se la sequenza del pacchetto corrisponde al nextSequenceRecv che l'estremità del canale si aspetta. Se la sequenza del pacchetto è inferiore al nextSequenceRecv, il che significa che il pacchetto è già stato ricevuto, allora il core IBC effettua un'operazione nullo, come mostrato qui.

Se la sequenza del pacchetto è maggiore del nextSequenceRecv, il che significa che esiste un pacchetto con una sequenza precedente che deve essere ricevuto prima di quello attuale, il pacchetto viene respinto e un relayer invierà eventualmente il pacchetto corretto.

Quando un pacchetto viene ricevuto su un canale non ordinato, il core IBC si assicura semplicemente che il pacchetto non sia già stato ricevuto (poiché l'ordinamento non ha importanza) cercando una ricevuta del pacchetto, cioè un singolo bit che indica che un pacchetto è stato ricevuto. Se la ricevuta esiste già, il canale effettua un'operazione nullo. Se non esiste, viene impostata la ricevuta e il pacchetto viene elaborato.

Apertura/chiusura del canale tramite handshake I canali vengono stabiliti attraverso un handshake a 4 vie. In ogni fase del processo di handshake, il core IBC esegue una verifica e una logica di base, effettuando chiamate di callback all'applicazione dove è possibile eseguire una logica di handshake personalizzata.

La negoziazione della versione tra i due moduli avviene durante il handshake. Ciò consente alle applicazioni di concordare la struttura dei dati del pacchetto che verranno inviati su quel canale, la logica relativa all'uso dei middleware, ecc.

L'handshake del canale nel caso di ICS-20¹ è il seguente:

  1. Un relayer chiama la funzione chanOpenInit su chain A. Successivamente, la callback onChanOpenInit consente all'applicazione su chain A di eseguire una logica personalizzata. Questo imposta lo stato dell'estremità del canale (su A) su INIT. Un relayer passa questa versione su chain B al passaggio 2 come stringa counterpartyVersion.

  2. Viene chiamato chanOpenTry su chain B, dove l'applicazione esegue una logica di handshake personalizzata su onChanOpenTry, impostando lo stato dell'estremità del canale su TRY. L'applicazione su B verifica che la sua versione sia la stessa di quella proposta dall'applicazione della controparte, come mostrato qui.

  3. Viene chiamato chanOpenAck su A. La negoziazione della versione dell'applicazione viene finalizzata durante questo passaggio e lo stato dell'estremità del canale su A viene impostato su OPEN.

  4. Viene chiamato chanOpenConfirm su B, dove l'estremità del canale viene anch'essa impostata su OPEN.

È possibile che le callback restituiscano un errore in qualsiasi fase del processo di handshake. In questo caso, l'handshake del canale fallisce e ne deve essere negoziato uno nuovo.

Una volta che è stato stabilito un canale tra due moduli, non è possibile effettuare l'aggiornamento di tali moduli o utilizzare middleware per avvolgere i moduli senza aprire un nuovo canale o coordinare un aggiornamento a livello di rete. Il nostro lavoro attuale sulla capacità di aggiornamento del canale affronta questo problema in modo che i moduli possano essere aggiornati per sfruttare nuove funzionalità o possa essere aggiunto un middleware su entrambi i lati mantenendo gli stessi canali.

Un canale si chiude solo in circostanze eccezionali. Parleremo di tutte le ragioni per cui un canale potrebbe chiudersi nella parte 2 di questa serie. La chiusura di un canale avviene attraverso un handshake di chiusura del canale a 2 fasi. Un relayer invia ChannelCloseInit, il che consente all'applicazione su quella catena di eseguire una logica personalizzata su onChanCloseInit. Viene quindi inviato ChannelCloseConfirm sulla controparte, dove viene chiamata la callback onChanCloseConfirm.

Timeout dei pacchetti Una delle principali garanzie fornite dal core IBC è che un pacchetto non sarà ricevuto sulla catena di destinazione se è trascorso il timeout. I timeout sono specificati nell'interfaccia del pacchetto attraverso un timeoutHeight e un timeoutTimestamp della catena della controparte (ricevente).

Specificare il timeout in base all'orologio locale del destinatario garantisce che non ci sarà mai una situazione in cui il mittente ritiene che un pacchetto abbia fatto timeout e compie un'azione, come il rilascio di token in un trasferimento di token, mentre il pacchetto è stato effettivamente ricevuto in tempo dalla controparte, che poi procede a coniare token, causando una doppia spesa.

Ci sono due scenari in cui un pacchetto può scadere: 1) il caso standard in cui il timestamp o l'altezza di timeout è trascorso sulla catena ricevente, o 2) un canale si chiude e quindi tutti i pacchetti che non sono stati ricevuti verranno marcati come scaduti.

Esamineremo entrambi questi casi separatamente. Prima, vediamo il caso più comune.

Timeout standard del pacchetto Quando un pacchetto viene inviato alla catena di destinazione in cui è trascorso il timestamp/altezza di timeout, la funzione RecvPacket restituisce un errore. Un relayer ricerca tali errori (ErrPacketTimeout) e invia un TimeoutPacket alla catena di origine. Il TimeoutPacket è composto da una prova e da una ProofHeight. La prova viene utilizzata per dimostrare alla catena di origine che a questa particolare altezza il pacchetto non è stato ricevuto. Se sei familiare con il funzionamento dei light client, saprai che ogni ProofHeight è associato a uno stato di consenso particolare. E ogni stato di consenso ha un timestamp particolare. Pertanto, prendiamo il timestamp associato a una ProofHeight particolare utilizzando la funzione GetTimestampAtHeight. Pertanto, la ProofHeight dimostra che il timestamp della controparte (associato alla ProofHeight) è superiore al timestamp di timeout specificato nel pacchetto. Se invece è specificata un'altezza di timeout, allora dimostriamo che la ProofHeight è maggiore o uguale all'altezza di timeout. Una volta dimostrato il passo 5. o 6., procediamo a dimostrare che a questa altezza il pacchetto non è stato ricevuto. Questo passo differisce in base all'ordinamento del canale: 7.a. Nel caso semplice dei canali non ordinati, dimostriamo che a questa ProofHeight non esiste una ricevuta del pacchetto. In altre parole, questa è una prova che una certa chiave non esiste nell'albero di stato.

7.b. Per i canali ordinati, verifichiamo che il nextSequenceReceive sia minore o uguale alla sequenza del pacchetto. Ciò garantisce che la catena di destinazione non abbia ricevuto detto pacchetto. Ad esempio, se il nextSequenceReceive è 3 e la sequenza del pacchetto è 4, allora il canale si aspetta di ricevere il pacchetto con sequenza 3 prima di ricevere il 4. A differenza di 7.a., questa è una prova di appartenenza al nextSequenceReceive.

Una volta dimostrati i passaggi 5. (o 6.) e 7., il modulo mittente chiama OnTimeoutPacket per annullare eventuali modifiche di stato o eseguire una logica personalizzata. Nel caso dei trasferimenti di token ICS-20, i token messi in escrow vengono sbloccati e rimborsati all'utente.

Timeouts quando un canale si chiude La funzione TimeoutOnClose è chiamata dal modulo mittente per dimostrare che il canale su cui è stato inviato un pacchetto è stato chiuso.

Eseguire la logica di timeout quando un canale si chiude è semplice. A differenza dei timeout menzionati in precedenza, in cui il core IBC dimostra che il tempo sulla controparte ha superato il timeout del pacchetto, in questo caso dobbiamo solo dimostrare che lo stato del canale della controparte è chiuso.

Fungibilità dei token Nell'IBC, lo stesso token inviato su due diversi canali non è fungibile. Questo non è un difetto ma un aspetto fondamentale del modello di sicurezza dell'IBC.

Anche nello scenario in cui esistano due canali diversi 1 e 2, tra le stesse due catene A e B, lo stesso token $FOO inviato su questi canali non sarà fungibile. Questo perché ogni canale ha le proprie proprietà di sicurezza. Ad esempio, due canali diversi possono avere versioni diverse. Oppure i canali potrebbero non essere associati alla stessa connessione, ma a due connessioni diverse (che potrebbero essere collegate a diversi light client). Ma è importante sottolineare che i moduli non hanno modo di sapere a quale catena appartiene l'estremità del canale della controparte. In altre parole, i moduli hanno conoscenza solo del canale su cui stanno inviando dati e non della catena con cui stanno comunicando.

Di conseguenza, è imperativo che ciascun canale sia isolato dal punto di vista dei confini di sicurezza e che non siano fatte assunzioni naive riguardo ai canali e ai loro light client associati.

Conclusione Come illustrato sopra, i canali svolgono un ruolo importante nel trasporto e nell'ordinamento dei pacchetti all'interno dell'IBC. Dato che i moduli e i canali sono strettamente accoppiati, è utile che gli sviluppatori di applicazioni siano familiarità con i dettagli dei canali IBC.

Nella parte 2 di questa serie, chiariremo alcune delle domande frequenti degli sviluppatori di applicazioni riguardo ai canali. Nel frattempo, sentiti libero di consultare il nostro portale per sviluppatori per saperne di più sui canali IBC.

Last updated