Commit dec22a912c13d6f91bb6578f98965f882889ab68

Authored by Marius Hanne
1 parent c578e45775

documentation for Bitcoin::Builder

Showing 2 changed files with 198 additions and 140 deletions Side-by-side Diff

... ... @@ -10,7 +10,7 @@
10 10 * load the blockchain from the network and stay in sync (WITHOUT verification yet!)
11 11 * store and the blockchain and query for txouts (Bitcoin::Storage)
12 12 * script implementation, create/run scripts and verify signatures (Bitcoin::Script)
13   -* create transactions (and even blocks!) (Bitcoin::Builder)
  13 +* create transactions (and even blocks!) (Bitcoin::Protocol, Bitcoin::Builder)
14 14 * manage keys (Bitcoin::Key) in a wallet (Bitcoin::Wallet, WALLET)
15 15 * there is even a highly experimental(!) Bitcoin::Gui
16 16  
lib/bitcoin/builder.rb
1 1 module Bitcoin
2 2  
  3 + # Optional DSL to help create blocks and transactions.
  4 + #
  5 + # see also BlockBuilder, TxBuilder, TxInBuilder, TxOutBuilder, ScriptBuilder
3 6 module Builder
4 7  
  8 + # create a Bitcoin::Protocol::Block matching the given +target+.
  9 + # see BlockBuilder for details.
5 10 def blk(target = "00".ljust(32, 'f'), &block)
6 11 c = BlockBuilder.new
7 12 c.instance_eval &block
8 13 c.block(target)
9 14 end
10 15  
  16 + # create a Bitcoin::Protocol::Tx.
  17 + # see TxBuilder for details.
11 18 def tx &block
12 19 c = TxBuilder.new
13 20 c.instance_eval &block
14 21 c.tx
15 22 end
16 23  
17   - end
  24 + # DSL to create a Bitcoin::Protocol::Block used by Builder#blk.
  25 + # block = blk("00".ljust(32, 'f')) do
  26 + # prev_block "\x00"*32
  27 + # tx do
  28 + # input { coinbase }
  29 + # output do
  30 + # value 5000000000;
  31 + # script { type :address; recipient Bitcoin::Key.generate.addr }
  32 + # end
  33 + # end
  34 + # end
  35 + class BlockBuilder
18 36  
  37 + def initialize
  38 + @block = Bitcoin::P::Block.new(nil)
  39 + end
19 40  
20   - class BlockBuilder
  41 + # specify block version. this is usually not necessary. defaults to 1.
  42 + def version v
  43 + @version = v
  44 + end
21 45  
22   - def initialize
23   - @block = Bitcoin::P::Block.new(nil)
24   - end
  46 + # set the hash of the previous block.
  47 + def prev_block hash
  48 + @prev_block = hash
  49 + end
25 50  
26   - def version v
27   - @version = v
28   - end
  51 + # add transactions to the block (see TxBuilder).
  52 + def tx &block
  53 + c = TxBuilder.new
  54 + c.instance_eval &block
  55 + @block.tx << c.tx
  56 + end
29 57  
30   - def prev_block hash
31   - @prev_block = hash
32   - end
  58 + # create the block according to values specified via DSL.
  59 + def block target
  60 + @block.ver = @version || 1
  61 + @block.prev_block = [@prev_block].pack("H*").reverse
  62 + @block.mrkl_root = @mrkl_root
  63 + @block.time = Time.now.to_i
  64 + @block.nonce = 0
  65 + @block.mrkl_root = [Bitcoin.hash_mrkl_tree(@block.tx.map {|t|
  66 + t.hash }).last].pack("H*")
  67 + find_hash(target)
  68 + Bitcoin::P::Block.new(@block.to_payload)
  69 + end
33 70  
34   - def tx &block
35   - c = TxBuilder.new
36   - c.instance_eval &block
37   - @block.tx << c.tx
38   - end
  71 + private
39 72  
40   - def block target
41   - @block.ver = @version || 1
42   - @block.prev_block = [@prev_block].pack("H*").reverse
43   - @block.mrkl_root = @mrkl_root
44   - @block.time = Time.now.to_i
45   - @block.nonce = 0
46   - @block.mrkl_root = [Bitcoin.hash_mrkl_tree(@block.tx.map {|t|
47   - t.hash }).last].pack("H*")
48   - find_hash(target)
49   - Bitcoin::P::Block.new(@block.to_payload)
50   - end
51   -
52   - def find_hash target
53   - @block.bits = Bitcoin.encode_compact_bits(target)
54   - t = Time.now
55   - @block.recalc_block_hash
56   - until @block.hash < target
57   - @block.nonce += 1
  73 + # increment nonce/time to find a block hash matching the +target+.
  74 + def find_hash target
  75 + @block.bits = Bitcoin.encode_compact_bits(target)
  76 + t = Time.now
58 77 @block.recalc_block_hash
59   - if @block.nonce == 100000
60   - if t
61   - tt = 1 / ((Time.now - t) / 100000) / 1000
62   - print "\r%.2f khash/s" % tt
  78 + until @block.hash < target
  79 + @block.nonce += 1
  80 + @block.recalc_block_hash
  81 + if @block.nonce == 100000
  82 + if t
  83 + tt = 1 / ((Time.now - t) / 100000) / 1000
  84 + print "\r%.2f khash/s" % tt
  85 + end
  86 + t = Time.now
  87 + @block.time = Time.now.to_i
  88 + @block.nonce = 0
  89 + $stdout.flush
63 90 end
64   - t = Time.now
65   - @block.time = Time.now.to_i
66   - @block.nonce = 0
67   - $stdout.flush
68 91 end
69 92 end
  93 +
70 94 end
71 95  
72   - end
  96 + # DSL to create Bitcoin::Protocol::Tx used by Builder#tx.
  97 + # tx = tx do
  98 + # input do
  99 + # prev_out prev_tx # previous transaction
  100 + # prev_out_index 0 # index of previous output
  101 + # signature_key key # Bitcoin::Key used to sign the input
  102 + # end
  103 + # output do
  104 + # value 12345 # 0.00012345 BTC
  105 + # script { type :address; recipient key.addr }
  106 + # end
  107 + # end
  108 + class TxBuilder
73 109  
74   - class TxBuilder
  110 + def initialize
  111 + @tx = Bitcoin::P::Tx.new(nil)
  112 + @tx.ver, @tx.lock_time = 1, 0
  113 + @ins, @outs = [], []
  114 + end
75 115  
76   - def initialize
77   - @tx = Bitcoin::P::Tx.new(nil)
78   - @tx.ver, @tx.lock_time = 1, 0
79   - @ins, @outs = [], []
80   - end
  116 + # specify tx version. this is usually not necessary. defaults to 1.
  117 + def version n
  118 + @tx.ver = n
  119 + end
81 120  
82   - def version n
83   - @tx.ver = n
84   - end
  121 + # specify tx lock_time. this is usually not necessary. defaults to 0.
  122 + def lock_time n
  123 + @tx.lock_time = n
  124 + end
85 125  
86   - def lock_time n
87   - @tx.lock_time = n
88   - end
  126 + # add an input to the transaction (see TxInBuilder).
  127 + def input &block
  128 + c = TxInBuilder.new
  129 + c.instance_eval &block
  130 + @ins << c
  131 + end
89 132  
90   - def input &block
91   - c = TxInBuilder.new
92   - c.instance_eval &block
93   - @ins << c
94   - end
  133 + # add an output to the transaction (see TxOutBuilder).
  134 + def output &block
  135 + c = TxOutBuilder.new
  136 + c.instance_eval &block
  137 + @outs << c
  138 + end
95 139  
96   - def output &block
97   - c = TxOutBuilder.new
98   - c.instance_eval &block
99   - @outs << c
100   - end
101   -
102   - def tx
103   - @ins.each {|i| @tx.add_in(i.txin) }
104   - @outs.each {|o| @tx.add_out(o.txout) }
105   - @ins.each_with_index do |inc, i|
106   - if @tx.in[i].coinbase?
107   - script_sig = [inc.coinbase_data].pack("H*")
  140 + # create the transaction according to values specified via DSL and sign inputs.
  141 + def tx
  142 + @ins.each {|i| @tx.add_in(i.txin) }
  143 + @outs.each {|o| @tx.add_out(o.txout) }
  144 + @ins.each_with_index do |inc, i|
  145 + if @tx.in[i].coinbase?
  146 + script_sig = [inc.coinbase_data].pack("H*")
  147 + @tx.in[i].script_sig_length = script_sig.bytesize
  148 + @tx.in[i].script_sig = script_sig
  149 + next
  150 + end
  151 + prev_tx = inc.instance_variable_get(:@prev_out)
  152 + sig_hash = @tx.signature_hash_for_input(i, prev_tx)
  153 + sig = inc.key.sign(sig_hash)
  154 + script_sig = Bitcoin::Script.to_signature_pubkey_script(sig, [inc.key.pub].pack("H*"))
108 155 @tx.in[i].script_sig_length = script_sig.bytesize
109 156 @tx.in[i].script_sig = script_sig
110   - next
  157 + raise "Signature error" unless @tx.verify_input_signature(i, prev_tx)
111 158 end
112   - prev_tx = inc.instance_variable_get(:@prev_out)
113   - sig_hash = @tx.signature_hash_for_input(i, prev_tx)
114   - sig = inc.key.sign(sig_hash)
115   - script_sig = Bitcoin::Script.to_signature_pubkey_script(sig, [inc.key.pub].pack("H*"))
116   - @tx.in[i].script_sig_length = script_sig.bytesize
117   - @tx.in[i].script_sig = script_sig
118   - raise "Signature error" unless @tx.verify_input_signature(i, prev_tx)
  159 + Bitcoin::P::Tx.new(@tx.to_payload)
119 160 end
120   - Bitcoin::P::Tx.new(@tx.to_payload)
121 161 end
122   - end
123 162  
124   - class TxInBuilder
125   - attr_reader :key, :coinbase_data
  163 + # create a Bitcoin::Protocol::TxIn used by TxBuilder#input.
  164 + #
  165 + # inputs can be either 'coinbase', in which case they only need to specify #coinbase,
  166 + # or they have to define a #prev_out, #prev_out_index and #signature key.
  167 + class TxInBuilder
  168 + attr_reader :key, :coinbase_data
126 169  
127   - def initialize
128   - @txin = Bitcoin::P::TxIn.new
129   - end
  170 + def initialize
  171 + @txin = Bitcoin::P::TxIn.new
  172 + end
130 173  
131   - def prev_out tx
132   - @prev_out = tx
133   - end
  174 + # previous transaction that contains the output we want to use.
  175 + def prev_out tx
  176 + @prev_out = tx
  177 + end
134 178  
135   - def prev_out_index i
136   - @prev_out_index = i
137   - end
  179 + # index of the output in the #prev_out transaction.
  180 + def prev_out_index i
  181 + @prev_out_index = i
  182 + end
138 183  
139   - def sequence s
140   - @sequence = s
141   - end
  184 + # specify sequence. this is usually not needed.
  185 + def sequence s
  186 + @sequence = s
  187 + end
142 188  
143   - def signature_key key
144   - @key = key
145   - end
  189 + # Bitcoin::Key used to sign the signature_hash for the input.
  190 + # see Bitcoin::Script.signature_hash_for_input and Bitcoin::Key.sign.
  191 + def signature_key key
  192 + @key = key
  193 + end
146 194  
147   - def coinbase data = nil
148   - @coinbase_data = data || OpenSSL::Random.random_bytes(32)
149   - @prev_out = nil
150   - @prev_out_index = 4294967295
151   - end
  195 + # specify that this is a coinbase input. optionally set +data+.
  196 + def coinbase data = nil
  197 + @coinbase_data = data || OpenSSL::Random.random_bytes(32)
  198 + @prev_out = nil
  199 + @prev_out_index = 4294967295
  200 + end
152 201  
153   - def txin
154   - @txin.prev_out = (@prev_out ? [@prev_out.hash].pack("H*").reverse : "\x00"*32)
155   - @txin.prev_out_index = @prev_out_index
156   - @txin.sequence = @sequence || "\xff\xff\xff\xff"
157   - @txin
  202 + # create the txin according to values specified via DSL
  203 + def txin
  204 + @txin.prev_out = (@prev_out ? [@prev_out.hash].pack("H*").reverse : "\x00"*32)
  205 + @txin.prev_out_index = @prev_out_index
  206 + @txin.sequence = @sequence || "\xff\xff\xff\xff"
  207 + @txin
  208 + end
158 209 end
159   - end
160 210  
161   - class ScriptBuilder
162   - attr_reader :script
  211 + # create a Bitcoin::Script used by TxOutBuilder#script.
  212 + class ScriptBuilder
  213 + attr_reader :script
163 214  
164   - def initialize
165   - @type = nil
166   - @script = nil
167   - end
  215 + def initialize
  216 + @type = nil
  217 + @script = nil
  218 + end
168 219  
169   - def type type
170   - @type = type.to_sym
171   - end
  220 + # script type (:pubkey, :address, :hash160, :multisig).
  221 + def type type
  222 + @type = type.to_sym
  223 + end
172 224  
173   - def recipient data
174   - @script = Bitcoin::Script.send("to_#{@type}_script", data)
  225 + # recipient(s) of the script.
  226 + # depending on the #type, either an address, hash160 pubkey, etc.
  227 + def recipient data
  228 + @script = Bitcoin::Script.send("to_#{@type}_script", data)
  229 + end
175 230 end
176   - end
177 231  
178   - class TxOutBuilder
179   - attr_reader :txout
  232 + # create a Bitcoin::Protocol::TxOut used by TxBuilder#output.
  233 + class TxOutBuilder
  234 + attr_reader :txout
180 235  
181   - def initialize
182   - @txout = Bitcoin::P::TxOut.new
183   - end
  236 + def initialize
  237 + @txout = Bitcoin::P::TxOut.new
  238 + end
184 239  
185   - def value value
186   - @txout.value = value
187   - end
  240 + # set output value (in base units / "satoshis")
  241 + def value value
  242 + @txout.value = value
  243 + end
188 244  
189   - def script &block
190   - c = ScriptBuilder.new
191   - c.instance_eval &block
192   - @txout.pk_script = c.script
193   - end
  245 + # add a script to the output (see ScriptBuilder).
  246 + def script &block
  247 + c = ScriptBuilder.new
  248 + c.instance_eval &block
  249 + @txout.pk_script = c.script
  250 + end
194 251  
  252 + end
  253 +
195 254 end
196   -
197 255 end