Bitcoin İle Toplu Maaş Ödemeleri — C# Kod Örneği

Faruk Terzioğlu
8 min readOct 20, 2019

--

Bu yazıda yapacağım örnekte Bitcoin transaction modelini inceleyip, C# dilini kullanarak basit bir örnekle yeni transaction oluşturmanın bir kaç yöntemini anlatacağım.

Yazı içinde kullanmak üzere örnek bitcoine ihtiyacımız olduğu için üzerinde çalışmak üzere bir bitcoin ağına ve node’a ihtiyacımız var. Kolay bir şekilde bitcoin simülasyon ağı oluşturmak için;

Yukarıda paylaştığım yazıdaki örnek ile çalıştıracağımız bitcoin node’u cüzdan özelliklerini de barındırdığı ve kullanacağımız bitcoinlerin private key’i cüzdan içerisinde olduğu için, transaction’ları imzalamak için cüzdana rpc isteği göndermemiz yetiyor.

Daha sonra başka bir örnekte bir cüzdana veya node’a ihtiyaç duymadan private key ve geçmiş transaction bilgileri kullanarak offline olarak yeni bir transaction oluşturmayı ve bize ait olmayan bir node üzerinden göndermeyi anlatacağım.

Yapacağım örnekler bitcoinin UTXO modeline dayandığı için eğer hakim değilseniz öncelikli olarak bu konuları hatırlamak isteyebilirsiniz;

Yapacağım örnekte bir adet input ile 5 farklı maaş ödemesini bir transaction içerisinde yapacağız.

Simülasyon ağı

Yukarı paylaştığım yazıda detaylı olarak nasıl bitcoin simülasyon ağı çalıştırabileceğimizi anlattım. İhtiyacımız olan komutlar şunlar;

$ docker run — name bitcoind -d — volume /home/[USER_NAME]/bitcoin_data:/root/.bitcoin -p 127.0.0.1:443:18443 farukter/bitcoind:regtest (1)$ docker exec -it bitcoind bash (2)
$ ADD=$(bitcoin-cli getnewaddress) (3)
$ bitcoin-cli generatetoaddress 101 $ADD (4)
$ exit

Bu komutlar sonunda artık kullanacağımız bir adet 50 BTC’lik bir utxo bulunmaktadır. Kısaca yaptığımız şey;

  1. Simülasyon bitcoin Docker image’ı ile yeni bir container oluşturduk.
  2. Çalışan container’ın (bitcoind) içine girdik.
  3. Yeni bir adres üretip [ADD] değişkenine atadık.
  4. Toplamda 101 adet blok üretip blok ödüllerini [ADD] adresine tanımladık.

Blok ödüllerinin kullanılabilmesi için 100 onay alınması gerekiyor. Onun için ilk blok ödülünü (50 BTC) kullanmak için üzerine 100 blok daha üretip toplamda 101 blok ürettik.

Emin olmak için bir rpc isteği atalım;

Kod içerisinde kullanacağımız bitcoinlerimiz hazır. Aşağıdaki bilgileri not alalım, daha sonra inceleyeceğiz;

“txid”: “1e80635217864105eb816*****be857a41ab210fd33f83a3273ddba76”,
“vout”: 0,
“amount”: 50.00000000

Uygulama

Örnekleri bir C# unit test uygulaması içerisinde yapacağım, bu sayede hızlı bir şekilde test edebilirim.

Yapacağım adımlar aşağı yukarı;

  1. Ödeme yapılacak adresler ve kaçar TL ödeme yapılacağı bilgisini almak.
  2. 1 TL nin karşılığı olan bitcoin bilgisini almak.
  3. Toplam yapılacak BTC miktarını karışılamak için UTXO’lar arasında yeterli miktarda transaction’ın seçilmesi.
  4. Bu bilgiler ile yeni bir transaction oluşturup input ve outputlarını eklemek.
  5. Oluşan para üstünün hesaplanması ve ek bir output olarak eklenmesi.
  6. Transaction’ın imzalanması ve gönderilmesi.

Bitcoin node’u ile haberleşmek üzere C# projesinde NBitcoin kütüphanesi kullanacağız. Projeye eklemek için;

$ dotnet add package NBitcoin

Kullanacağımız namespaceler;

using NBitcoin;
using NBitcoin.RPC;

Gerekli bilgileri çekmek üzere çalışan bitcoin node’una istekler yapacağınız. Bunun için bir RPCClient nesnesi oluşturalım;

Simülasyon ağında çalıştığımız için network değişkenini Network.RegTest olarak belirledik. Alabildiği diğer değerler TestNet ve Main.

Kullanıcı adı ve şifre olarak, çalışan simülasyon ağının ayarlarında (bitcoin.conf) belirtilen değerleri kullandık; myuser, SomeDecentp4ssw0rd

Docker imajını çalıştırırken bitcoin node portunu (18443) host makinedeki 127.0.0.1:443 adresine bağlamıştık. İstekleri buraya göndermek üzere address: new Uri(“http://127.0.0.1:443") şeklinde tanımladık.

Yapacağımız ödemeleri temsilen örnek adres-maaş bilgilerini alalım;

TL maaşların BTC karşılığını hesaplamak üzere bitcoin fiyatını BtcTurk API’ı üzerinden alabilirsiniz;
https://api.btcturk.com/api/v2/ticker?pairSymbol=BTCTRY

Ben basitlik adına 1 TL yi 0.00001 BTC olarak alıyorum.

decimal btcPerTl = 0.00001m;

Toplam yapılacak BTC ödemesini hesaplayalım;

var totalPayment = salaries.Sum((pair) => pair.Value);Money totalPaymentAmountBtc = new Money(totalPayment * btcPerTl, MoneyUnit.BTC);

Bitcoin miktarını tutmak üzere Money class’ını kullanıyoruz. Hesaplamaları Bitcoin üzerinden yaptığımız için, miktarın tipini (Bitcoin, Satoshi vs) belirtmek üzere MoneyUnit.BTC değeri kullanıyoruz. Diğer değerler;

public enum MoneyUnit
{
Satoshi = 1,
Bit = 100,
MilliBTC = 100000,
BTC = 100000000
}

Ödenecek toplam tutara madenci ücretlerini de eklemeliyiz. Örnek olarak madenci ücretini (fee) 0.000003 BTC olarak belirledim;

var minerFee = new Money(0.000003m, MoneyUnit.BTC);
totalPaymentAmountBtc += minerFee;

Yazının başında Postman’den gönderdiğimiz istek (listunspent) ile kullanabileceğimiz utxo’ları listelemiştik. Aynı isteği NBitcoin ile gönderelim;

var unspentResponse = await client.ListUnspentAsync();
client.ListUnspentAsync() sonucu

Bizim örneğimizde elimizde kullanmak üzere 50 BTC lik bir utxo var. Fakat daha küçük utxo’larda olabilirdi. Onun için sahip olduğumuz utxo’lar içerisinden yeterli miktarda toplamak üzere;

Yeni bir transaction oluşturup seçilen input’ları ekleyelim;

var transaction = Transaction.Create(network);
foreach (var input in inputList)
{
transaction.Inputs.Add(input.OutPoint);
}

Şimdi ise ödenecek maaşlara karşılık gelen outputları ekleyelim;

foreach (var salary in salaries)
{
transaction.Outputs.Add(new TxOut()
{
Value = new Money(salary.Value * btcPerTl, MoneyUnit.BTC),
ScriptPubKey = BitcoinAddress.Create(salary.Key, network).ScriptPubKey
});
}

“salary.Value” değeri maaşın TL değerini tutuyor, bunu bir TL’nin BTC karşılığı (btcPerTl) ile çarpıyoruz ve tipini MoneyUnit.BTC olarak belirtiyoruz.

string tipindeki adresi (salary.Key) bitcoin adresine çevirmek için BitcoinAddress.Create metodunu kullanıyoruz ve bitcoin adresleri çalışılan ağa göre değiştiği için network bilgisini veriyoruz. Oluşan transaction’ı incelersek;

Girdiler (örneğimizde 50 BTC) çıktılardan büyük olacağı için bir para üstü oluşacaktır;

var changeAmount = inputBtcSum - totalPaymentAmountBtc;

Para üstünü kendimize tanımlamak için bir para üstü adresi (ChangeAddress) almalıyız;

var changeAddress = await client.GetRawChangeAddressAsync();

Hesapladığımız para üstünü ve adresi belirledikten sonra transaction’a bir çıktı daha ekleyelim;

TxOut changeTxOut = new TxOut()
{
Value = changeAmount,
ScriptPubKey = changeAddress.ScriptPubKey
};
transaction.Outputs.Add(changeTxOut);

Oluşan transaction’ı inceleyelim. İşaretlediğim yerler yukarıdan aşağıya; Girdilerin sayısı (1 adet 50 BTC lik utxo), çıktıların adeti (6), bir maaş ödemesi (0.125 BTC) ve para üstü (49.68249900 BTC)

Oluşan transaction’ı imzalayalım;

var signedTxResponse = await client.SignRawTransactionWithWalletAsync(new SignRawTransactionRequest() 
{
Transaction = transaction,
});

Tekrar hatırlatayım, bir private key kullanmadan imzalayı, çalıştırdığımız node’daki açık olan cüzdan ile sağlıyoruz.

İmzaladığımız transaction’ı gönderelim;

var txHash = await client.SendRawTransactionAsync(signedTxResponse.SignedTransaction);

Sonuç olarak gelen transaction id ile node üzerinden de inceleyelim;

$ bitcoin-cli getrawtransaction 684e2b2534e****7847ff868616418ddd0 1

“getrawtransaction” ilk parametresi tx id, 2. parametre ile de detaylı (verbose) olarak görmek istediğimizi belirtiyoruz.

utxo’ları tekrar listelersek artık boş olduğunu görebiliriz;

Oluşturduğumuz transaction’ı bir bloğa yazalım;

Oluşan bloğu incelemek için;

Blok içerisinde iki transaction (tx) olduğu görebiliriz. Biri yukarıda oluşturduğumuz transaction (684e2b253****) diğeri de yeni oluşan bloğun ödülü. Bir bloktaki ilk transaction her zaman madenci ödülü olan transaction’dır.

utxo’ları (unspent transaction output) tekrar listeleyelim (listunspent);

Görüldüğü üzere iki utxo var. Biri az önce kod ile oluşturduğumuz transaction’daki para üstü (49.68249700) diğeri de yeterli olduğuna (100 confirmation) ulaşmış madenci ödülü (coinbase transaction). Bu yeni utxo 2. bloğun madenci ödülü, 1.’sini az önce kullandık. 2. bloğu incelersek, sadece bir adet transaction olduğunu ve bunun da yukarıda utxo’lar arasında listenen transaction (112a58****) olduğunu görebiliriz;

İnceleyeceğim bir diğer metod da “FundRawTransaction”. Bu metod ile output’ları belirlenmiş bir transaction’ın input’larını ve fee’yi otomatik olarak seçtirebiliriz. Bu metodu çağırmadan önce transaction;

Transaction içerisinde sadece ödenecek 5 adet maaşa denk gelen output’lar var, inputlar ise boş. Bu transaction’ı fonlarsak (FundRawTransaction);

await client.FundRawTransactionAsync(transaction);

Yeni oluşan transaction;

Input’a dikkat ederseniz, daha önce oluşturduğumuz tx’in (684e2b2***) 6. çıktısı kullanılmış. PrevOut değerinin karşısında satır sonunda 5 olarak belirtilmiş. İlk 5 çıktı maaş ödemeleri idi. Tekrar para üstü oluştuğu için de output sayısı 6 ya çıktı.

Transaction’ı gönderdikten sonra kalan utxo’lar;

Mempool

Bu transaction’ı bloğa yazmadan önce inceleyeceğim bir konu da “mempool”.

Bloğa yazılmamış transaction’lar mempool’da bekler. Daha sonra madenciler belli kriterlere göre (yüksek fee gibi) mempool’dan transaction’ları seçerek blok içerisine dahil eder. Mempool’daki transaction’ları listelemek için;

Detayını incelersek;

$ bitcoin-cli getrawtransaction 525604****5400b14a9433782 1

4. output (“n”: 3) bize geri gelen para üstü;

Mempool’daki bu output’u incelememin sebebi, örneklendireceğim bir sonraki konu; bir bloğa yazılmamış transaction’ı harcamak.

Yukarıdaki örneklerde hep utxo’lar arasından bir input seçtik. ‘listunspent’ metodundan dönenler yeni transaction’a girdi oluyordu. Şu anda listunspent metodunu çağırırsak sadece madendi ödülü olan 50 BTC var, para üstü olarak aldığımız 49.36493820 BTC kullanılabilir gözükmüyor.

Fakat mempool’daki bu output’u (bloğa yazılmamış) kullanmak mümkün.

Yeni bir transaction’da input eklerken utxo’lar arasından seçmek veya otomatik fonlamak (FundRawTransaction) yerine, transaction id’sini ve transaction içerisindeki sırasını bildiğimiz, bize ait olan fakat henüz bloğa yazılmamış bir output’u da kullanabiliriz. Bunun için yapmamız gereken;

transaction.Inputs.Add(new OutPoint(){
Hash = new uint256(“5256***9433782”),
N = 3
});

Yukarıdaki kodda, “5256***9433782” id’ye sahip transaction’ın 4. output’unu (N = 3) input olarak ekledik.

Bitcoin de, transaction id ve output index ikilisine Outpoint denmekte.

Bu transaction’ın input ve output’ları belli olmasına rağmen hala madenci ücreti belirlenmeli ve para üstü için ek bir output eklemeliyiz. Bunun için de FundRawTransaction metodunu kullanabiliriz. Eğer fee ve output’ları karşılayacak input varsa yeni bir input eklemez. Eğer input’ların toplamı output’ların toplamına eşit ise fee’yi karşılamak üzere yeni bir input eklenecektir.

Bu transaction’ı da gönderdikten sonra mempool;

Bunların detayını görmek istersek ek olarak “true” parametresi ekleyelim;

Dikkat ederseniz, ilk transaction’da kim tarafından harcandığı (“spentby”) ikinci transaction’da ise kime bağımlı olduğu (“depends”) belirtilmiş.

Yeni bir blok üretirsek;

Mempool’daki her iki transaction’ında bloğa yazıldığını görebiliriz.

Uzunca bir örnek oldu fakat Bitcoin’in temeli olan UTXO modelini kod üzerinden incelemiş olduk. Yukarıdaki örneklerin kodunu şurada bulabilirsiniz;

--

--