Ma is tanultam valamit: Bitcoin CPFP!

Előszó: Tudom-tudom… nem pont egy ilyen teki cikkel kéne megünnepelni, hogy újabb ATH-n vagyunk… De most éppen ez volt a csőben. Majd jönni fog hamarosan általánosabb és to-the-moonosabb cikk is, de továbbra is törekednék arra a blog kapcsán, hogy az agy dopamin termelő receptorai mellett azért a szürkeállományt is tudjam stimulálni.

Bár az elmúlt években sokat javult a viszonyom az Ethereummal, de azt továbbra sem állítanám, hogy szoros barátságban lennénk. Itt a blogon is sokszor értekeztem már az olyan nonszensz dolgokról, mint a trackelhetetlen belső tranzakciók, vagy az előre azonosíthatatlan contract címre történő transfer, stb. Ezekről a témákról még 2019 februárban írtam alaposabban itt: -link-

Egy olyan dolog volt eddig az Ethereum kapcsán, amire viszont – hithű Bitcoinosként – kifejezetten féltékenyen tekintettem: Ez pedig a tranzakció kicserélés lehetőség. Ennek nagyon leegyszerűsítve annyi a lényege, hogy amíg egy tranzakció nem kerül blokkba, addig az bármikor kicserélhető egy másik tranzakcióra. Főleg akkor van létjogosultsága, ha például sikerül egy tranzakciót nagyon alcsony fee-vel elindítani, ami hírtelen sürgőssé válik. Ilyenkor ethereum tx esetén egyszerűen csak fogom magam, összerakom az új tranzakciót, majd ugyanazzal a nonce-szal (sorszámmal) külön el, mint ami a kicserélendő tranzakció sorszáma és voilá(!)

Természetesen Bitcoin esetén is van erre lehetőség, korábban már elmélkedtem itt a blogon a Replace-by-Fee (RbF) megoldásról, ami egyébként egy horror, nem is beszélve arról, hogy a különböző kliensek különböző módon kezelik és kifejezetten körülményes azzal boostolni a tranzakciót. Pláne, hogy eleve csak a meglévő change output terhére növelhető vagy módosítható a tranzakció. Plusz hozzájön még az, hogy alap esetben majd az összes wallet tiltja az RbF-et (nSequence=max(UINT)).

Szóval nem az igazi…

Pont a napokban keveredtem egy olyan helyzetbe, amikor nagyon megszorultunk egy tranzakcióval, ami egy téves költségbecslés miatt igen alacsony fee-vel futott ki, úgyhogy kicsit beleástam magam a Bitcoin ezirányú rejtelmeibe… Mikor is több helyen is elém került a Child-Pays-for-Parent megoldás.

Ez gyakorlatilag az ethereum kapcsán fentebb bemutatott tranzakció kicserélős módszer Bitcoinos implementációja. Ráadásul simán használható bármilyen tranzakció esetén (kivéve az n. input 1 output tranzakciókat, ahol az output nem a mi címünk -> De ez igencsak ritka holló…).

Child-Pays-for-Parent (CPFP)

A megoldás lényege végtelenül egyszerű. Adott egy beszorult tranzakció, ami feltehetően napokig vagy hetekig nem fog blokkba kerülni, sőt az is lehet, hogy teljesen eltűnik a fullnodeokból. Ez a tranzakció minimum 1 darab inputot (utxo) tartalmaz, amit el akarunk költeni és ideális esetben egy vagy több elköltendő outputot és egy további un. change outputot, ami valójában a mi saját címünk.

Függetlenül attól, hogy a konkrét beragadt tranzakció még nincs blockba, valójában annak az outputjai már újra elkölthetők. Igaz ezen tranzakciók majd csak akkor kerülhetnek blockba, ha az eredeti tranzakció is blockba kerül! És itt jön a csavar az egészben! Ha hajlandók vagyunk elég sokat fizetni egy soron következő (függőségben levő) tranzakcióért, ami bőven fedezi az előző (beragadt) tranzakció alacsony feejét is… Akkor bizony a bányászok azt a döntést fogják meghozni, hogy a következő blockba bekerül soron kívül az eredetileg beragadt tranzakció és az arra épülő új tranzació is… és máris végrehajtódott a tranzakció!

Ez persze így leírva valószínűleg eléggé ködös lehet elsőre, ezért nézzük egy egyszerű példát:

  • Van 0.2 BTC-nk “A” címen. Amiből 0.1-et el akarunk küldeni “B” címre.
  • A tranzakció mérete 200 byte lesz, és 50 sat/byte fee-t állítunk be. Így a tranzakció a következőképpen fog kinézni:
    • 0.2 BTC input (“A” címről)
    • 0.1 BTC output (“B” cím)
    • 0.0999 BTC change output (“A” címre)
  • A fennmadaró 0.0001 BTC pedig a fee lesz, ami a minernél marad.

Azonban elszámolhattunk valamint, mert bizony 100 sat/byte alatt órákon keresztül nem kerülnek blockba a tranzakciók!

  • Ezért létrehozzunk egy újabb tranzakciót, ami a követekezőképpen fog kinézni:
    • 0.0999 BTC input (előző tranzakció change UTXO-ja)
    • 0.0991 BTC output (“A” címre)

A különbözetből látható, hogy az újabb ~200 byte-os tranzakcióért már 400 sat/byte-ot fizetnénk, de azt csak akkor kaphatja meg a miner, ha ehhez az első tranzakciót is blockba rakja! A két tranzakció együtt ~400 byte átlag 225 byte/sat jár érte a bányászanak, ha blockba rakja mindkettőt, amit nyilván meg is fog tenni, hiszen ez áll gazdaságilag az érdekébe!

CPFP a gyakorlatban?

A fentebbi gondolatmenet logikus és jól is hangzik, de miként is fog ez a valóságban végrehajtódni? A helyzet az, hogy a walletek 90+%-a nem támogatja natív módon a CPFP-t, meg úgy kb semmit a Bitcoin protokoll fejlesztésekből, a legtöbb ilyen megoldás megáll az alapértelmezett SPV wallet funkcionalitásnál.

BitcoinQT console (syncing)

Univerzális megoldás hiányában marad a barkács megoldás. Ha van egy saját (Bitcoin Core) bitcoind fullnode-od, akkor meg is vagy, ha nincs, akkor pl az Electrum wallet beépített CPFP funkcióját tudod használni. Mivel, ez a legtöbb olvasónál – ide a rozsdás bökőt… – nem áll fenn, így mutatok egy egyszerű megoldást:

  • Mivel az alábbiakhoz nincs szükséged egy teljesen felsyncelt full node-ra, sőt igazából mégcsak syncelni sem kell, így elég letöltened a bitcoin.org-ról az aktuális bitcoind-t.
  • Ugyan nem kell elindítanod, de ha mégis el akarod indítani, akkor az indításnál mindenképpen kapcsold be, hogy pruneolja az adatbázist. Alap esetben ezt egyébként fel is ajánlja neked egyből az indításnál:
Set Prune on bitcoind

Ezzel eléred, hogy maximum 2 GByteot használhasson a diszkedből a full node. Egyébként egy mai normálisabb gépen 10 óra alatt teljesen fel is synceli magát, de mint fentebb írtam erre nincs szükség az alábbiakhoz.

Mielőtt folytatnánk két figyelmeztetés:

FONTOS: a CPFP-hez fel kell fedned a tárcádhoz tartozó nyers privát kulcsot, hiszen alá kell majd írnod a tranzakciót. Éppen ezért fontos, hogy csakis kontrollált körülmények között és kizárólag olyan eszközön csináld ezt, ami garantáltan nincs szétfertőzve mindenféle malwarekkel és más nem használja. Ha ebben nem vagy teljesen biztos, de mégis meg kell tenned, akkor mindenképpen úgy végezd el a taraakciót, hogy annak a végén NE az eredeti change input címre küld vissza a maradékot és lehetőleg örökre felejtsd el is ezt a címet.

FONTOS#2: Ha a korábban leírt CPFP folyamatot nem értetted kristálytisztán, akkor ezen a ponton inkább ne folytasd a cikket, mert a következőkben bemutatásra kerülő kalkulációkat PONTOSAN kell elvégezni, ellenkező esetben előfordulhat, hogy mondjuk 400 sat/byte helyett véletlenül 40000 sat/byte-tal fogod elküldeni a tranzakciót, ami pont 100x drágább lesz mint tervezted volna és a fejed fogod verni a falba…

Éééés akkor végre a lépések:

Első lépésként hozzunk létre az új tranzakciót bitcoin-cli parancsból vagy BitcoinQt konzolból (Window->Console parancs, vagy MacOS-en alma+T)

createrawtransaction "[{\"txid\":\"prevtxid\",\"vout\":X,\"sequence\":4294967295}]" "[{\"address\":amount}]"

Paraméterek:

  • prevtxid: Annak a tranzakciónak a txid hash-e ami beragadt és el kéne költeni újra, hogy bekerülhessen blokkba.
  • X: Az outputok közül annak az outputnak a sorszáma, amit el akarunk újra költeni (a mi change addressünk, aminek rendelkezünk a privát kulcsával). Ez egy szokásos 1 input -> 2 output tranzakció esetén a második output lesz, de mivel a sorszámozás nullától indul, így az X helyére 1-et kell írni.
  • address: Az a cím, ahova küldeni akarjuk a CPFP tranzakció után a megmaradt change összeget.
  • amount: az az összes ami megmarad a CPFP tranzakció végén. Ennek a kiszámítását az előző fejezetben már bemutattam.

A sequence értéket nem kell módosítani.

A művelet eredményeként egy nyers bitcoin tranzakció keletkezik, ami fixen 020000…-val kezdődik.

Ezt a tranzakciót alá kell írnunk:

Jó hír, hogy mindehhez nem kell beimportálni a walletet a bitcoind-be, ami egyébként egy horror lenne! Helyette:

signrawtransactionwithkey "020000000193[...]" “[\”LXXXXXXXXXX\”]”

Két paramétere lesz az műveletnek: Maga a nyers tranzakció és a privát kulcs. Utóbbit fixen ebben a formátumban kell megadni: “[\”LXXXXXXXXXX\”]” Ügyelve az idézőjelekre és a backslashekre is. Az LXXXX… helyére az eredeti tranzakcióban lévő change address nyers privát kulcsát kell írni. Az utasítás hatására egy újabb bitcoin tranzakció keletkezik, ami viszont már alá van írva.

Ha ezzel is megvagyunk, akkor esetleg nem árthat ellenőrizni a tranzakciót, ehhez a decoderawtransacion parancs lesz a barátunk, aminek simán csak meg kell adni a signrawtransaction eredményeként megkapott tranzakció szöveget. A decode erre json struktúrában megmutatja, hogy honnan hova mennyi bitcoin fog menni a tranzakció hatására. Ez az utolsó pont, ahol még megtehetjük, hogy újra számoljuk a tranzakciós költséget és esetleg korrigáljuk, ha valamit elszámoltunk volna.

Ha minden rendben van, akkor beküldhetjük a hálózatra:

sendrawtransaction 020000000.....

Egyetlen paramétere van a sendrawtransactton-nek, mégpedig a tranzakciós szöveg amit az aláírás után kaptunk. Fontos, hogy pontosan küldjük el, mert ezen a ponton már semmit sem ellenőriz a protokoll, amit bemásoltunk azt küldi tovább.

A sendrawtransaction eredményeként az új (CPFP) tranzakció hash-ét kapjuk meg, amivel már mehetünk is a kedvenc tranzakció tracking oldalunkra, ahol gyönyörködhetünk a művelet sikerességében!

Egészségünkre!

Ui: A CPFP POC-olás kapcsán külön köszönet iFá-nak a fusion solutionstől, akivel közösen “bindzsiztük” ki a fentebbi módszert.

Bookmark the permalink.

2 Comments

  1. en nem tartom jo otletnek ezt a modszert: amikor el van floodolva a mempool, es orakat/napokat kell varni a blockba jutasert, akkor global szinten nagyon nemjo, hogy ilyen plusz tranzakciokkal fogyasztjuk a rendelkezesre allo blocksize-ot

  2. Ezt jó tudni! Mondjuk drágább lesz, mint az RBF, mert így a második TX bájtjaiért is fizetnem kell.
    Egy apróság: a második TX mérete kisebb lesz, mert csak egy kimenete van.

Leave a Reply

Your email address will not be published. Required fields are marked *