Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Bitcoin CoreとRails Webアプリ開発ハンズオン

Bitcoin CoreとRails Webアプリ開発ハンズオン

日本暗号通貨ユーザ会 ビットコインとか勉強会 第37回にて、

Bitcoin CoreのRPC APIをRuby on Railsから操作する簡単なWebアプリ開発ハンズオンを行いました。

https://cryptocurrency.connpass.com/event/161743/

Shu Kobuchi

January 31, 2020
Tweet

More Decks by Shu Kobuchi

Other Decks in Programming

Transcript

  1. ίϛϡχςΟ঺հ ೔ຊ҉߸௨՟ϢʔβձɹIUUQTDSZQUPDVSSFODZDPOOQBTTDPN  ۚ  ౔ 04$ ΦʔϓϯιʔεΧϯϑΝϨϯε ౦ژ ʮϏοτίΠϯͱ͔ษڧձʯʢ݄̍ճɿฏ೔໷ʣ

    w ࣍ճ͸೥݄೔ ໦ ͰʮϞφίΠϯͱ͔ษڧձʯௐ੔த ʮ҉߸௨՟ಡॻձʯʢ݄̍ճɿฏ೔໷ʣ w ࣍ճ͸೥݄೔ Ր ʮ"CBMBODIFʯ DSBTIBDBEFNZ *5ܥಈը഑৴ ʹͯϏοτίΠϯͱ͔ษڧձͷΞʔΧΠϒ഑৴த
 IUUQTDSBTIBDBEFNZDPNNVOJUZDSZQUPDVSSFODZ :PV5VCFνϟϯωϧʢษڧձʗಡॻձʣIUUQCJUMZZPVUVCFDDTUVEZ 3
  2. ϏοτίΠϯ͸Φʔϓϯιʔε ϏοτίΠϯ #JUDPJO ͸Φʔϓϯιʔε ୭Ͱ΋։ൃʹࢀՃՄೳʂ
 IUUQTHJUIVCDPNCJUDPJOCJUDPJO ϏοτίΠϯΛ͸͡Ίɺ҉߸௨՟ɾϒϩοΫνΣʔϯͷଟ͘͸044 ٕज़తʹ΋໘ന͍ʂʂ ϥΠτίΠϯ -JUFDPJO

    ͸#JUDPJOͷιʔείʔυ͔ΒϑΥʔΫ
 IUUQTHJUIVCDPNMJUFDPJOQSPKFDUMJUFDPJO ϞφίΠϯ .POBDPJO ͸-JUFDPJOͷιʔείʔυ͕ϕʔε
 IUUQTHJUIVCDPNNPOBDPJOQSPKFDUNPOBDPJO ΠʔαϦΞϜ &UIFSFVN ΋Φʔϓϯιʔε
 IUUQTHJUIVCDPNFUIFSFVNHPFUIFSFVN 7
  3. CJUDPJODPOG -JOVYͷ৔߹ DEdCJUDPJO .BDͷ৔߹ DE6TFST\64&3^-JCSBSZ"QQMJDBUJPO4VQQPSU#JUDPJO OBOPCJUDPJODPOG·ͨ͸WJCJUDPJODPOG ԼهΛίϐϖͯ͠อଘ 15 regtest=1 txindex=1

    server=1 daemon=1 rpcuser=hoge rpcpassword=hoge [regtest] rpcport=18443 port=18444 regtest=1 # regtestϞʔυ༗ޮԽ txindex=1 # શͯͷTXΛऔಘ(ϑϧϊʔυ) server=1 # RPCΞΫηεՄೳʹ daemon=1 # ίϯιʔϧϩά͕ग़ͳ͍ rpcuser=hoge # RPCΞΫηεϢʔβ rpcpassword=hoge # RPCΞΫηεύεϫʔυ [regtest] # regtest༻ઃఆ rpcport=18443 # RPC༻port port=18444 # ଞͷϊʔυͱ௨৴͢Δport
  4. 6CVOUV΁ͷ%PDLFSͷΠϯετʔϧ IUUQTEPDTEPDLFSDPNJOTUBMMMJOVYEPDLFSDFVCVOUV ʹԊͬͯΠϯετʔϧ TVEPBQUHFUVQEBUF ԼهߦΛίϐϖ࣮ͯ͠ߦ 1%'͔Βίϐϖ͢Δͱվߦ͕ೖͬͯ͠·͏͔΋  TVEPBQUHFUJOTUBMMBQUUSBOTQPSUIUUQTDBDFSUJpDBUFTDVSMHOVQH BHFOUTPGUXBSFQSPQFSUJFTDPNNPO ԼهߦΛίϐϖ࣮ͯ͠ߦ

    DVSMGT4-IUUQTEPXOMPBEEPDLFSDPNMJOVYVCVOUVHQHcTVEP BQULFZBEE ԼهߦΛίϐϖ࣮ͯ͠ߦ TVEPBEEBQUSFQPTJUPSZEFC<BSDIBNE>IUUQT EPXOMPBEEPDLFSDPNMJOVYVCVOUV MTC@SFMFBTFDT TUBCMF 20
  5. +BWB4DSJQUͱ%# ZBSO DPOpHEBUBCBTFZNM ͷԼʹԼهΛ௥ه ۭͷ%#ͷ࡞੒ SBJMTECDSFBUF 25 default: &default adapter:

    postgresql encoding: unicode # For details on connection pooling, see Rails configuration guide # https://guides.rubyonrails.org/configuring.html#database-pooling pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> host: db username: postgres password: ""
  6. ίϯτϩʔϥΛ௥Ճ $USM DͰ3BJMTఀࢭ ίϯτϩʔϥΛ௥Ճ SBJMTHDPOUSPMMFSCJUDPJO@BQQ CJUDPJOUPLBBQQDPOUSPMMFSTCJUDPJO@BQQ@DPOUSPMMFSSC ͳͲ͕࡞੒͞Ε͍ͯͯɺ͜ͷϑΝΠϧΛฤू ߦͷؒʹԼهΛ௥Ճɺߦͷ্ʹࠨهΛ௥ه 29 class

    BitcoinAppController < ApplicationController end require 'bitcoin' require 'net/http' require 'json' Bitcoin.chain_params = :regtest RPCUSER="hoge" RPCPASSWORD="hoge" HOST="host.docker.internal" PORT=18443 def explorer @blockchaininfo = bitcoinRPC('getblockchaininfo',[]) logger.debug @blockchaininfo render template: 'bitcoin_app/explorer' end private def bitcoinRPC(method,param) http = Net::HTTP.new(HOST, PORT) request = Net::HTTP::Post.new('/') request.basic_auth(RPCUSER,RPCPASSWORD) request.content_type = 'application/json' request.body = {method: method, params: param, id: 'jsonrpc'}.to_json JSON.parse(http.request(request).body)["result"] end
  7. #JUDPJO3BJMTΞϓϦ։ൃଓ͖ (JU)VC্ʹ͋Δ3BJMTΞϓϦΛࢀߟʹਐΊ͍͖ͯ·͢ IUUQTHJUIVCDPNTIVLPCSBJMTCJUDPJO FYQMPSFSίϯτϩʔϥΛ࡞ͬͨΈ͍ͨʹɺ֤ػೳΛ௥Ճ͍͖ͯ͠·͠ΐ͏ʂ 31 3FTU7FSC 63* "DUJPO ϝιου 1SFpY

    ΤΠϦΞε ϚΠχϯά HFU NJOJOH NJOJOH  ϒϩοΫͷ ৘ใ HFU CMPDLJE CMPDLJOGP CMPDLJOGP ૹۚೖྗ TFOEJOHT TFOEJOHT TFOEJOHT ૹۚॲཧ QPTU TFOU TFOU
  8. ίϛϡχςΟ঺հ ೔ຊ҉߸௨՟ϢʔβձɹIUUQTDSZQUPDVSSFODZDPOOQBTTDPN ʮϏοτίΠϯͱ͔ษڧձʯʢ݄̍ճɿฏ೔໷ʣ w ࣍ճ͸೥݄೔ ໦ ͰʮϞφίΠϯͱ͔ษڧձʯௐ੔த ʮ҉߸௨՟ಡॻձʯʢ݄̍ճɿฏ೔໷ʣ w ࣍ճ͸೥݄೔

    Ր ʮ"CBMBODIFʯ DSBTIBDBEFNZ *5ܥಈը഑৴ ʹͯϏοτίΠϯͱ͔ษڧձͷΞʔΧΠϒ഑৴த
 IUUQTDSBTIBDBEFNZDPNNVOJUZDSZQUPDVSSFODZ :PV5VCFνϟϯωϧʢษڧձʗಡॻձʣIUUQCJUMZZPVUVCFDDTUVEZ 32
  9. .BDʹ3BJMTΛΠϯετʔϧ DE OBOPHFNSD ԼهΛίϐϖ $USM PͰॻ͖ࠐΈ $USM YͰด͡Δ HFNJOTUBMMCVOEMFS CVOEMFW

    HFNJOTUBMMSBJMTWFSTJPOdz SBJMTW 3BJMT 35 install: --no-ri --no-rdoc update: --no-ri --no-rdoc
  10. 3BJMTىಈ FYJU WBHSBOUఀࢭ WBHSBOUIBMU OBOP7BHSBOUpMF DPOpHWNOFUXPSLQSJWBUF@OFUXPSL JQlz
 ͷίϝϯτΞ΢τ  Λ֎͢

    WBHSBOUVQ WBHSBOUTTI DEWBHSBOUSBJMTCJUDPJO ZBSOJOTUBMMDIFDLpMFT SBJMTTC IUUQ 44
  11. 3FHUFTUϞʔυ 5FTUOFU͸ແՁ஋ͳ#JUDPJOͷྲྀ௨͢ΔωοτϫʔΫ 1P8 1SPPGPG8PSL ܕͰϚΠχϯά΋͍ͯ͠Δ w ϒϩοΫੜ੒ִؒฏۉ෼͕ͩɺ҆ఆ͠ͳ͍ w ٸܹʹૣ͘ͳͬͨ൓ಈͰ஗͘ͳͬͨΓ 3FHUFTU͸։ൃ༻ͷωοτϫʔΫ

    ೚ҙͷλΠϛϯάͰϚΠχϯά͠์୊ʢίετ̌ʣ w ঝೝͱ͍͏ςετ΋͙͢ʹͰ͖Δ ωοτͷͳ͍؀ڥͰ΋࢖͑Δ ෳ਺୆ܨ͛Δ͜ͱ΋Մೳ w 3FPSHͷςετ΋؆୯ʹͰ͖Δ w #MPDLXJUIIPMEJOHBUUBDL ηϧϑΟογϡɾϚΠχϯά߈ܸ ΋؆୯ 50
  12. ΞϦε͕Ϙϒʹ#5$ΛૹΔ Ҏલड͚औֹ͍ͬͯͨΛ߹ࢉͯ͠ɺ૬खʹૹΓɺ͓௼Γ͸ࣗ෼΁ 59தʹهࡌͷͳֹ͍ ͜͜Ͱ͸#5$ ͸ޙʹϚΠφʔ΁ τϥϯβΫγϣϯͷߏ଄ 53 ΞϦε Ϙϒ 5SBOTBDUJPO

    59 */165 065165 ΞϦε Ωϟϩϧ ΞϦεˠϘϒ #5$ ΩϟϩϧˠΞϦε #5$ ΞϦεˠΞϦε ͓௼Γ  #5$ #5$ σϏοτˠΞϦε #5$ #5$ σϏοτ ܭ#5$ ܭ#5$ ϚΠφʔ ωοτϫʔΫख਺ྉ #5$ ˞59ʹهࡌ͸ͳ͍ ൿີ伴Λࠩ͠ࠐΜͰ ϩοΫղআɿిࢠॺ໊ ΞϦεͷ ൿີ伴 ΞϦεͷ ൿີ伴 ΞϦεͷ ެ։伴 Ϙϒͷ ެ։伴 ΞϦεͷ ެ։伴 ΞϦεͷ ެ։伴
  13. .OFNPOJD #JUDPJO8BMMFU.BTUFS,FZHFOFSBUF 58 irb(main):035:0> mk = Bitcoin::Wallet::MasterKey.generate => #<Bitcoin::Wallet::MasterKey:0x00000000019d8d10 @mnemonic=["shed",

    "horror", "bleak", "destroy", "shield", "salute", "dose", "blast", "aim", "orphan", "crawl", "model", "salon", "fall", "amount", "repair", "unfold", "craft", "wall", "mean", "strong", "obtain", "holiday", "couple"], @seed="2a462344bbeafbc81996bd3bb93beb3eb0db9e1826de4dfab165e2d6714bf37f8a4ade6195579ae686365dd467e8687791 9bca0740f58eaa1beb393bb557471e", @encrypted=false, @salt=""> irb(main):036:0> mk = Bitcoin::Wallet::MasterKey.generate.mnemonic => ["rocket", "habit", "slam", "pioneer", "ethics", "client", "outer", "name", "alarm", "bulb", "visa", "special", "index", "shadow", "tube", "bronze", "focus", "skin", "deposit", "climb", "tattoo", "chimney", "anger", "daring"]
  14. #JUDPJO4DSJQU -*'0 -BTU*O'JSTU0VU ͷ୯७ͳεΫϦϓτ 'PSUIͷΑ͏ͳٯϙʔϥϯυه๏ #JUDPJO5SBOTBDUJPOʹ͸ඞؚͣ·ΕΔ #JUDPJO$PSF CJUDPJOE ΍ϥΠϒϥϦΛ࢖͏ͱɺ#JUDPJO4DSJQUΛҙࣝ͠ͳ ͯ͘΋ɺϓϩάϥϜ͸ॻ͚Δ

    6OMPDLJOH4DSJQUͱ-PDLJOH4DSJQUΛಉ࣌ʹ࣮ߦ ਖ਼࣮͘͠ߦͰ͖Ε͹6OMPDLJOH4DSJQUͰ-PDLJOH4DSJQUΛղআͯ͠ɺτϥϯ βΫγϣϯͷ༗ޮੑΛࣔͤΔ 59 6OMPDLJOH4DSJQU -PDLJOH4DSJQU
  15. #JUDPJO4DSJQU TDSJQU#JUDPJO4DSJQUGSPN@TUSJOH 01@"%%01@&26"-z  TDSJQUSVO TDSJQU#JUDPJO4DSJQUGSPN@TUSJOH 01@"%%01@&26"-z  TDSJQUSVO 61

    irb(main):017:0> script = Bitcoin::Script.from_string("2 4 OP_ADD 6 OP_EQUAL").run => true irb(main):018:0> script = Bitcoin::Script.from_string("2 3 OP_ADD 6 OP_EQUAL").run => false irb(main):019:0> script = Bitcoin::Script.from_string("2 4 OP_ADD 6 OP_EQUAL") => #<Bitcoin::Script:0x0000000001be1c10 @chunks=["R", "T", "\x93", "V", "\x87"]> irb(main):020:0> script.run => true
  16. ࢀߟʣ11,) 1BZUP1VCMJD,FZ)BTIʢ11,ͷൃల൛ʣ ެ։伴ϋογϡΛ༻͍Δ͜ͱͰΞυϨεΛ୹͘දݱͰ͖Δ TDSJQU͸ʢʣ಺Ͱͷݺͼํ΋͋Δ 63 -PDLJOH4DSJQU TDSJQU1VC,FZ 6OMPDLJOH4DSJQU TDSJQU4JH TJH

    TJH1VC,01@%6101@)"4)1VC,)BTI01@&26"-7&3*': 01@$)&$,4*(Λ࣮ߦ TJH 536& 1VC, TJH 1VC, TJH 1VC, 1VC, 1VC, 1VC,)BTI TJH 1VC, 1VC,)BTI 1VC,)BTI TJH 1VC, 1VC,)BTI 1VC,)BTI TJH 1VC, 01@%61 )"4) ෳ੡ 01@&26"- ౳͍͠ TJH1VC, 01@%6101@)"4)1VC,)BTI 01@&26"-7&3*':01@$)&$,4*( ಉ஋͔ 4)"ͷޙ 3*1&.% ෳ੡
  17. ࢀߟʣ11,)ʢଓ͖ʣ ࢒Γ͸11,ͱಉ͡ 64 -PDLJOH4DSJQU 6OMPDLJOH4DSJQU TJH 1VC, TJH 1VC, 

    536& $)&$,4*( TJH1VC, 01@%6101@)"4)1VC,)BTI 01@&26"-7&3*':01@$)&$,4*( ॺ໊Λݕূ
  18. ࢀߟʣϚϧνγά NPGOʢOਓதNਓͷॺ໊ͰϩοΫղআͰ͖ΔʣϚϧνγά Α͋͘ΔPGͷྫ 65 -PDLJOH4DSJQU TDSJQU1VC,FZ 6OMPDLJOH4DSJQU TDSJQU4JH 01@TJH"4JH#1VC,"1VC,#1VC,$01@$)&$,.6-5*4*( )"4)

    01@TJH"TJH# 1VC,"1VC,#1VC,$ 01@$)&$,.6-5*4*( 536&  TJHOBUVSF#  1VCMJD,FZ$  $)&$,.6-5*4*( TJHOBUVSF" 1VCMJD,FZ# 1VCMJD,FZ" 
  19. CJUDPJOSC࢖༻ JSC ԼهͷίʔυΛෳ਺ߦؙ͝ͱίϐϖ 66 irb(main):001:0> require 'bitcoin' => true irb(main):002:0>

    require 'net/http' => true irb(main):003:0> require 'json' => false irb(main):004:0> Bitcoin.chain_params = :regtest => :regtest irb(main):005:0> RPCUSER="hoge" => "hoge" irb(main):006:0> RPCPASSWORD="hoge" => "hoge" irb(main):007:0> HOST="localhost" => "localhost" irb(main):008:0> PORT=18332 => 18332 irb(main):011:0> def bitcoinRPC(method,param) irb(main):012:1> http = Net::HTTP.new(HOST, PORT) irb(main):013:1> request = Net::HTTP::Post.new('/') irb(main):014:1> request.basic_auth(RPCUSER,RPCPASSWORD) irb(main):015:1> request.content_type = 'application/json' irb(main):016:1> request.body = {method: method, params: param, id: 'jsonrpc'}.to_json irb(main):017:1> JSON.parse(http.request(request).body)["result"] irb(main):018:1> end => :bitcoinRPC require 'bitcoin' require 'net/http' require 'json' Bitcoin.chain_params = :regtest RPCUSER="hoge" RPCPASSWORD="hoge" HOST="localhost" PORT=18332 def bitcoinRPC(method,param) http = Net::HTTP.new(HOST, PORT) request = Net::HTTP::Post.new('/') request.basic_auth(RPCUSER,RPCPASSWORD) request.content_type = 'application/json' request.body = {method: method, params: param, id: 'jsonrpc'}.to_json JSON.parse(http.request(request).body)["result"] end
  20. CJUDPJO31$Ͱ#JUDPJO$PSFΛૢ࡞ JSC NBJO CJUDPJO31$ bHFUCMPDLDIBJOJOGP` <>  BEESFTTCJUDPJO31$ bHFUOFXBEESFTT <>

     CJUDPJO31$ HFOFSBUFUPBEESFTT < BEESFTT> 67 irb(main):019:0> bitcoinRPC('getblockchaininfo',[]) => {"chain"=>"regtest", "blocks"=>259, "headers"=>259, "bestblockhash"=>"537f9822f97e2110fa8d04d7676855eef5c7628c425a2a2e271a49d6a0741480", "difficulty"=>4.656542373906925e-10, "mediantime"=>1574683242, "verificationprogress"=>1, "initialblockdownload"=>true, "chainwork"=>"0000000000000000000000000000000000000000000000000000000000000208", "size_on_disk"=>108511, "pruned"=>false, "softforks"=>{"bip34"=>{"type"=>"buried", "active"=>false, "height"=>500}, "bip66"=>{"type"=>"buried", "active"=>false, "height"=>1251}, "bip65"=>{"type"=>"buried", "active"=>false, "height"=>1351}, "csv"=>{"type"=>"buried", "active"=>false, "height"=>432}, "segwit"=>{"type"=>"buried", "active"=>true, "height"=>0}, "testdummy"=>{"type"=>"bip9", "bip9"=>{"status"=>"started", "bit"=>28, "start_time"=>0, "timeout"=>9223372036854775807, "since"=>144, "statistics"=>{"period"=>144, "threshold"=>108, "elapsed"=>116, "count"=>116, "possible"=>true}}, "active"=>false}}, "warnings"=>""}