Initial commit
This commit is contained in:
463
spec/lib/bitcoin/blockchain_spec.rb
Normal file
463
spec/lib/bitcoin/blockchain_spec.rb
Normal file
@@ -0,0 +1,463 @@
|
||||
describe Bitcoin::Blockchain do
|
||||
context :features do
|
||||
it 'defaults' do
|
||||
blockchain1 = Bitcoin::Blockchain.new
|
||||
expect(blockchain1.features).to eq Bitcoin::Blockchain::DEFAULT_FEATURES
|
||||
end
|
||||
|
||||
it 'override defaults' do
|
||||
blockchain2 = Bitcoin::Blockchain.new(cash_addr_format: true)
|
||||
expect(blockchain2.features[:cash_addr_format]).to be_truthy
|
||||
end
|
||||
|
||||
it 'custom feautures' do
|
||||
blockchain3 = Bitcoin::Blockchain.new(custom_feature: :custom)
|
||||
expect(blockchain3.features.keys).to contain_exactly(*Bitcoin::Blockchain::SUPPORTED_FEATURES)
|
||||
end
|
||||
end
|
||||
|
||||
context :configure do
|
||||
let(:blockchain) { Bitcoin::Blockchain.new }
|
||||
it 'default settings' do
|
||||
expect(blockchain.settings).to eq({})
|
||||
end
|
||||
|
||||
it 'currencies and server configuration' do
|
||||
currencies = Currency.where(type: :coin).first(2).map(&:to_blockchain_api_settings)
|
||||
settings = { server: 'http://user:password@127.0.0.1:18332',
|
||||
currencies: currencies,
|
||||
something: :custom }
|
||||
blockchain.configure(settings)
|
||||
expect(blockchain.settings).to eq(settings.slice(*Peatio::Blockchain::Abstract::SUPPORTED_SETTINGS))
|
||||
end
|
||||
end
|
||||
|
||||
context :transaction_sources do
|
||||
let(:server) { 'http://user:password@127.0.0.1:18332' }
|
||||
let(:endpoint) { '127.0.0.1:18332' }
|
||||
let(:blockchain) do
|
||||
Bitcoin::Blockchain.new.tap {|b| b.configure(server: server)}
|
||||
end
|
||||
|
||||
def request_raw_transaction(transaction)
|
||||
{
|
||||
jsonrpc: '1.0',
|
||||
method: :getrawtransaction,
|
||||
params: [transaction.hash, 1]
|
||||
}.to_json
|
||||
end
|
||||
|
||||
context 'transaction 3 vins' do
|
||||
|
||||
let(:transaction_file_name) { 'b3aa85765aa52cf7eb0a7f0eb7ac3447b9b1a82b9323bdd8d73fc305073a3711.json' }
|
||||
let(:transaction_data) do
|
||||
Rails.root.join('spec', 'resources', 'bitcoin-data', transaction_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
|
||||
let(:vin1_transaction_file_name) { 'vin-1ec4bf89b77f0d72ee41f41c97a6d380bd69e0221bf182b993b64bb37d017b57.json' }
|
||||
let(:vin1_transaction_data) do
|
||||
Rails.root.join('spec', 'resources', 'bitcoin-data', vin1_transaction_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
|
||||
let(:vin2_transaction_file_name) { 'vin-8bde1da3d2315e09f910cb9782018fe243740c56cd7ca78a19016b169e74180a.json' }
|
||||
let(:vin2_transaction_data) do
|
||||
Rails.root.join('spec', 'resources', 'bitcoin-data', vin2_transaction_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
|
||||
let(:vin3_transaction_file_name) { 'vin-64a04cad438b64e260bc0b832bde79913d618d3206e834de66bbcb1304629d61.json' }
|
||||
let(:vin3_transaction_data) do
|
||||
Rails.root.join('spec', 'resources', 'bitcoin-data', vin3_transaction_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
|
||||
let(:transaction) { Peatio::Transaction.new(hash: 'b3aa85765aa52cf7eb0a7f0eb7ac3447b9b1a82b9323bdd8d73fc305073a3711') }
|
||||
let(:vin1_transaction) { Peatio::Transaction.new(hash: '1ec4bf89b77f0d72ee41f41c97a6d380bd69e0221bf182b993b64bb37d017b57') }
|
||||
let(:vin2_transaction) { Peatio::Transaction.new(hash: '8bde1da3d2315e09f910cb9782018fe243740c56cd7ca78a19016b169e74180a') }
|
||||
let(:vin3_transaction) { Peatio::Transaction.new(hash: '64a04cad438b64e260bc0b832bde79913d618d3206e834de66bbcb1304629d61') }
|
||||
|
||||
before do
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: request_raw_transaction(transaction))
|
||||
.to_return(body: transaction_data.to_json)
|
||||
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: request_raw_transaction(vin1_transaction))
|
||||
.to_return(body: vin1_transaction_data.to_json)
|
||||
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: request_raw_transaction(vin2_transaction))
|
||||
.to_return(body: vin2_transaction_data.to_json)
|
||||
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: request_raw_transaction(vin3_transaction))
|
||||
.to_return(body: vin3_transaction_data.to_json)
|
||||
end
|
||||
|
||||
it do
|
||||
addresses = blockchain.transaction_sources(transaction)
|
||||
expect(addresses).to eq(['1GsV7tXxXhfdp7kEFoFkgQLUWyv2xgPjnQ'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'miner transaction' do
|
||||
let(:transaction) { Peatio::Transaction.new(hash: '1da4b5a135cc0fee9d3aeb5428a898fd12fe5b1b777fd291dbbdbb72c2da8759') }
|
||||
let(:transaction_file_name) { 'miner_transaction.json' }
|
||||
let(:transaction_data) do
|
||||
Rails.root.join('spec', 'resources', 'bitcoin-data', transaction_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
|
||||
before do
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: request_raw_transaction(transaction))
|
||||
.to_return(body: transaction_data.to_json)
|
||||
end
|
||||
|
||||
it do
|
||||
addresses = blockchain.transaction_sources(transaction)
|
||||
expect(addresses).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context :latest_block_number do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:server) { 'http://user:password@127.0.0.1:18332' }
|
||||
let(:endpoint) { '127.0.0.1:18332' }
|
||||
let(:blockchain) do
|
||||
Bitcoin::Blockchain.new.tap {|b| b.configure(server: server)}
|
||||
end
|
||||
|
||||
it 'returns latest block number' do
|
||||
block_number = 1489174
|
||||
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: { jsonrpc: '1.0',
|
||||
method: :getblockcount,
|
||||
params: [] }.to_json)
|
||||
.to_return(body: { result: block_number,
|
||||
error: nil,
|
||||
id: nil }.to_json)
|
||||
|
||||
expect(blockchain.latest_block_number).to eq(block_number)
|
||||
end
|
||||
|
||||
it 'raises error if there is error in response body' do
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: { jsonrpc: '1.0',
|
||||
method: :getblockcount,
|
||||
params: [] }.to_json)
|
||||
.to_return(body: { result: nil,
|
||||
error: { code: -32601, message: 'Method not found' },
|
||||
id: nil }.to_json)
|
||||
|
||||
expect{ blockchain.latest_block_number }.to raise_error(Peatio::Blockchain::ClientError)
|
||||
end
|
||||
|
||||
it 'keeps alive' do
|
||||
stub_request(:post, endpoint)
|
||||
.to_return(body: { result: 1489174,
|
||||
error: nil,
|
||||
id: nil }.to_json)
|
||||
.with(headers: { 'Connection': 'keep-alive',
|
||||
'Keep-Alive': '30' })
|
||||
|
||||
blockchain.latest_block_number
|
||||
end
|
||||
end
|
||||
|
||||
context :fetch_block! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:block_file_name) { '1354419-1354420.json' }
|
||||
let(:block_data) do
|
||||
Rails.root.join('spec', 'resources', 'bitcoin-data', block_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
|
||||
let(:start_block) { block_data.first['result']['height'] }
|
||||
let(:latest_block) { block_data.last['result']['height'] }
|
||||
|
||||
def request_block_hash_body(block_height)
|
||||
{ jsonrpc: '1.0',
|
||||
method: :getblockhash,
|
||||
params: [block_height]
|
||||
}.to_json
|
||||
end
|
||||
|
||||
def request_block_body(block_hash)
|
||||
{ jsonrpc: '1.0',
|
||||
method: :getblock,
|
||||
params: [block_hash, 2]
|
||||
}.to_json
|
||||
end
|
||||
|
||||
before do
|
||||
block_data.each do |blk|
|
||||
# stub get_block_hash
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: request_block_hash_body(blk['result']['height']))
|
||||
.to_return(body: {result: blk['result']['hash']}.to_json)
|
||||
|
||||
# stub get_block
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: request_block_body(blk['result']['hash']))
|
||||
.to_return(body: blk.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
let(:currency) do
|
||||
Currency.find_by(id: :btc)
|
||||
end
|
||||
|
||||
let(:server) { 'http://user:password@127.0.0.1:18332' }
|
||||
let(:endpoint) { 'http://127.0.0.1:18332' }
|
||||
let(:blockchain) do
|
||||
Bitcoin::Blockchain.new.tap { |b| b.configure(server: server, currencies: [currency]) }
|
||||
end
|
||||
|
||||
context 'first block' do
|
||||
subject { blockchain.fetch_block!(start_block) }
|
||||
|
||||
it 'builds expected number of transactions' do
|
||||
expect(subject.count).to eq(14)
|
||||
end
|
||||
|
||||
it 'all transactions are valid' do
|
||||
expect(subject.all?(&:valid?)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'last block' do
|
||||
subject { blockchain.fetch_block!(latest_block) }
|
||||
|
||||
it 'builds expected number of transactions' do
|
||||
expect(subject.count).to eq(20)
|
||||
end
|
||||
|
||||
it 'all transactions are valid' do
|
||||
expect(subject.all?(&:valid?)).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context :build_transaction do
|
||||
|
||||
let(:tx_file_name) { '1858591d8ce638c37d5fcd92b9b33ee96be1b950e593cf0cbf45e6bfb1ad8a22.json' }
|
||||
|
||||
let(:tx_hash) do
|
||||
Rails.root.join('spec', 'resources', 'bitcoin-data', tx_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
let(:expected_transactions) do
|
||||
[{:hash=>"1858591d8ce638c37d5fcd92b9b33ee96be1b950e593cf0cbf45e6bfb1ad8a22",
|
||||
:txout=>0,
|
||||
:to_address=>"mg4KVGerD3rYricWC8CoBaayDp1YCKMfvL",
|
||||
:amount=>0.325e0,
|
||||
:status=>"success",
|
||||
:currency_id=>currency.id},
|
||||
{:hash=>"1858591d8ce638c37d5fcd92b9b33ee96be1b950e593cf0cbf45e6bfb1ad8a22",
|
||||
:txout=>1,
|
||||
:to_address=>"mqaBwWDjJCE2Egsf6pfysgD5ZBrfsP7NkA",
|
||||
:amount=>0.1964466932e2,
|
||||
:status=>"success",
|
||||
:currency_id=>currency.id}]
|
||||
end
|
||||
|
||||
let(:currency) do
|
||||
Currency.find_by(id: :btc)
|
||||
end
|
||||
|
||||
let(:blockchain) do
|
||||
Bitcoin::Blockchain.new.tap { |b| b.configure(currencies: [currency.to_blockchain_api_settings]) }
|
||||
end
|
||||
|
||||
it 'builds formatted transactions for passed transaction' do
|
||||
expect(blockchain.send(:build_transaction, tx_hash)).to contain_exactly(*expected_transactions)
|
||||
end
|
||||
|
||||
context 'multiple currencies' do
|
||||
let(:expected_transactions) do
|
||||
[{:hash=>"1858591d8ce638c37d5fcd92b9b33ee96be1b950e593cf0cbf45e6bfb1ad8a22",
|
||||
:txout=>0,
|
||||
:to_address=>"mg4KVGerD3rYricWC8CoBaayDp1YCKMfvL",
|
||||
:amount=>0.325e0,
|
||||
:status=>"success",
|
||||
:currency_id=>currency1.id},
|
||||
{:hash=>"1858591d8ce638c37d5fcd92b9b33ee96be1b950e593cf0cbf45e6bfb1ad8a22",
|
||||
:txout=>1,
|
||||
:to_address=>"mqaBwWDjJCE2Egsf6pfysgD5ZBrfsP7NkA",
|
||||
:amount=>0.1964466932e2,
|
||||
:status=>"success",
|
||||
:currency_id=>currency1.id},
|
||||
{:hash=>"1858591d8ce638c37d5fcd92b9b33ee96be1b950e593cf0cbf45e6bfb1ad8a22",
|
||||
:txout=>0,
|
||||
:to_address=>"mg4KVGerD3rYricWC8CoBaayDp1YCKMfvL",
|
||||
:amount=>0.325e0,
|
||||
:status=>"success",
|
||||
:currency_id=>currency2.id},
|
||||
{:hash=>"1858591d8ce638c37d5fcd92b9b33ee96be1b950e593cf0cbf45e6bfb1ad8a22",
|
||||
:txout=>1,
|
||||
:to_address=>"mqaBwWDjJCE2Egsf6pfysgD5ZBrfsP7NkA",
|
||||
:amount=>0.1964466932e2,
|
||||
:status=>"success",
|
||||
:currency_id=>currency2.id}]
|
||||
end
|
||||
|
||||
let(:currency1) do
|
||||
Currency.find_by(id: :btc)
|
||||
end
|
||||
|
||||
let(:currency2) do
|
||||
Currency.find_by(id: :btc)
|
||||
end
|
||||
|
||||
let(:blockchain) do
|
||||
Bitcoin::Blockchain.new.tap do |b|
|
||||
b.configure(currencies: [currency1.to_blockchain_api_settings, currency2.to_blockchain_api_settings])
|
||||
end
|
||||
end
|
||||
|
||||
it 'builds formatted transactions for passed transaction per each currency' do
|
||||
expect(blockchain.send(:build_transaction, tx_hash)).to contain_exactly(*expected_transactions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'three vout transaction' do
|
||||
let(:tx_file_name) { '1da5cd163a9aaf830093115ac3ac44355e0bcd15afb59af78f84ad4084973ad0.json' }
|
||||
|
||||
let(:expected_transactions) do
|
||||
[{:hash=>"1da5cd163a9aaf830093115ac3ac44355e0bcd15afb59af78f84ad4084973ad0",
|
||||
:txout=>0,
|
||||
:to_address=>"2N5WyM3QT1Kb6fvkSZj3Xvcx2at7Ydm5VmL",
|
||||
:amount=>0.1e0,
|
||||
:status=>"success",
|
||||
:currency_id=>"btc"},
|
||||
{:hash=>"1da5cd163a9aaf830093115ac3ac44355e0bcd15afb59af78f84ad4084973ad0",
|
||||
:txout=>1,
|
||||
:to_address=>"2MzDFuDK9ZEEiRsuCDFkPdeHQLGvwbC9ufG",
|
||||
:amount=>0.2e0,
|
||||
:status=>"success",
|
||||
:currency_id=>"btc"},
|
||||
{:hash=>"1da5cd163a9aaf830093115ac3ac44355e0bcd15afb59af78f84ad4084973ad0",
|
||||
:txout=>2,
|
||||
:to_address=>"2MuvCKKi1MzGtvZqvcbqn5twjA2v5XLaTWe",
|
||||
:amount=>0.11749604e0,
|
||||
:status=>"success",
|
||||
:currency_id=>"btc"}]
|
||||
end
|
||||
|
||||
it 'builds formatted transactions for each vout' do
|
||||
expect(blockchain.send(:build_transaction, tx_hash)).to contain_exactly(*expected_transactions)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context :load_balance_of_address! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:server) { 'http://user:password@127.0.0.1:18332' }
|
||||
let(:blockchain) do
|
||||
Bitcoin::Blockchain.new.tap {|b| b.configure(server: server)}
|
||||
end
|
||||
|
||||
|
||||
let(:response) do
|
||||
[
|
||||
[
|
||||
[
|
||||
"mh2e7YHio7fTjLXHZ3KRXDfU52RbwQbhtK",
|
||||
0,
|
||||
""
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
"mkuYucVhRQDiSHszbZ9M7d7vygKymyZ549",
|
||||
0.14458097,
|
||||
"my_imported_address_from_electrum"
|
||||
],
|
||||
[
|
||||
"mpD544rTPbRNDr9yzK9MTGS4ckfVxUNY42",
|
||||
0.24997197,
|
||||
"imported_address_from_bitgo"
|
||||
]
|
||||
]
|
||||
]
|
||||
end
|
||||
|
||||
before do
|
||||
stub_request(:post, 'http://127.0.0.1:18332')
|
||||
.with(body: { jsonrpc: '1.0',
|
||||
method: :listaddressgroupings,
|
||||
params: [] }.to_json)
|
||||
.to_return(body: { result: response,
|
||||
error: nil,
|
||||
id: nil }.to_json)
|
||||
end
|
||||
|
||||
context 'address with balance is defined' do
|
||||
it 'requests rpc listaddressgroupings and finds address balance' do
|
||||
address = 'mpD544rTPbRNDr9yzK9MTGS4ckfVxUNY42'
|
||||
|
||||
result = blockchain.load_balance_of_address!(address, :btc)
|
||||
expect(result).to be_a(BigDecimal)
|
||||
expect(result).to eq('0.24997197'.to_d)
|
||||
end
|
||||
|
||||
it 'requests rpc listaddressgroupings and finds address with zero balance' do
|
||||
address = 'mh2e7YHio7fTjLXHZ3KRXDfU52RbwQbhtK'
|
||||
|
||||
result = blockchain.load_balance_of_address!(address, :btc)
|
||||
expect(result).to be_a(BigDecimal)
|
||||
expect(result).to eq('0'.to_d)
|
||||
end
|
||||
end
|
||||
|
||||
context 'address is not defined' do
|
||||
it 'requests rpc listaddressgroupings and do not find address' do
|
||||
address = '1PoxQx6Pk5NwWN1yyBx2jifFvS9eJAksdf'
|
||||
expect{ blockchain.load_balance_of_address!(address, :btc)}.to raise_error(Peatio::Blockchain::UnavailableAddressBalanceError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'client error is raised' do
|
||||
before do
|
||||
stub_request(:post, 'http://127.0.0.1:18332')
|
||||
.with(body: { jsonrpc: '1.0',
|
||||
method: :listaddressgroupings,
|
||||
params: [] }.to_json)
|
||||
.to_return(body: { result: nil,
|
||||
error: { code: -32601, message: 'Method not found' },
|
||||
id: nil }.to_json)
|
||||
end
|
||||
|
||||
it 'raise wrapped client error' do
|
||||
expect{ blockchain.load_balance_of_address!('anything', :btc)}.to raise_error(Peatio::Blockchain::ClientError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user