bitcoin-ruby 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -1
- data/Gemfile +21 -0
- data/README.rdoc +85 -25
- data/Rakefile +7 -3
- data/bin/bitcoin_node +39 -42
- data/bin/bitcoin_shell +1 -0
- data/bin/bitcoin_wallet +129 -53
- data/bitcoin-ruby.gemspec +4 -7
- data/concept-examples/blockchain-pow.rb +1 -1
- data/doc/CONFIG.rdoc +5 -5
- data/doc/EXAMPLES.rdoc +9 -5
- data/doc/NAMECOIN.rdoc +34 -0
- data/doc/NODE.rdoc +147 -10
- data/examples/balance.rb +10 -4
- data/examples/bbe_verify_tx.rb +7 -2
- data/examples/forwarder.rb +73 -0
- data/examples/generate_tx.rb +34 -0
- data/examples/simple_network_monitor_and_util.rb +187 -0
- data/examples/verify_tx.rb +1 -1
- data/lib/bitcoin.rb +308 -18
- data/lib/bitcoin/builder.rb +62 -36
- data/lib/bitcoin/config.rb +2 -0
- data/lib/bitcoin/connection.rb +11 -8
- data/lib/bitcoin/electrum/mnemonic.rb +162 -0
- data/lib/bitcoin/ffi/openssl.rb +187 -21
- data/lib/bitcoin/gui/addr_view.rb +2 -0
- data/lib/bitcoin/gui/conn_view.rb +2 -0
- data/lib/bitcoin/gui/connection.rb +2 -0
- data/lib/bitcoin/gui/em_gtk.rb +2 -0
- data/lib/bitcoin/gui/gui.rb +2 -0
- data/lib/bitcoin/gui/helpers.rb +2 -0
- data/lib/bitcoin/gui/tree_view.rb +2 -0
- data/lib/bitcoin/gui/tx_view.rb +2 -0
- data/lib/bitcoin/key.rb +77 -11
- data/lib/bitcoin/litecoin.rb +81 -0
- data/lib/bitcoin/logger.rb +20 -1
- data/lib/bitcoin/namecoin.rb +279 -0
- data/lib/bitcoin/network/command_client.rb +7 -6
- data/lib/bitcoin/network/command_handler.rb +229 -43
- data/lib/bitcoin/network/connection_handler.rb +182 -70
- data/lib/bitcoin/network/node.rb +231 -106
- data/lib/bitcoin/protocol.rb +44 -23
- data/lib/bitcoin/protocol/address.rb +5 -3
- data/lib/bitcoin/protocol/alert.rb +3 -4
- data/lib/bitcoin/protocol/aux_pow.rb +123 -0
- data/lib/bitcoin/protocol/block.rb +98 -18
- data/lib/bitcoin/protocol/handler.rb +6 -5
- data/lib/bitcoin/protocol/parser.rb +44 -19
- data/lib/bitcoin/protocol/tx.rb +105 -52
- data/lib/bitcoin/protocol/txin.rb +39 -19
- data/lib/bitcoin/protocol/txout.rb +28 -13
- data/lib/bitcoin/protocol/version.rb +16 -7
- data/lib/bitcoin/script.rb +579 -122
- data/lib/bitcoin/storage/{dummy.rb → dummy/dummy_store.rb} +8 -14
- data/lib/bitcoin/storage/models.rb +20 -7
- data/lib/bitcoin/storage/{sequel_store/sequel_migrations.rb → sequel/migrations.rb} +22 -7
- data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +52 -0
- data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +50 -0
- data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +18 -0
- data/lib/bitcoin/storage/sequel/sequel_store.rb +436 -0
- data/lib/bitcoin/storage/storage.rb +233 -28
- data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +52 -0
- data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +18 -0
- data/lib/bitcoin/storage/utxo/utxo_store.rb +361 -0
- data/lib/bitcoin/validation.rb +369 -0
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/coinselector.rb +3 -0
- data/lib/bitcoin/wallet/keygenerator.rb +3 -1
- data/lib/bitcoin/wallet/keystore.rb +6 -2
- data/lib/bitcoin/wallet/txdp.rb +6 -4
- data/lib/bitcoin/wallet/wallet.rb +54 -16
- data/spec/bitcoin/bitcoin_spec.rb +48 -3
- data/spec/bitcoin/builder_spec.rb +40 -17
- data/spec/bitcoin/fixtures/000000000000056b1a3d84a1e2b33cde8915a4b61c0cae14fca6d3e1490b4f98.json +3697 -0
- data/spec/bitcoin/fixtures/03d7e1fa4d5fefa169431f24f7798552861b255cd55d377066fedcd088fb0e99.json +23 -0
- data/spec/bitcoin/fixtures/0961c660358478829505e16a1f028757e54b5bbf9758341a7546573738f31429.json +24 -0
- data/spec/bitcoin/fixtures/0f24294a1d23efbb49c1765cf443fba7930702752aba6d765870082fe4f13cae.json +37 -0
- data/spec/bitcoin/fixtures/315ac7d4c26d69668129cc352851d9389b4a6868f1509c6c8b66bead11e2619f.json +31 -0
- data/spec/bitcoin/fixtures/35e2001b428891fefa0bfb73167c7360669d3cbd7b3aa78e7cad125ddfc51131.json +27 -0
- data/spec/bitcoin/fixtures/3a17dace09ffb919ed627a93f1873220f4c975c1248558b18d16bce25d38c4b7.json +72 -0
- data/spec/bitcoin/fixtures/3e58b7eed0fdb599019af08578effea25c8666bbe8e200845453cacce6314477.json +27 -0
- data/spec/bitcoin/fixtures/514c46f0b61714092f15c8dfcb576c9f79b3f959989b98de3944b19d98832b58.json +24 -0
- data/spec/bitcoin/fixtures/51bf528ecf3c161e7c021224197dbe84f9a8564212f6207baa014c01a1668e1e.json +30 -0
- data/spec/bitcoin/fixtures/69216b8aaa35b76d6613e5f527f4858640d986e1046238583bdad79b35e938dc.json +28 -0
- data/spec/bitcoin/fixtures/7208e5edf525f04e705fb3390194e316205b8f995c8c9fcd8c6093abe04fa27d.json +27 -0
- data/spec/bitcoin/fixtures/761d8c5210fdfd505f6dff38f740ae3728eb93d7d0971fb433f685d40a4c04f6.json +27 -0
- data/spec/bitcoin/fixtures/aea682d68a3ea5e3583e088dcbd699a5d44d4b083f02ad0aaf2598fe1fa4dfd4.json +27 -0
- data/spec/bitcoin/fixtures/bd1715f1abfdc62bea3f605bdb461b3ba1f2cca6ec0d73a18a548b7717ca8531.json +34 -0
- data/spec/bitcoin/fixtures/block-testnet-0000000000ac85bb2530a05a4214a387e6be02b22d3348abc5e7a5d9c4ce8dab.bin +0 -0
- data/spec/bitcoin/fixtures/cd874fa8cb0e2ec2d385735d5e1fd482c4fe648533efb4c50ee53bda58e15ae2.json +24 -0
- data/spec/bitcoin/fixtures/ce5fad9b4ef094d8f4937b0707edaf0a6e6ceeaf67d5edbfd51f660eac8f398b.json +41 -0
- data/spec/bitcoin/fixtures/f003f0c1193019db2497a675fd05d9f2edddf9b67c59e677c48d3dbd4ed5f00b.json +23 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +43 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +67 -0
- data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.bin +0 -0
- data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.json +39 -0
- data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.bin +0 -0
- data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-auxpow.bin +0 -0
- data/spec/bitcoin/fixtures/tx-313897799b1e37e9ecae15010e56156dddde4e683c96b0e713af95272c38aee0.json +30 -0
- data/spec/bitcoin/fixtures/tx-3da75972766f0ad13319b0b461fd16823a731e44f6e9de4eb3c52d6a6fb6c8ae.json +23 -0
- data/spec/bitcoin/fixtures/tx-44b833074e671120ba33106877b49e86ece510824b9af477a3853972bcd8d06a.json +30 -0
- data/spec/bitcoin/fixtures/tx-d3d77d63709e47d9ef58f0b557800115a6b676c6a423012fbb96f45d8fcef830.json +28 -0
- data/spec/bitcoin/key_spec.rb +128 -3
- data/spec/bitcoin/namecoin_spec.rb +182 -0
- data/spec/bitcoin/network_spec.rb +5 -3
- data/spec/bitcoin/node/command_api_spec.rb +376 -0
- data/spec/bitcoin/protocol/addr_spec.rb +2 -0
- data/spec/bitcoin/protocol/alert_spec.rb +2 -0
- data/spec/bitcoin/protocol/aux_pow_spec.rb +44 -0
- data/spec/bitcoin/protocol/block_spec.rb +134 -39
- data/spec/bitcoin/protocol/getblocks_spec.rb +32 -0
- data/spec/bitcoin/protocol/inv_spec.rb +10 -8
- data/spec/bitcoin/protocol/notfound_spec.rb +31 -0
- data/spec/bitcoin/protocol/ping_spec.rb +2 -0
- data/spec/bitcoin/protocol/tx_spec.rb +83 -17
- data/spec/bitcoin/protocol/version_spec.rb +7 -5
- data/spec/bitcoin/script/opcodes_spec.rb +412 -133
- data/spec/bitcoin/script/script_spec.rb +112 -13
- data/spec/bitcoin/spec_helper.rb +68 -0
- data/spec/bitcoin/storage/reorg_spec.rb +199 -0
- data/spec/bitcoin/storage/storage_spec.rb +337 -0
- data/spec/bitcoin/storage/validation_spec.rb +261 -0
- data/spec/bitcoin/wallet/coinselector_spec.rb +10 -7
- data/spec/bitcoin/wallet/keygenerator_spec.rb +2 -0
- data/spec/bitcoin/wallet/keystore_spec.rb +2 -0
- data/spec/bitcoin/wallet/txdp_spec.rb +2 -0
- data/spec/bitcoin/wallet/wallet_spec.rb +91 -58
- metadata +105 -51
- data/lib/bitcoin/storage/sequel.rb +0 -335
- data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +0 -27
- data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +0 -27
- data/spec/bitcoin/reorg_spec.rb +0 -129
- data/spec/bitcoin/storage_spec.rb +0 -229
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
3
|
+
require_relative 'spec_helper.rb'
|
|
4
|
+
require 'bitcoin/script'
|
|
5
|
+
|
|
6
|
+
include Bitcoin
|
|
7
|
+
include Bitcoin::Builder
|
|
8
|
+
|
|
9
|
+
describe 'Bitcoin::Namecoin' do
|
|
10
|
+
|
|
11
|
+
describe :script do
|
|
12
|
+
|
|
13
|
+
before do
|
|
14
|
+
Bitcoin.network = :namecoin
|
|
15
|
+
@name_new = Script.from_string("3045022023686b3584247c07f483de4048f3d5136c4faa2f961a6d1e487eb77437422b51022100b1ea62910f2dbb0533d32bd661e8a212d129057d3d71620572278895dbb5c7b501
|
|
16
|
+
04b656d7be83e73344e298feba41b38c52ea50d4583ead0c947fd8019f75c906e0d810c5d167ee616c46d28d7cb5ca1d7a20a180470c9dad79524118bafe6cf569 1 820fa9c6d252d6773e4ef26a2feffa93f0237641 OP_2DROP OP_DUP OP_HASH160 eb86f8f23909e248d199192d8407881c3435a5db OP_EQUALVERIFY OP_CHECKSIG")
|
|
17
|
+
@name_firstupdate = Script.from_string("3044022054e1557304c504498f8d40b961373d7401a4e4c1650518db8a9c3a49ee9add7e022061573b2837598c346b21c01969081a309c055d5da23b0ac7e139a6290f2f0a4b01
|
|
18
|
+
04d2628245cdfc6ccf5a762d303ba8a10bd54d597cfdcbf0ac3823bae666a36bb744ed44a2384f42525e37b5b1150c5321718806c0f941904336588dd624f4ce93 2 642f626974636f696e a8c22832fb0d40e900 7b22696e666f223a7b22726567697374726172223a22687474703a2f2f72656769737465722e646f742d6269742e6f7267227d2c22656d61696c223a2022726567697374657240646f742d6269742e6f7267222c226e73223a5b226e73302e7765622d73776565742d7765622e6e6574222c226e73312e7765622d73776565742d7765622e6e6574225d2c226d6170223a7b22223a7b226e73223a5b226e73302e7765622d73776565742d7765622e6e6574222c226e73312e7765622d73776565742d7765622e6e6574225d7d7d7d OP_2DROP OP_2DROP OP_DUP OP_HASH160 f3f4aee9d80da759a4a3547cf6aa95c09881decb OP_EQUALVERIFY OP_CHECKSIG")
|
|
19
|
+
@name_update = Script.from_string("304402206a8598a87aadd697732d0a187220023e2b54e542e144d0dee67660c8ca3d66f4022000de36f02afb9162f2d27e947d452d4e28baadfdb21637db2b1f252ad62d7fb201
|
|
20
|
+
04b61d1529dbe912c84d0de898e88ab8a9d9fa4a13e76e7b60419dd3bd2b72021842bbf0f5eba1001d55f0c6f2c255289bedbe843d0164b545bbc056f150b4c3f2 3 642f626974636f696e 7b22696e666f223a7b22726567697374726172223a22687474703a2f2f72656769737465722e646f742d6269742e6f7267227d2c22656d61696c223a2022726567697374657240646f742d6269742e6f7267222c226e73223a5b226e73302e7765622d73776565742d7765622e6e6574222c226e73312e7765622d73776565742d7765622e6e6574225d2c226d6170223a7b22223a7b226e73223a5b226e73302e7765622d73776565742d7765622e6e6574222c226e73312e7765622d73776565742d7765622e6e6574225d7d7d7d OP_2DROP OP_DROP OP_DUP OP_HASH160 8f29c40b89ceda0b9176819e2bb5a15f592c6548 OP_EQUALVERIFY OP_CHECKSIG")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'should parse name_new script' do
|
|
24
|
+
@name_new.is_name_new?.should == true
|
|
25
|
+
@name_firstupdate.is_name_new?.should == false
|
|
26
|
+
@name_update.is_name_new?.should == false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'should parse name_firstupdate script' do
|
|
30
|
+
@name_new.is_name_firstupdate?.should == false
|
|
31
|
+
@name_firstupdate.is_name_firstupdate?.should == true
|
|
32
|
+
@name_update.is_name_firstupdate?.should == false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'should parse name_update script' do
|
|
36
|
+
@name_new.is_name_update?.should == false
|
|
37
|
+
@name_firstupdate.is_name_update?.should == false
|
|
38
|
+
@name_update.is_name_update?.should == true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'should run scripts' do
|
|
42
|
+
@name_new.run { true }.should == true
|
|
43
|
+
@name_firstupdate.run { true }.should == true
|
|
44
|
+
@name_update.run { true }.should == true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'should get name_hash' do
|
|
48
|
+
@name_new.get_namecoin_hash.should == "820fa9c6d252d6773e4ef26a2feffa93f0237641"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'should get name' do
|
|
52
|
+
@name_firstupdate.get_namecoin_name.should == "d/bitcoin"
|
|
53
|
+
@name_update.get_namecoin_name.should == "d/bitcoin"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'should get value' do
|
|
57
|
+
@name_firstupdate.get_namecoin_value.should == '{"info":{"registrar":"http://register.dot-bit.org"},"email": "register@dot-bit.org","ns":["ns0.web-sweet-web.net","ns1.web-sweet-web.net"],"map":{"":{"ns":["ns0.web-sweet-web.net","ns1.web-sweet-web.net"]}}}'
|
|
58
|
+
@name_update.get_namecoin_value.should == @name_firstupdate.get_namecoin_value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def set_rand rand; @rand = rand; end
|
|
62
|
+
|
|
63
|
+
it 'should create scripts' do
|
|
64
|
+
key = Key.generate
|
|
65
|
+
script = Script.to_name_new_script(self, "test/foo", key.addr)
|
|
66
|
+
Script.new(script).to_string.should =~
|
|
67
|
+
/^1 (.*?) OP_2DROP OP_DUP OP_HASH160 #{key.hash160} OP_EQUALVERIFY OP_CHECKSIG$/
|
|
68
|
+
@rand.should != nil
|
|
69
|
+
|
|
70
|
+
script = Script.to_name_firstupdate_script("test/foo", "1234", "testing", key.addr)
|
|
71
|
+
Script.new(script).to_string.should ==
|
|
72
|
+
"2 746573742f666f6f 1234 74657374696e67 OP_2DROP OP_2DROP " +
|
|
73
|
+
"OP_DUP OP_HASH160 #{key.hash160} OP_EQUALVERIFY OP_CHECKSIG"
|
|
74
|
+
|
|
75
|
+
script = Script.to_name_update_script("test/foo", "more testing", key.addr)
|
|
76
|
+
Script.new(script).to_string.should ==
|
|
77
|
+
"3 746573742f666f6f 6d6f72652074657374696e67 OP_2DROP OP_DROP " +
|
|
78
|
+
"OP_DUP OP_HASH160 #{key.hash160} OP_EQUALVERIFY OP_CHECKSIG"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
[
|
|
84
|
+
{ :name => :utxo, :db => 'sqlite:/', utxo_cache: 0 },
|
|
85
|
+
{ :name => :sequel, :db => 'sqlite:/' },
|
|
86
|
+
].each do |configuration|
|
|
87
|
+
|
|
88
|
+
describe "Namecoin (#{configuration[:name]} store)" do
|
|
89
|
+
|
|
90
|
+
before do
|
|
91
|
+
Bitcoin.network = :namecoin
|
|
92
|
+
class Bitcoin::Validation::Block; def difficulty; true; end; end
|
|
93
|
+
class Bitcoin::Validation::Block; def min_timestamp; true; end; end
|
|
94
|
+
Bitcoin.network[:proof_of_work_limit] = Bitcoin.encode_compact_bits("ff"*32)
|
|
95
|
+
[:name_new, :name_firstupdate, :name_update].each {|type|
|
|
96
|
+
Bitcoin::Storage::Backends::SequelStore::SCRIPT_TYPES << type }
|
|
97
|
+
@store = Bitcoin::Storage.send(configuration[:name], configuration)
|
|
98
|
+
@store.reset
|
|
99
|
+
@store.log.level = :error
|
|
100
|
+
@key = Bitcoin::Key.generate
|
|
101
|
+
@block = create_block "00"*32, false, [], @key
|
|
102
|
+
Bitcoin.network[:genesis_hash] = @block.hash
|
|
103
|
+
@store.store_block(@block)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def set_rand r; @rand = r; end
|
|
107
|
+
|
|
108
|
+
it "should store names" do
|
|
109
|
+
# create name_new
|
|
110
|
+
@block = create_block @block.hash, true, [->(t) {
|
|
111
|
+
t.input {|i| i.prev_out @block.tx[0]; i.prev_out_index 0; i.signature_key @key }
|
|
112
|
+
t.output {|o| o.value 50e8; o.script {|s| s.type(:name_new)
|
|
113
|
+
s.recipient(self, "test", @key.addr) } } }], @key
|
|
114
|
+
@store.db[:names][hash: Bitcoin.hash160(@rand + "test".hth)].should != nil
|
|
115
|
+
|
|
116
|
+
# name_firstupdate should not be valid yet
|
|
117
|
+
@block = create_block @block.hash, true, [->(t) {
|
|
118
|
+
t.input {|i| i.prev_out @block.tx[0]; i.prev_out_index 0; i.signature_key @key }
|
|
119
|
+
t.output {|o| o.value 50e8; o.script {|s| s.type(:name_firstupdate)
|
|
120
|
+
s.recipient("test", @rand, "testvalue", @key.addr) } } }], @key
|
|
121
|
+
@store.name_show("test").should == nil
|
|
122
|
+
|
|
123
|
+
# create enough blocks for name_new to become valid
|
|
124
|
+
Namecoin::FIRSTUPDATE_LIMIT.times {
|
|
125
|
+
@block = create_block @block.hash, true, [], @key }
|
|
126
|
+
|
|
127
|
+
# name_firstupdate should be valid now
|
|
128
|
+
@block = create_block @block.hash, true, [->(t) {
|
|
129
|
+
t.input {|i| i.prev_out @block.tx[0]; i.prev_out_index 0; i.signature_key @key }
|
|
130
|
+
t.output {|o| o.value 50e8; o.script {|s|; s.type(:name_firstupdate)
|
|
131
|
+
s.recipient("test", @rand, "testvalue", @key.addr) } } }], @key
|
|
132
|
+
|
|
133
|
+
name = @store.name_show("test")
|
|
134
|
+
name.get_address.should == @key.addr
|
|
135
|
+
name.name.should == "test"
|
|
136
|
+
name.value.should == "testvalue"
|
|
137
|
+
name.hash.should == Bitcoin.hash160(@rand + "test".hth)
|
|
138
|
+
|
|
139
|
+
# create name_update
|
|
140
|
+
@new_key = Bitcoin::Key.generate
|
|
141
|
+
@block = create_block @block.hash, true, [->(t) {
|
|
142
|
+
t.input {|i| i.prev_out @block.tx[0]; i.prev_out_index 0; i.signature_key @key}
|
|
143
|
+
t.output {|o|o.value 50e8; o.script {|s| s.type(:name_update)
|
|
144
|
+
s.recipient("test", "testupdate", @new_key.addr) } } }], @new_key
|
|
145
|
+
|
|
146
|
+
name = @store.name_show("test")
|
|
147
|
+
name.get_address.should == @new_key.addr
|
|
148
|
+
name.value.should == "testupdate"
|
|
149
|
+
|
|
150
|
+
h = @store.name_history("test")
|
|
151
|
+
h.size.should == 2
|
|
152
|
+
h[0].value.should == "testvalue"
|
|
153
|
+
h[0].get_address.should == @key.addr
|
|
154
|
+
h[1].value.should == "testupdate"
|
|
155
|
+
h[1].get_address.should == @new_key.addr
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it "should expire names" do
|
|
159
|
+
@block = create_block @block.hash, true, [->(t) {
|
|
160
|
+
t.input {|i| i.prev_out @block.tx[0]; i.prev_out_index 0; i.signature_key @key }
|
|
161
|
+
t.output {|o| o.value 50e8; o.script {|s| s.type(:name_new)
|
|
162
|
+
s.recipient(self, "test", @key.addr) } } }], @key
|
|
163
|
+
@store.db[:names][hash: Bitcoin.hash160(@rand + "test".hth)].should != nil
|
|
164
|
+
|
|
165
|
+
# create enough blocks for name_new to become valid
|
|
166
|
+
Namecoin::FIRSTUPDATE_LIMIT.times {
|
|
167
|
+
@block = create_block @block.hash, true, [], @key }
|
|
168
|
+
|
|
169
|
+
# name_firstupdate should be valid now
|
|
170
|
+
@block = create_block @block.hash, true, [->(t) {
|
|
171
|
+
t.input {|i| i.prev_out @block.tx[0]; i.prev_out_index 0; i.signature_key @key }
|
|
172
|
+
t.output {|o| o.value 50e8; o.script {|s|; s.type(:name_firstupdate)
|
|
173
|
+
s.recipient("test", @rand, "testvalue", @key.addr) } } }], @key
|
|
174
|
+
|
|
175
|
+
@store.name_show("test").expires_in.should == Namecoin::EXPIRATION_DEPTH
|
|
176
|
+
@block = create_block @block.hash, true, [], @key
|
|
177
|
+
@store.name_show("test").expires_in.should == Namecoin::EXPIRATION_DEPTH - 1
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
1
3
|
require_relative 'spec_helper.rb'
|
|
2
4
|
|
|
3
5
|
describe 'Bitcoin::network' do
|
|
@@ -9,19 +11,19 @@ describe 'Bitcoin::network' do
|
|
|
9
11
|
net[:address_version].should == "00"
|
|
10
12
|
net[:genesis_hash].should == "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
|
|
11
13
|
end
|
|
12
|
-
|
|
14
|
+
|
|
13
15
|
it 'can be set to main net' do
|
|
14
16
|
Bitcoin::network = :bitcoin
|
|
15
17
|
Bitcoin::network.should == Bitcoin::NETWORKS[:bitcoin]
|
|
16
18
|
end
|
|
17
|
-
|
|
19
|
+
|
|
18
20
|
class Test_Handler
|
|
19
21
|
attr_reader :inv
|
|
20
22
|
def on_inv_transaction inv
|
|
21
23
|
@inv = inv
|
|
22
24
|
end
|
|
23
25
|
end
|
|
24
|
-
|
|
26
|
+
|
|
25
27
|
it 'uses correct magic_head when parsing a message' do
|
|
26
28
|
pkt = ["f9 be b4 d9 69 6e 76 00 00 00 00 00 00 00 00 00 49 00 00 00 11 ea 1c 91 02 01 00 00 00 e0 41 c2 38 f7 32 1a 68 0a 34 06 bf fd 72 12 e3 d1 2c b5 12 2a 8c 0b 52 76 de 82 30 b1 00 7a 42 01 00 00 00 33 00 09 71 a9 70 7b 6c 6d 6e 77 aa 2e ac 43 f3 e5 67 84 cb 61 b2 35 fb 8d fe e0 86 8b 40 7c f3".split(" ").join].pack("H*")
|
|
27
29
|
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
require_relative '../spec_helper.rb'
|
|
2
|
+
|
|
3
|
+
include Bitcoin
|
|
4
|
+
include Builder
|
|
5
|
+
|
|
6
|
+
describe 'Node Command API' do
|
|
7
|
+
|
|
8
|
+
def test_command command, params = [], response = nil, &block
|
|
9
|
+
$responses = {}
|
|
10
|
+
EM.run do
|
|
11
|
+
@client = Bitcoin::Network::CommandClient.connect(*@config[:command]) do
|
|
12
|
+
on_connected do
|
|
13
|
+
request(command, *params)
|
|
14
|
+
end
|
|
15
|
+
on_response do |cmd, data|
|
|
16
|
+
$responses[cmd] = data
|
|
17
|
+
EM.stop
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
result = $responses[command]
|
|
23
|
+
|
|
24
|
+
return result unless response || block
|
|
25
|
+
|
|
26
|
+
if block
|
|
27
|
+
block.call(result)
|
|
28
|
+
else
|
|
29
|
+
result.should == response
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
before do
|
|
35
|
+
Bitcoin::Validation::Block::RULES.merge({
|
|
36
|
+
syntax: [:hash, :tx_list, :bits, :max_timestamp, :coinbase, :coinbase_scriptsig, :transactions_syntax],
|
|
37
|
+
context: [:prev_hash, :coinbase_value, :min_timestamp, :transactions_context]
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
Bitcoin.network = :spec
|
|
41
|
+
@config = {
|
|
42
|
+
listen: ["127.0.0.1", 38333],
|
|
43
|
+
command: ["127.0.0.1", 38332],
|
|
44
|
+
storage: "sequel::sqlite:/",
|
|
45
|
+
dns: false,
|
|
46
|
+
intervals: { queue: 0.01 },
|
|
47
|
+
log: { network: :warn, storage: :warn },
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@node = Bitcoin::Network::Node.new(@config)
|
|
51
|
+
@pid = fork do
|
|
52
|
+
$stdout = StringIO.new
|
|
53
|
+
SimpleCov.running = false if defined?(SimpleCov)
|
|
54
|
+
@node.run
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
@genesis = P::Block.new("0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae180101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000".htb)
|
|
58
|
+
|
|
59
|
+
Bitcoin.network[:proof_of_work_limit] = Bitcoin.encode_compact_bits("ff"*32)
|
|
60
|
+
@key = Bitcoin::Key.generate
|
|
61
|
+
@block = create_block @genesis.hash, false, [], @key
|
|
62
|
+
|
|
63
|
+
test_command "store_block", [@genesis.to_payload.hth]
|
|
64
|
+
sleep 0.1
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
after do
|
|
68
|
+
Process.kill("TERM", @pid)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "should return error for unknown command" do
|
|
72
|
+
test_command("foo").should == {"error" => "unknown command: foo. send 'help' for help."}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "should return error for wrong parameters" do
|
|
76
|
+
test_command("info", "foo").should == {"error" => "wrong number of arguments (1 for 0)"}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "should query tslb" do
|
|
80
|
+
test_command("tslb") do |res|
|
|
81
|
+
res.keys.include?("tslb").should == true
|
|
82
|
+
res["tslb"].should >= 0
|
|
83
|
+
res["tslb"].should <= 1
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "should query info" do
|
|
88
|
+
info = test_command "info"
|
|
89
|
+
info.is_a?(Hash).should == true
|
|
90
|
+
info["blocks"].should == "0 (?)"
|
|
91
|
+
info["addrs"].should == "0 (0)"
|
|
92
|
+
info["connections"].should == "0 established (0 out, 0 in), 0 connecting"
|
|
93
|
+
info["queue"].should == 0
|
|
94
|
+
info["inv_queue"].should == 0
|
|
95
|
+
info["inv_cache"].should == 0
|
|
96
|
+
info["network"].should == "bitcoin"
|
|
97
|
+
info["storage"].should == "sequel::sqlite:/"
|
|
98
|
+
info["version"].should == 70001
|
|
99
|
+
info["external_ip"].should == "127.0.0.1"
|
|
100
|
+
info["uptime"].should =~ /00:00:00:0[0|1]/
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "should query config" do
|
|
104
|
+
test_command("config").should == JSON.load(@node.config.to_json)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# TODO
|
|
108
|
+
it "should query connections" do
|
|
109
|
+
test_command("connections").should == []
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# TODO
|
|
113
|
+
it "should connect" do
|
|
114
|
+
test_command("connect", ["127.0.0.1:1234"])["state"].should == "Connecting..."
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# TODO
|
|
118
|
+
it "should disconnect" do
|
|
119
|
+
test_command("disconnect", ["127.0.0.1:1234"])["state"].should == "Disconnected"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it "should store block" do
|
|
123
|
+
test_command("info")["blocks"].should == "0 (?)"
|
|
124
|
+
res = test_command "store_block", [ @block.to_payload.hth ]
|
|
125
|
+
res.should == { "queued" => [ "block", @block.hash ] }
|
|
126
|
+
sleep 0.1
|
|
127
|
+
test_command("info")["blocks"].should == "1 (?) sync"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
describe :create_tx do
|
|
131
|
+
|
|
132
|
+
before do
|
|
133
|
+
@key2 = Key.generate
|
|
134
|
+
test_command("store_block", [@block.to_payload.hth])
|
|
135
|
+
sleep 0.1
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it "should create transaction from given private keys" do
|
|
139
|
+
res = test_command("create_tx", [[@key.to_base58], [[@key2.addr, 10e8], [@key.addr, 40e8]]])
|
|
140
|
+
tx = P::Tx.new(res[0].htb)
|
|
141
|
+
tx.is_a?(P::Tx).should == true
|
|
142
|
+
tx.verify_input_signature(0, @block.tx[0]).should == true
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it "should create transaction from given addresses" do
|
|
146
|
+
res = test_command("create_tx", [[@key.addr], [[@key2.addr, 10e8], [@key.addr, 40e8]]])
|
|
147
|
+
tx = P::Tx.new(res[0].htb)
|
|
148
|
+
tx.is_a?(P::Tx).should == true
|
|
149
|
+
tx.in[0].script_sig.should == ""
|
|
150
|
+
-> { tx.verify_input_signature(0, @block.tx[0]) }.should.raise(TypeError)
|
|
151
|
+
|
|
152
|
+
res[1].each.with_index do |sig_data, idx|
|
|
153
|
+
sig_hash, sig_addr = *sig_data
|
|
154
|
+
sig_addr.should == @key.addr
|
|
155
|
+
sig = @key.sign(sig_hash.htb)
|
|
156
|
+
script_sig = Script.to_signature_pubkey_script(sig, @key.pub.htb)
|
|
157
|
+
tx.in[idx].script_sig_length = script_sig.bytesize
|
|
158
|
+
tx.in[idx].script_sig = script_sig
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
tx.verify_input_signature(0, @block.tx[0]).should == true
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it "should create transaction from given pubkeys" do
|
|
165
|
+
res = test_command("create_tx", [[@key.pub], [[@key2.addr, 10e8], [@key.addr, 40e8]]])
|
|
166
|
+
tx = P::Tx.new(res[0].htb)
|
|
167
|
+
tx.is_a?(P::Tx).should == true
|
|
168
|
+
-> { tx.verify_input_signature(0, @block.tx[0]) }.should.raise(TypeError)
|
|
169
|
+
|
|
170
|
+
res[1].each.with_index do |sig_data, idx|
|
|
171
|
+
sig_hash, sig_addr = *sig_data
|
|
172
|
+
sig_addr.should == @key.addr
|
|
173
|
+
sig = @key.sign(sig_hash.htb)
|
|
174
|
+
script_sig = Script.to_signature_pubkey_script(sig, @key.pub.htb)
|
|
175
|
+
tx.in[idx].script_sig_length = script_sig.bytesize
|
|
176
|
+
tx.in[idx].script_sig = script_sig
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
tx.verify_input_signature(0, @block.tx[0]).should == true
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
describe :assemble_tx do
|
|
185
|
+
|
|
186
|
+
it "should assemble tx from unsigned tx structure, signatures and pubkeys" do
|
|
187
|
+
tx = build_tx do |t|
|
|
188
|
+
t.input do |i|
|
|
189
|
+
i.prev_out @block.tx[0]
|
|
190
|
+
i.prev_out_index 0
|
|
191
|
+
end
|
|
192
|
+
t.output {|o| o.value 50e8; o.script {|s| s.recipient @key.addr } }
|
|
193
|
+
end
|
|
194
|
+
sig = @key.sign(tx.in[0].sig_hash)
|
|
195
|
+
test_command("store_block", [@block.to_payload.hth])
|
|
196
|
+
sleep 0.1
|
|
197
|
+
res = test_command("assemble_tx", [tx.to_payload.hth, [[sig.hth, @key.pub]]])
|
|
198
|
+
tx = Bitcoin::P::Tx.new(res.htb)
|
|
199
|
+
tx.verify_input_signature(0, @block.tx[0]).should == true
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
describe :relay_tx do
|
|
205
|
+
|
|
206
|
+
it "should handle decoding error" do
|
|
207
|
+
res = test_command("relay_tx", ["foobar"])
|
|
208
|
+
res["error"].should == "Error decoding transaction."
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
it "should handle syntax error" do
|
|
212
|
+
# create transaction with invalid output size
|
|
213
|
+
block = create_block(@block.hash, false, [->(t) {
|
|
214
|
+
create_tx(t, @block.tx[0], 0, [[22e14, @key]]) }], @key)
|
|
215
|
+
tx = block.tx[1]
|
|
216
|
+
|
|
217
|
+
error = test_command("relay_tx", [tx.to_payload.hth])
|
|
218
|
+
error["error"].should == "Transaction syntax invalid."
|
|
219
|
+
error["details"].should == ["output_values", [22e14, 21e14]]
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it "should handle context error" do
|
|
223
|
+
# create transaction with invalid input
|
|
224
|
+
block = create_block(@block.hash, false, [->(t) {
|
|
225
|
+
create_tx(t, @block.tx[0], 0, [[25e8, @key]]) }], @key)
|
|
226
|
+
tx = block.tx[1]
|
|
227
|
+
|
|
228
|
+
error = test_command("relay_tx", [tx.to_payload.hth])
|
|
229
|
+
error["error"].should == "Transaction context invalid."
|
|
230
|
+
error["details"].should == ["prev_out", [[@block.tx[0].hash, 0]]]
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
it "should relay transaction" do
|
|
234
|
+
block = create_block(@block.hash, false, [->(t) {
|
|
235
|
+
create_tx(t, @block.tx[0], 0, [[25e8, @key]]) }], @key)
|
|
236
|
+
tx = block.tx[1]
|
|
237
|
+
|
|
238
|
+
test_command("store_block", [@block.to_payload.hth])
|
|
239
|
+
sleep 0.1
|
|
240
|
+
res = test_command("relay_tx", [tx.to_payload.hth, 1, 0])
|
|
241
|
+
res["success"].should == true
|
|
242
|
+
res["hash"].should == tx.hash
|
|
243
|
+
res["propagation"].should == { "sent" => 1, "received" => 0, "percent" => 0.0 }
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
describe :monitor do
|
|
249
|
+
|
|
250
|
+
before do
|
|
251
|
+
@client = TCPSocket.new(*@config[:command])
|
|
252
|
+
|
|
253
|
+
def send data
|
|
254
|
+
@client.write(data.to_json + "\x00")
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def should_receive expected
|
|
258
|
+
buf = ""
|
|
259
|
+
while b = @client.read(1)
|
|
260
|
+
break if b == "\x00"
|
|
261
|
+
buf << b
|
|
262
|
+
end
|
|
263
|
+
resp = JSON.load(buf)
|
|
264
|
+
resp.should == expected
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def store_block block
|
|
268
|
+
send ["store_block", [ block.to_payload.hth ]]
|
|
269
|
+
should_receive ["store_block", {"queued" => [ "block", block.hash ]}]
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
describe :block do
|
|
275
|
+
|
|
276
|
+
before do
|
|
277
|
+
send ["monitor", ["block"]]
|
|
278
|
+
should_receive ["monitor", ["block", [ @genesis.to_hash, 0 ]]]
|
|
279
|
+
store_block @block
|
|
280
|
+
should_receive ["monitor", ["block", [ @block.to_hash, 1 ]]]
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
it "should monitor block" do
|
|
284
|
+
@block = create_block @block.hash, false
|
|
285
|
+
store_block @block
|
|
286
|
+
should_receive ["monitor", ["block", [ @block.to_hash, 2 ]]]
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
it "should not monitor side or orphan blocks" do
|
|
291
|
+
@side = create_block @genesis.hash, false
|
|
292
|
+
store_block @side
|
|
293
|
+
|
|
294
|
+
@orphan = create_block "00" * 32, false
|
|
295
|
+
store_block @orphan
|
|
296
|
+
|
|
297
|
+
# should not send side or orphan block only the next main block
|
|
298
|
+
@block = create_block @block.hash, false
|
|
299
|
+
store_block @block
|
|
300
|
+
should_receive ["monitor", ["block", [ @block.to_hash, 2 ]]]
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
describe :tx do
|
|
306
|
+
|
|
307
|
+
it "should monitor unconfirmed tx" do
|
|
308
|
+
send ["monitor", ["tx"]]
|
|
309
|
+
tx = @block.tx[0]
|
|
310
|
+
send ["store_tx", [ tx.to_payload.hth ] ]
|
|
311
|
+
should_receive ["store_tx", { "queued" => [ "tx", tx.hash ]}]
|
|
312
|
+
should_receive ["monitor", ["tx", [ tx.to_hash, 0 ]]]
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
it "should monitor confirmed tx" do
|
|
316
|
+
send ["monitor", ["tx_1"]]
|
|
317
|
+
store_block @block
|
|
318
|
+
should_receive ["monitor", ["tx_1", [ @block.tx[0].to_hash, 1 ]]]
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
it "should monitor tx for given confirmation level" do
|
|
322
|
+
send ["monitor", ["tx_3"]]
|
|
323
|
+
@tx = @block.tx[0]
|
|
324
|
+
store_block @block
|
|
325
|
+
@block = create_block @block.hash, false
|
|
326
|
+
store_block @block
|
|
327
|
+
should_receive ["monitor", ["tx_3", [ @genesis.tx[0].to_hash, 3 ]]]
|
|
328
|
+
@block = create_block @block.hash, false
|
|
329
|
+
store_block @block
|
|
330
|
+
should_receive ["monitor", ["tx_3", [ @tx.to_hash, 3 ]]]
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
describe :output do
|
|
336
|
+
|
|
337
|
+
before do
|
|
338
|
+
@tx = @block.tx[0]; @out = @tx.out[0]
|
|
339
|
+
@addr = Bitcoin::Script.new(@out.pk_script).get_address
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
it "should monitor unconfirmed outputs" do
|
|
343
|
+
send ["monitor", ["output"]]
|
|
344
|
+
tx = @block.tx[0]
|
|
345
|
+
send ["store_tx", [ tx.to_payload.hth ]]
|
|
346
|
+
should_receive ["store_tx", { "queued" => [ "tx", tx.hash ]}]
|
|
347
|
+
addr = Bitcoin::Script.new(tx.out[0].pk_script).get_address
|
|
348
|
+
should_receive ["monitor", ["output", [ tx.hash, addr, tx.out[0].value, 0]]]
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
it "should monitor confirmed output" do
|
|
352
|
+
send ["monitor", ["output_1"]]
|
|
353
|
+
store_block @block
|
|
354
|
+
should_receive ["monitor", ["output_1", [ @tx.hash, @addr, @out.value, 1 ]]]
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
it "should monitor output for given confirmation level" do
|
|
358
|
+
send ["monitor", ["output_3"]]
|
|
359
|
+
store_block @block
|
|
360
|
+
@block = create_block @block.hash, false
|
|
361
|
+
store_block @block
|
|
362
|
+
tx = @genesis.tx[0]; out = tx.out[0]
|
|
363
|
+
addr = Bitcoin::Script.new(out.pk_script).get_address
|
|
364
|
+
should_receive ["monitor", ["output_3", [ tx.hash, addr, out.value, 3 ]]]
|
|
365
|
+
|
|
366
|
+
@block = create_block @block.hash, false
|
|
367
|
+
store_block @block
|
|
368
|
+
should_receive ["monitor", ["output_3", [ @tx.hash, @addr, @out.value, 3 ]]]
|
|
369
|
+
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
end
|