Bitcoin’i Kodlamak — 3 (Golang ile)

Faruk Terzioğlu
Blokzincir
Published in
8 min readApr 7, 2019

“Bitcoin’i Kodlamak” yazı dizisinin üçüncü kısmında bitcoin cüzdan uygulaması olan btcwallet uygulamasında bir önceki yazıda yazdığım kodları tamamlayacağım.

Normal bir bitcoin transferi yapmak istediğimizde göndermek istenen miktar UTXO’lar arasından belli kurallara göre btcwallet uygulaması tarafından seçilir. Uyguladığımız senaryoda ise UTXO’lar içerisinde hash’ini bildiğimiz belli bir transaction’ı başka birin göndereceğiz.

Bir önceki yazıda, btcctl uygulamasından gönderdiğimiz rpc isteklerini btcd uygulamasına ulaştırmış ve bu bir cüzdan komutu olduğu için oradan da btcwallet uygulamasına yönlendirilmişti. btcwallet içerisinde de alınan komutu orijinal kodlar örnek alınarak gerekli go routine ve channel’lardan ilgili kütüphanelere kadar taşınmıştı.

Bu yazı içerisinde ise rpc isteğinden gelen hash ile transfer etmek istediğimiz transaction’ı UTXO kümesinden bulacak, input ve output’larını tanımlayarak bir yeni bir transaction oluşturup imzalayıp ağda yayınlayacağız.

UTXO Kümesi

Birinci yazıdan hatırlarsanız harcanmamış işlem çıktılarını (UTXO) bulmak için $ btcctl --wallet listunspentkomutunu çağırmıştık ve aşağıdaki görseldeki çıktıyı elde etmiştik.

UTXO listesi

UTXO listesine kod içerisinden ulaşmak ve aradığımız hash’e göre içinden birini seçmek için ‘txTransferToOutputs’ metodunu aşağıdaki gibi güncelleyelim.

Yukarıdaki kodları açıklarsak;

Cüzdan kilidi

Birinci yazıdan hatırlarsanız cüzdan üzerinde işlem yapabilmek için cüzdanı geçici olarak şu kodlarla açmamız gerekiyordu.

$ btcctl — wallet walletpassphrase “[WALLETPASS]” 600

UTXO listesine kod içerisinden erişebilmek için cüzdan yukarıdaki kod ile açılmış olması gerekiyor. Cüzdanın açık olduğundan emin olmak ve açık değilse hata dönmek için 5. satırdaki w.requireChainClient() komutunu çağırıyoruz. Dönüş değeri ‘chainClient’ üzerinden daha sonra gerekli parametrelere erişeceğiz.

Credit

UTXO listesinde elde edeceğimiz bir trancastion ‘wtxmgr.Credit’ tipinde olacaktır. ‘Credit’ tipini detaylı incelersek;

Credit, harcanmış veya harcanmamış transaction çıktılarını temsil eder. Credit tipinin ilk alanı Outpoint ile hangi transaction’ın (Hash) kaçıncı çıktısı (Index) olduğu belirtilir. Görselde de görüldüğü üzere bir transaction’ın farkı çıktıları farklı kişilere ait olabilir.

Wallet DB

UTXO’lara erişmek için cüzdan veri tabanını (w.db) readonly modda açmamız gerekiyor. Bunun için walletdb.View() metodunu çağırıyoruz. View() metodu ile cüzdan sadece readonly modda erişebiliyoruz. Yazının ilerisinde read/write modunda da erişeceğiz.

Buraya kadar elde ettiğimiz değişkenler; readonly cüzdan veritabanı (dbtx), göndermek istediğimiz transaction’ın hash’i (txHash), kullanacağımız account, gerekli olan minimum onay sayısı (minconf) ve son üretilen block bilgileri (bs). Hash’ini bildiğimiz transaction’ı bulmak üzere bu değerleri bir sonraki metodumuz ‘findTheTransaction’ a gönderiyoruz.

Gönderilecek transaction’ı bulmak için ‘wallet/wallet.go’ dosyası içine aşağıdaki kodu ekleyelim.

Bu uzunca metodu incelersek; cüzdan veritabanı ‘dbtx’ üzerinden adres manager’ı ve transaction manager’ını ‘waddrmgrNamespaceKey’ ve ‘wtxmgrNamespaceKey’ anahtarları ile çekiyoruz. (str. 4–5)

w.TxStore.UnspentOutputs ile tüm UTXO’ların listesini çekiyoruz. Bu liste üzerinde bazı kontroller ile bizim aradığımız tx’ı bulacağız.

  • İlk olarak liste içerisindeki tx ile bizim aradığımız tx’ın hash leri tutuyor mu diye kontrol ediyoruz. (str. 18)
  • Daha sonrasında ise bulduğumuz tx’ın aldığı onay sayısı(output.Height) gerekli onay sayısına (minconf) ulaşmış mı diye kontrol ediyoruz.
  • Eğer seçilen tx bir coinbase transaction ise onun kontrolünü ayrı olarak yapıyoruz. Çünkü coinbase tx’ların kullanılması için gerekli olan onay sayısı network içerisinde belirleniyor. Bu değere de ‘w.chainParams.CoinbaseMaturity’ ile erişiyoruz. (str. 25)
  • Diğer bir kontrol ise seçilen tx kullanıcı tarafından ‘lockunspent’ komutu çağrılarak kitlenip kilitlenmediği.
  • Yazının ilk kısmında bahsettiğim gibi, kullanılan network’ün main, test veya simülasyon olmasına göre adres prefixleri değişiyor. Hangi network de olduğumuza ‘w.chainParams’ ile erişiyoruz. Kontrol ettiğimiz UTXO’un hangi adrese gönderildiğini anlamak için ‘txscript.ExtractPkScriptAddrs’ metodunu w.chainParams ve UTXO’nun PKScript’ ile çağırıyoruz. (str. 37) Elde ettiğimiz adres ile metodun ilk başında tanımladığımız addrmgrNs’ı kullanarak adresin ait olduğu account’u buluyoruz. (str. 44) Eğer bu account bize ait bir account ise tüm kontroller sağlanmış olup bu UTXO transfer edilmek üzere seçilir.

Buraya kadar olan kısımda transfer edeceğimiz transaction’ı bulmuş olduk. Yazdığımız kodları şu commit altında bulabilirsiniz.

https://github.com/farukterzioglu/btcwallet-tutorial/commit/7f9dcec2da719fa3bdf7b7e4eb424f8b5c9fdb4b

Output

Bir bitcoin transaction’ında birden fazla input ve birden fazla output bulunur. Normal bir bitcoin gönderiminde, input’lar btcwallet cüzdan uygulaması tarafından belli kurallar ile bulunur. Bizim geliştirdiğimiz senaryoda spesifik bir transaction’ı transfer etmek istiyoruz ve yazının yukarıdaki kısmında bu transaction’ı elde ettik.

Yeni bir transaction oluşturmak için input’ları ve output’ları tanımlamamız gerekmekte. Normal bir bitcoin transaction’ında birinci output, gönderilecek bitcoin miktarı kadar alıcıya tanımlanır. Eğer input’ların toplamı output’tan, yani gönderilecek bitcoin miktarından fazla ise bir de göndericiye para üstü olarak ikinci bir output daha tanımlanır. Aşağıdaki örnekte Ahmet’ten Ayşe’ye gönderilen 10 BTC lik transfer sonucunda 5 BTC lik de bir para üstü output’u olmuştur.

‘Tx2’ transaction’ının ilk çıktısı Ayşe’ye gönderilmek istenen 10 BTC dir. Bizim de ilk çıktımız ‘findTheTransaction’ metodundan bulduğumuz transaction’ın miktarı kadar olacaktır. O halde yeni transaction’ın ilk çıktısını oluşturmak üzere aşağıdaki metodu ‘wallet/wallet.go’ dosyasına ekleyelim.

‘makeoutput’ metodu parametrelerden aldığı adres ve miktar bilgisi ile yeni bir output üretiyor. Output tipini incelersek;

// TxOut defines a bitcoin transaction output.
type TxOut struct {
Value int64
PkScript []byte
}

Ne kadarlık bir bitcoin göndereceğimiz output içerisindeki value değerine yazılıyor. Output’un kime gönderileceğini belirtmek için parametrelerden aldığımız adresi ‘bitcoin script’ e çevirmemiz gerekiyor. Bunun için ‘txscript’ paketinden ‘PayToAddrScript’ metodunu çağırıyoruz. Bitcoin Script başlı başına bir yazıda anlatılacak kadar uzun bir konu onun için sadece örnek bir script çıktısını şu şekilde gösterebilirim;

OP_DUP OP_HASH160 <Public KeyHash> OP_EQUAL OP_CHECKSIG

Script içerisindeki ‘<Public KeyHash>’ değerine parametrelerden aldığımız adres bilgisi gelmekte. Script oluşturma kodlarını incelemek için ‘txscript/standard.go’ dosyasına bakabilirsiniz. Yukarıdaki script’in oluşturan kod şu şekilde;

Output oluşturma kodlarını da yazdığımıza göre ‘txTransferToOutputs’ metoduna aşağıdaki kodları ekleyelim.

‘makeOutput’ metodu ile oluşturduğumuz output’un network kurallarına uyup uymadığını kontrol etmek için ‘txrules.CheckOutput’ metodunu çağırıyoruz. Bu metot kendi içinde, oluşturduğumuz output’un negatif olmadığını, gönderilebilecek maximum satoshi miktarını geçmediğini ve toz output olup olmadığını kontrol ediyor.

Unsigned Transaction

Yeni oluşturacağımız transfer transaction’ının input’unu UTXO listesinden ‘findTheTransaction’ metodu ile bulmuştuk. Buradan elde ettiğimiz miktar bilgisini makeOutput metoduna göndererek de output’u oluşturmuş olduk. Bu bilgiler ile artık yeni transaction’ı oluşturabiliriz.

Tek bir input ve tek bir output’tan oluşan unsigned transaction’ı oluşturmuş olduk. Transaction bu aşamada unsigned yani imzalanmamış durumda ve göndermeye hazır değil, yazının ilerleyen kısımlarında imzalayacağız.

Bu metodu da tamamladıktan sonra ‘txTransferToOutputs’ metodunu aşağıdaki satır ile sonlandıralım.

return newUnsignedTransactionFromInput(&txToBoTransferred, redeemOutput)

Commit : https://github.com/farukterzioglu/btcwallet-tutorial/commit/b1a0d57dccb382c987c04de0eb3703baa18628e5

Sıradaki kısım işlem ücretini ve para üstünü tanımlama.

İşlem ücreti

Bitcoin blockchain’ininde ağ üzerinden transaction gönderebilmek için madencilere işlem ücreti ödenmesi gerekmektedir. İşlem ücreti, oluşturulan transaction’ın boyutuna göre değişmekte ve btcwallet tarafından transaction oluşturulurken hesaplanmakta.

Hesaplanan işlem ücretini karşılamak için UTXO’lar içerisinden başka bir transaction seçilir ve yeni oluşturduğumuz transaction’a ikinci input olarak eklenir.

Bir bitcoin transaction’ında işlem ücreti

‘newUnsignedTransactionFromInput’ metodu içerisinde belirlediğimiz input ve output’lar ile yeni bir transaction oluşturmuştuk. Şimdi bu metot içerisinde gerekli olan işlem ücretini de bulmalı ve transaction’a ikinci input olarak eklemeliyiz. Metoda iki yeni parametre daha ekliyoruz;

..., relayFeePerKb btcutil.Amount, fetchInputs txauthor.InputSource)

‘relayFeePerKb’ parametresi kilobyte başına gerekli işlem ücretini belirtiyor. Bu değeri ‘txTransferToOutputs’ içerisinde elde etmiştik.
‘txauthor.InputSource’ tipindeki ‘fetchInputs’ parametresini ise, gerekli işlem ücreti bulmak için kullanacağız. newUnsignedTransactionFromInput metodunu aşağıdaki gibi güncelleyelim.

Metodun son hali uzun gözükse de yaptığı iş görece basit. Oluşturduğumuz transaction için işlem ücretini ‘txsizes’ ve ‘txrules’ paketlerini kullanarak şu kodlarla buluyoruz;

estimatedSize := txsizes.EstimateVirtualSize(0, 1, 0, outputs, true) targetFee := txrules.FeeForSerializeSize(relayFeePerKb, estimatedSize)

Buradan elde ettiğimiz gerekli işlem ücretini UTXO kaynağı ‘fetchInputs’ içerisinde aramamız gerekiyor. for döngüsü içerisinde tüm UTXO’lar taranarak işlem ücreti için yeterli miktarda bir transaction aranıyor. Bulunduğunda ise buna ait input değerleri ve script’ler toplanarak ‘AuthoredTx’ içerisine ekleniyor. Fark ettiyseniz bir önceki halinden farklı olarak PrevScripts, PrevInputValues ve TotalInput değerlerini de doldurduk. Bunları transaction’ı imzalarken kullanacağız.

İşlem ücretini UTXO lar arasında aramak için ‘fetchInputs’ parametresini kullandık. Bunu oluşturmak için ‘txTransferToOutputs’ metodunu aşağıdaki gibi güncelleyelim;

Yukarıdaki kodlarda satır 18 deki kontrol ile transfer edeceğimiz transaction’ı işlem ücreti için oluşturduğumuz input source dan çıkarıyoruz. Bunu yapmazsak transfer etmek üzere seçtiğimiz transaction, işlem ücreti için de seçilebilir ve doğrulama sırasında ‘duplicate input’ hatası alırız.

Buraya kadar olan kısımda işlem ücretini de eklemiş olduk. Buranın kodlarını şurada görebilirsiniz;

https://github.com/farukterzioglu/btcwallet-tutorial/commit/26e50f9a0320c920d70767f9335aa38eccc73e40

Para üstü

Normal bir bitcoin transaction’ında input olarak seçilen transaction hem gönderilmek istenen miktarı hem de işlem ücreti karşılayabilir. Örneğin 10 BTC göndermek istiyorsak ve UTXO’lar arasından 15 BTC lik UTXO seçilirse işlem ücreti için ikinci bir input’a gerek kalmaz.

15 BTC -> 10 BTC + X

Fakat bizim senaryomuzda spesifik bir transaction’ı transfer edeceğimiz için eklediğimiz ilk input. output’u birebir karşılar. Onun için işlem ücretini karşılaması için ikinci bir UTXO’u (3 BTC gibi)input olarak kesin ekliyoruz.

10 BTC + 3 BTC -> 10 BTC + X

Her iki durumda da input’lar output’lardan fazla olmakta. Oluşan fazla çıktının (X) boşa gitmemesi için kendimize tanımladığımız ikinci bir output eklemeliyiz. ‘newUnsignedTransactionFromInput’ metodunu aşağıdaki gibi güncelleyelim.

Para üstünü belirlemek için girdilerin toplamından çıktıyı ve işlem ücretini çıkarmamız gerekiyor. (str. 12)

changeAmount := inputAmount - targetAmount - maxRequiredFee

Elde ettiğimiz para üstü miktarının (changeAmount) toz sayılacak kadar küçük olup olmadığını kontrol ediyor ve eğer çok küçük miktar değilse yeni bir output tanımlayıp oluşturduğumuz transaction’ın çıktılarına eklememiz gerekiyor.

Yazının başlarında hatırlarsanız yeni bir output oluşturmak için makeOutput metodunu çağırmıştık ve TxOut tipinde bir değer dönmüştü. Para üstü için oluşturacağımız yeni TxOut’un Value değerini yani miktarını yukarıda belirlediğimiz para üstü (changeAmount) olarak belirliyoruz. Para üstünün alıcısını kendimiz olarak tanımlayacağımız için için TxOut’un PkScript değerini yani alıcı adresini almak üzere metod parametrelerinden fetchChange metodunu çağırarak yeni bir adres (changeScript) alıyoruz. (str. 15) wire.NewTxOut metodunu paraüstü miktarı (changeAmount) ve alıcı adresi (changeScript) ile çağırarak yeni bir Output (change) oluşturuyoruz ve mevcut output’lara ekliyoruz. (str. 25) Commit;

https://github.com/farukterzioglu/btcwallet-tutorial/commit/460c45b835d76ef7c201091f611a17979a28300b

Buraya kadar olan kısımda göndereceğimiz transaction’ı oluşturmuş olduk. Kısaca özetlersek, UTXO listesi içinden aradığımız spesifik transaction’ı bulup input olarak ekledik, bu input’a uygun bir output oluşturup ekledik, gerekli olan işlem ücretini hesaplayıp ikinci input olarak ekledik ve son olarak da fazla gelen miktarı para üstü olarak ekledik.

İmza

Bu transaction’ı gönderebilmek gerekli olan son işlem transaction’ı imzalamak. Bunun için transaction üzerinden ‘tx.AddAllInputScripts’ metodunu çağırıyoruz. ‘/wallet/txauthor/author.go’ paketi altında bu metodu incelersek ;

Yukarıdaki metodu özetlersem; her bir input’un (transfer edeceğimiz ve işlem ücreti için seçilen) imzaları metoda ikinci parametre olarak (prevPkScripts) geçiyoruz. Tüm input’lar bize ait olduğu için imzaların tamamı da bizim adresimize imzalanmıştır. Örnek bir bitcoin script’ini hatırlarsak;

OP_DUP OP_HASH160 <Public KeyHash> OP_EQUAL OP_CHECKSIG

Bu imzalar ile ve ‘secrets’ parametresi üzerinden elde edeceğimiz private key’imiz ile her bir input’u imzalıyoruz (str. 38) ve input’un SignatureScript’ine setliyoruz (str. 43) Bu işlem sonucunda transaction son halini almış oldu. Herşeyi doğru yaptığımızdan emin olmak için transaction’ı doğrulamamız gerekiyor.

err = validateMsgTx(tx.Tx, tx.PrevScripts, tx.PrevInputValues)

Publish

Transaction son halini aldığı için artık onu ağ üzerinde yayınlayabiliriz.
‘TransferTx’ metodu üzerinde aşağıdaki değişikliği yapalım.

Eğer transaction başarılı şekilde yayınlanırsa geriye oluşmuş hash’i döner. Bu değeri kullanıcıya geri döndürmek için ‘transferTransaction’ metodunu aşağıdaki gibi güncelleyelim.

Buraya kadar olan commit;

https://github.com/farukterzioglu/btcwallet-tutorial/commit/dc08451d95db481ea3f5dd4ac5de27ee52d97258

Tüm commit’ler;

https://github.com/farukterzioglu/btcwallet-tutorial/commits/master

Test

Uygulamaya çalıştığımız senaryomuz, spesifik bir transaction’ı başkasına transfer etmek, kod bakımından tamamlandı. transferTransaction isminde bir de rpc metodu tanımlamıştık. btcd ve btcwallet uygulamalarını tekrar derleyerek yeni eklediğimiz transaction transfer etme özelliğini test edelim.

$ cd $GOPATH/src/github.com/btcsuite/btcd
$ GO111MODULE=on go install -v . ./cmd/...
$ cd $GOPATH/src/github.com/btcsuite/btcwallet
$ GO111MODULE=on go install -v . ./cmd/...

Transfer edeceğimiz transaction UTXO’lar arasında olması gerektiği için UTXO’ları listeleyelim ve içinden birini seçelim.

$ btcctl --wallet listunspent

Yeni geliştirdiğimiz rpc komutunu çağıralım;

Komut : transfertransaction [Adres] [Transaction Hash]$ btcctl --wallet transfertransaction SPDwD28Dgf3ZH5g531sEnwWnKU6gVBWHmL 65a2b9453df52607c4845146d19a8159d52941a36f792e72fd420c83592f82fd>> 00a04951adcfad95a825740312b232eb6b354160125772741d748845045c131c

Seçtiğimiz bir transaction’ı (65a2….) SPDw…. adresine transfer etmiş olduk. Transaction transferi sonucunda oluşan yeni transaction’ın detaylarını görmek için;

$ btcctl --wallet gettransaction 00a04951adcfad95a825740312b232eb6b354160125772741d748845045c131c

Böylece bitcoin daemon uygulaması btcd ve cüzdan uygulaması btcwallet üzerinde yeni bir özellik kodlamış olduk.

--

--