Bitcoin’i Kodlamak — 2 (Golang ile)
“Bitcoin’i Kodlamak” yazı dizisinin ikinci kısmında bitcoin daemon uygulaması olan btcd ve bitcoin cüzdan uygulaması olan btcwallet uygulamaları üzerinde mevcut bitcoin de olmayan yeni bir özellik kodlayacağım. İlk yazıda da özellikle değindiğim transactionlar ile ilgili olarak spesifik bir transation’ı başka birine aktaracağımız ‘TransferTransaction’ rpc komutunu kodlayacağım. Yazının diğer kısımları için;
Normal bir bitcoin transaction da hangi UTXO’un gönderileceği belli başlı kurallar dahilinde bitcoin cüzdan uygulaması btcwallet tarafından belirleniyor. Uygulayacağım senaryoda ise UTXO’lar içerisinden hash’ini bildiğimiz bir transaction’ı başka birine göndereceğiz.
Bu sayede inceleyeceğimiz konular; cüzdan ile ilgili rpc isteklerinin btcwallet’a yönlendirilmesi, cüzdan database’i, utxo’ların bulunması, transfer için gerekli kuralların kontrolü, input ve output’lar ile bir transaction mesajının oluşturulması, transfer ücretinin belirlenmesi ve eklenmesi, para üstünün oluşturulması ve son olarak da transaction’ın imzalanması ve gönderilmesi.
Yukarıdaki işlemleri kodlarken btcd ve btcwallet içerisindeki kodları referans alıp nerelerde örnekleri olduğundan bahsedeceğim. Bu sayede kod okurluğumuz artıp, geliştirmek istediğimiz ek senaryolarda nelere dikkat etmemiz gerektiğine, nelere uyulması ve hangi kütüphanelerden neyi kullanmamız gerektiğine dair fikir sahibi olabileceğiz.
Her iki uygulamada da yapacağımız değişiklikleri aşağıdaki Github sayfalarında bulabilirsiniz;
https://github.com/farukterzioglu/btcd-tutorial
https://github.com/farukterzioglu/btcwallet-tutorial
btcd
İlk olarak kodlayacağımız kısım btcctl ile gönderdiğimiz rpc isteklerini karşılayan btcd uygulaması. Burada çok fazla birşey yapmayacağız. Sadece yeni oluşturduğumuz ‘TransferTransaction’ cüzdan komutunu btcwallet uygulamasına yönlendireceğiz. Uygulamak isteyebileceğimiz başka senaryolara göre btcd uygulamasına daha çok dokunmak gerekebilir, buranın içinde bitcoin kuralların işletilmesi gibi bir çok şey var. Aynı zamanda btcd içerisindeki bir çok kütüphane de btcwallet tarafından kullanılmakta.
Yapacağımız değişiklikleri kendi Github reponuzda tutmak isterseniz aşağıdaki adımları izleyebilirsiniz, Benim tercih ettiğim yöntem, boş bir repository açıp bunu origin remote olarak ayarlayıp değişiklikleri göndermek. Bu sayede btcd setup’ı sırasında indirdiğimiz kodlarda değişiklik yapıp kendi repomuza eşleyebiliriz.
$ cd $GOPATH/src/github.com/btcsuite/btcd
$ git remote rename origin upstream
$ git remote add origin [repository_adresi]
$ git push -u origin master
Transfer Transaction
Spesifik bir transaction’ı transfer edebilmemiz için terminalden iki parametre alacağız: biri göndermek istediğimiz transaction’ın hash’i, diğeri de göndereceğimiz adres. Bu parametreleri temsilen ‘TransferTransactionCmd’ isminde bit struct ve bir de constructor function tanımlayacağız. Bu komut cüzdan ile alakalı olduğu için ‘btcjson/walletsvrcmds.go’ altında tanımlıyoruz. ‘walletsvrcmds.go’ içerisinde cüzdan uygulaması tarafından desteklenen rpc komutları bulunmakta. Devamında yeni tanımladığımız komutu ‘MustRegisterCmd’ fonksiyonunu ile çağırarak ‘transfertransaction’ method adıyla kayıt ediyoruz.
‘transfertransaction’ komutu bir cüzdan komutu olduğu için btcd tarafından desteklenmediğini ve btcwallet uygulamasına yönlendirilmesi gerektiğini belirtmek için ‘rpcserver.go’ dosyası içine aşağıdaki kodu ekleyelim.
....
var rpcAskWallet = map[string]struct{}{
....
"transfertransaction": {},
}
Oluşturduğumuz ‘TransferTransactionCmd’ rpc mesajını cüzdan server’ına gönderip dönüş değerini alması için bir takım metotlar tanımlayacağız. Cüzdan ile ilgili metotları ‘rpcclient/wallet.go’ dosyası altında oluşturuyoruz. Aşağıdakileri wallet.go içerisine ekleyelim.
Yukarıdaki kodları özetlersek, geliştirdiğimiz transaction transfer özelliği için aldığımız ‘alıcı adresi’ ve ‘transaction hash’ parametrelerini cüzdan server’ına gönderip geriye yeni oluşmuş transaction’ın hash ini (veya hatayı) dönüyor.
Detaylı olarak incelersek, bitcoin rpc istemcisi olan ‘Clint’ üzerinden ‘TransferTransactionCmd’ komutunu cüzdan server’ına göndereceğiz. Komut parametrelerini ve dönüş değerini JSON tiplerinden parse etmesi için asenkron ve senkron ‘TransferTransactionAsync’ ve ‘TransferTransaction’ metodlarını tanımlıyoruz (str. 38). ‘TransferTransactionAsync’ metodu iki parametre alıyor; transfer yapacağımız adres ve göndermek istediğimiz transaction’ın hash’i. Bu parametrelerden RPC mesajımızı (TransferTransactionCmd) oluşturup Client’ın sendCmd metodu ile cüzdan server’ına gönderiyoruz (str. 31). Receive() içerisinde çağırdığımız ‘receiveFuture’ metotu, channel üzerinden bir veri (response tipinde) gelene kadar bloklar. (ref: /rpcclient/infrastructure.go)
Hata harici bir değer geldiğinde, gelen JSON nesnesini parse edip beklediğimiz string değer olan transaction hash’ini alıyoruz (str. 15)
Buraya kadar olan geliştirmeler ile gönderdiğimiz rpc komutu btcd üzerinden btcwallet uygulamasına yönlendirilmiş ve geri dönüş değeri alınmış oldu. Yukarıdaki kodları toplu olarak şu commit altında görebilirsiniz;
https://github.com/farukterzioglu/btcd-tutorial/commit/f3ed12356fd02e9dfe0785610807618461fbf5be
btcwallet
Cüzdan server’ı olan btcwallet uygulamasının btcd içerisindeki birçok kütüphaneyi kullandığından bahsetmiştim. Aynı şekilde yukarıda tanımladığımız ‘TransferTransactionCmd’ struct’ına da btcwallet içerisinden erişmemiz gerekiyor.
btcd ve btcwallet uygulamaları Go v1.11 versiyonu ile gelen Go Module ile çalışmakta. btcwallet bağımlı olduğu btcd yi ‘go.mod’ içerisinde tanımlamakta. btcd kodlarının ‘$GOPATH/pkg/mod’ içerisindeki cache den değil de bizim kodunda değişiklik yaptığımız repository’den okunduğundan emin olmak için go.mod içerisine aşağıdaki satırı ekleyelim. (Hem btcd hem de btcwallet repolarının $GOPATH/src/github.com/btcsuite dizininde olduğunu varsayıyorum)
replace github.com/btcsuite/btcd => ../btcd
Yazı içerisinde paylaştığım kodlar btcwallet içerisinde yazacağımız kodların son hallerini değildir, daha net anlaşılması için kodları adım adım yazacağım. Bir dosya içerisindeki kod yazı içerisinde bir kaç defa güncellenebilir.
btcd tarafından gönderilen rpc isteğini btcwallet tarafında karşılamak için ‘rpc/legacyrpc/methods.go’ içerisine aşağıdaki kodları yazalım.
‘var rpcHandlers = map[string]struct’ değişkeninde hangi rpc komutlarının hangi handler ile yönetileceği belirtiliyor. ‘transferTransaction’ fonksiyonu içerisinde ise rpc üzerinden gelen komutu daha önce tanımladığımız (btcd içerisinde) ‘TransferTransactionCmd’ tipine cast ediyoruz.
Şimdilik sadece konsola çıktı veriyoruz fakat asıl kodlayacağımız kısımların girişi buradan oluyor. Son olarak rpc server için help dosyasına transfer transaction komutu için ekleme yapalım. ‘rpc/legacyrpc/rpcserverhelp.go’ dosyası içerisinie aşağıdaki satırı ekleyelim;
func helpDescsEnUS() map[string]string {
return map[string]string{
....
"transfertransaction": "[transfertransaction açıklaması]",
}
}
(Commit : https://github.com/farukterzioglu/btcwallet-tutorial/commit/874e6a28ed164656f5c5bf7fd42c9a15bc54fca5)
btcwallet içerisinde cüzdan işlemleri ile ilgili komutların işletildiğinden bahsetmiştim. Bunları da ‘wallet/wallet.go’ dosyası içine, ‘Wallet’ struct üzerine tanımlayacağımız metotlarda yapıyoruz. Transaction transfer işlemlerini yapmak üzere aşağıdaki kodu bu dosya içerisine ekleyelim.
TransferTx metodunun aldığı parametreleri incelersek; transfer etmek istediğimiz kişinin adresi, transfer edeceğimiz transaction’ın hash’i, hangi account üzerinden transfer edeceğimiz, transfer edeceğimiz transaction için gerekli minimum doğrulama (minconf) ve kilobyte başına gerekli olan işlem ücreti (feeSatPerKb).
Tanımladığımız TransferTx metodunu çağırmak için daha önce ‘rpc/legacyrpc/methods.go’ dosyasına yazdığımız kodları aşağıdaki gibi düzenleyelim.
‘transferToAddress’ metodunun account parametresine varsayılan account’u temsilen ‘DefaultAccountNum’ değeri 0'ı (ref: /waddrmgr/manager.go), minimum confirmation değeri olarak da 1 gönderiyoruz çünkü bir onay almış transaction’ı bile göndermek istiyoruz, işlem ücreti olarak da yine sistem standartı olan ‘DefaultRelayFeePerKb’ değerini gönderiyoruz.
(ref: /wallet/txrules/rules.go)
(https://github.com/farukterzioglu/btcwallet-tutorial/commit/3074f88a683e4e2ed2abc9f8ea28217d711e5032)
Buraya kadar olan kısımda gerekli parametreleri wallet.go içerisindeki TransferTx metoduna kadar taşımış olduk. Transfer transaction isteklerini karşılamak için yeni bir go routine tanımlayacağız ve gelen istekleri bir channel üzerinden alarak işleyeceğiz. Aşağıdakileri /wallet/wallet.go içerisine ekleyelim. Kodların hemen hemen hepsi kendini açıklıyor.
Kullanacağımız go routine ‘txTransferCreator’ da oldukça açık ve şu şekilde;
Bu go routine, channel üzerinden okuduğu komutları yeni yazacağımız ‘txTransferToOutputs’ metoduna aktarıyor ve dönüş değerini request içerisindeki response channel’ına aktarıyor. Transaction transfer isteklerini yeni oluşturduğumuz channel’a göndermesi için daha önce oluşturduğumuz ‘TransferTx’ metodunu aşağıdaki gibi güncelleyelim. (dönüş değerini daha sonra kullanacağız, şimdilik nil,nil dönüyoruz)
‘createTxTransferRequests’ channel’ı üzerinden gelen komutları alması için de aşağıdaki txTransferToOutputs metodunu tanımlayalım. Transaction transferi ile ilgili kısımların çoğunu ‘txTransferToOutputs’ içerisinde tanımlayacağız.
(https://github.com/farukterzioglu/btcwallet-tutorial/commit/918f9ef9de4920c750eaeb1630dad8a631953b14)
Bundan sonraki kısımda bir transaction oluşturmak için gerekli adımları kodlayacağım. Devamı yazının 3. kısmında…