dropzone_ruby 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/Drop Zone - An Anonymous Peer-To-Peer Local Contraband Marketplace.pdf +0 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +69 -0
  6. data/README.md +62 -0
  7. data/bin/dropzone +487 -0
  8. data/dropzone-screenshot.jpg +0 -0
  9. data/dropzone_ruby.gemspec +31 -0
  10. data/lib/blockrio_ext.rb +52 -0
  11. data/lib/dropzone/buyer.rb +21 -0
  12. data/lib/dropzone/command.rb +488 -0
  13. data/lib/dropzone/communication.rb +43 -0
  14. data/lib/dropzone/connection.rb +312 -0
  15. data/lib/dropzone/invoice.rb +23 -0
  16. data/lib/dropzone/item.rb +160 -0
  17. data/lib/dropzone/listing.rb +64 -0
  18. data/lib/dropzone/message_base.rb +178 -0
  19. data/lib/dropzone/payment.rb +36 -0
  20. data/lib/dropzone/profile.rb +86 -0
  21. data/lib/dropzone/record_base.rb +34 -0
  22. data/lib/dropzone/seller.rb +21 -0
  23. data/lib/dropzone/session.rb +161 -0
  24. data/lib/dropzone/state_accumulator.rb +39 -0
  25. data/lib/dropzone/version.rb +4 -0
  26. data/lib/dropzone_ruby.rb +14 -0
  27. data/lib/veto_checks.rb +74 -0
  28. data/spec/bitcoin_spec.rb +115 -0
  29. data/spec/buyer_profile_spec.rb +279 -0
  30. data/spec/buyer_spec.rb +109 -0
  31. data/spec/command_spec.rb +353 -0
  32. data/spec/config.yml +5 -0
  33. data/spec/invoice_spec.rb +129 -0
  34. data/spec/item_spec.rb +294 -0
  35. data/spec/lib/fake_connection.rb +97 -0
  36. data/spec/listing_spec.rb +150 -0
  37. data/spec/payment_spec.rb +152 -0
  38. data/spec/seller_profile_spec.rb +290 -0
  39. data/spec/seller_spec.rb +120 -0
  40. data/spec/session_spec.rb +303 -0
  41. data/spec/sham/buyer.rb +5 -0
  42. data/spec/sham/invoice.rb +5 -0
  43. data/spec/sham/item.rb +8 -0
  44. data/spec/sham/payment.rb +13 -0
  45. data/spec/sham/seller.rb +7 -0
  46. data/spec/spec_helper.rb +49 -0
  47. metadata +267 -0
@@ -0,0 +1,5 @@
1
+ database:
2
+ adapter: sqlite3
3
+ database: db/test.sqlite3
4
+ pool: 5
5
+ timeout: 5000
@@ -0,0 +1,129 @@
1
+ #encoding: utf-8
2
+ require_relative 'spec_helper'
3
+ require_relative 'sham/invoice'
4
+ require_relative 'sham/payment'
5
+
6
+ describe Dropzone::Invoice do
7
+ include_context 'globals'
8
+
9
+ describe "defaults" do
10
+ it "has accessors" do
11
+ invoice = Dropzone::Invoice.sham!(:build)
12
+
13
+ expect(invoice.expiration_in).to eq(6)
14
+ expect(invoice.amount_due).to eq(100_000_000)
15
+ expect(invoice.receiver_addr).to eq(test_pubkey)
16
+ end
17
+ end
18
+
19
+ describe "serialization" do
20
+ it "serializes to_transaction" do
21
+ expect(Dropzone::Invoice.sham!(:build).to_transaction).to eq({
22
+ tip: 20000, receiver_addr: test_pubkey,
23
+ data: "INCRTE\x01p\xFE\x00\xE1\xF5\x05\x01e\x06".force_encoding('ASCII-8BIT') })
24
+ end
25
+ end
26
+
27
+ describe "database" do
28
+ after{ clear_blockchain! }
29
+
30
+ it ".save() and .find()" do
31
+ invoice_id = Dropzone::Invoice.sham!(:build).save!(test_privkey)
32
+ expect(invoice_id).to be_kind_of(String)
33
+
34
+ invoice = Dropzone::Invoice.find invoice_id
35
+
36
+ expect(invoice.expiration_in).to eq(6)
37
+ expect(invoice.amount_due).to eq(100_000_000)
38
+ expect(invoice.receiver_addr).to eq(test_pubkey)
39
+ end
40
+ end
41
+
42
+ describe "associations" do
43
+ # It's a bit obtuse that there can be support for multiple payments
44
+ # but this should nonetheless be support to aid with reputation analysis
45
+ it "has_many payments" do
46
+ invoice_id = Dropzone::Invoice.sham!(:build,
47
+ receiver_addr: TESTER2_PUBLIC_KEY).save!(test_privkey)
48
+
49
+ Dropzone::Payment.sham!(:build, invoice_txid: invoice_id,
50
+ description: 'abc', receiver_addr: test_pubkey ).save! TESTER2_PRIVATE_KEY
51
+
52
+ increment_block_height!
53
+
54
+ Dropzone::Payment.sham!(:build, invoice_txid: invoice_id,
55
+ description: 'xyz', receiver_addr: test_pubkey ).save! TESTER2_PRIVATE_KEY
56
+
57
+ invoice = Dropzone::Invoice.find invoice_id
58
+ expect(invoice.payments.length).to eq(2)
59
+ expect(invoice.payments.collect(&:description)).to eq(['xyz','abc'])
60
+ end
61
+ end
62
+
63
+ describe "validations" do
64
+ it "validates default build" do
65
+ expect(Dropzone::Invoice.sham!(:build).valid?).to eq(true)
66
+ end
67
+
68
+ it "validates minimal invoice" do
69
+ invoice = Dropzone::Invoice.new receiver_addr: test_pubkey
70
+
71
+ expect(invoice.valid?).to eq(true)
72
+ end
73
+
74
+ it "expiration_in must be numeric" do
75
+ invoice = Dropzone::Invoice.sham! expiration_in: 'abc'
76
+
77
+ expect(invoice.valid?).to eq(false)
78
+ expect(invoice.errors.count).to eq(2)
79
+ expect(invoice.errors.on(:expiration_in)).to eq(
80
+ ['is not a number', "must be greater than or equal to 0"])
81
+ end
82
+
83
+ it "expiration_in must be gt 0" do
84
+ invoice = Dropzone::Invoice.sham! expiration_in: -1
85
+
86
+ expect(invoice.valid?).to eq(false)
87
+ expect(invoice.errors.count).to eq(1)
88
+ expect(invoice.errors.on(:expiration_in)).to eq(
89
+ ['must be greater than or equal to 0'])
90
+ end
91
+
92
+ it "amount_due must be numeric" do
93
+ invoice = Dropzone::Invoice.sham! amount_due: 'abc'
94
+
95
+ expect(invoice.valid?).to eq(false)
96
+ expect(invoice.errors.count).to eq(2)
97
+ expect(invoice.errors.on(:amount_due)).to eq(
98
+ ['is not a number', "must be greater than or equal to 0"])
99
+ end
100
+
101
+ it "amount_due must be gt 0" do
102
+ invoice = Dropzone::Invoice.sham! amount_due: -1
103
+
104
+ expect(invoice.valid?).to eq(false)
105
+ expect(invoice.errors.count).to eq(1)
106
+ expect(invoice.errors.on(:amount_due)).to eq(
107
+ ['must be greater than or equal to 0'])
108
+ end
109
+
110
+ it "validates output address must be present" do
111
+ invoice = Dropzone::Invoice.sham! receiver_addr: nil
112
+
113
+ expect(invoice.valid?).to eq(false)
114
+ expect(invoice.errors.count).to eq(1)
115
+ expect(invoice.errors.on(:receiver_addr)).to eq(['is not present'])
116
+ end
117
+
118
+ it "declaration must not be addressed to self" do
119
+ id = Dropzone::Invoice.sham!(receiver_addr: test_pubkey).save! test_privkey
120
+
121
+ invoice = Dropzone::Invoice.find id
122
+
123
+ expect(invoice.valid?).to eq(false)
124
+ expect(invoice.errors.count).to eq(1)
125
+ expect(invoice.errors.on(:receiver_addr)).to eq(['matches sender_addr'])
126
+ end
127
+
128
+ end
129
+ end
@@ -0,0 +1,294 @@
1
+ #encoding: utf-8
2
+ require_relative 'spec_helper'
3
+ require_relative 'sham/item'
4
+
5
+ describe Dropzone::Item do
6
+ include_context 'globals'
7
+
8
+ describe "defaults" do
9
+ it "has accessors" do
10
+ item = Dropzone::Item.sham!(:build)
11
+
12
+ expect(item.description).to eq("Item Description")
13
+ expect(item.price_currency).to eq('BTC')
14
+ expect(item.price_in_units).to eq(100_000_000)
15
+ expect(item.expiration_in).to eq(6)
16
+ expect(item.latitude).to eq(51.500782)
17
+ expect(item.longitude).to eq(-0.124669)
18
+ expect(item.radius).to eq(1000)
19
+ expect(item.receiver_addr).to eq('mfZ1415XX782179875331XX1XXXXXgtzWu')
20
+ expect(Bitcoin.valid_address?(item.receiver_addr)).to be_truthy
21
+ expect(item.sender_addr).to eq(nil)
22
+ end
23
+ end
24
+
25
+ describe "burn addresses" do
26
+ it "supports 6 digit distances" do
27
+ [90, 0, -90, 51.500782,-51.500782].each do |lat|
28
+ [180, 0, -180, -0.124669,0.124669].each do |lon|
29
+ [9,8,5,2,0,101,11010,999999,100000].each do |radius|
30
+ addr = Dropzone::Item.sham!(:build, :radius => radius,
31
+ :latitude => lat, :longitude => lon).receiver_addr
32
+
33
+ /\AmfZ([0-9X]{9})([0-9X]{9})([0-9X]{6})/.match addr
34
+
35
+ expect(addr.length).to eq(34)
36
+ expect($1.tr('X','0').to_i).to eq(((lat+90) * 1_000_000).floor)
37
+ expect($2.tr('X','0').to_i).to eq(((lon+180) * 1_000_000).floor)
38
+ expect($3.tr('X','0').to_i).to eq(radius)
39
+
40
+ end
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+
47
+ describe "serialization" do
48
+ it "serializes to_transaction" do
49
+ expect(Dropzone::Item.sham!(:build).to_transaction).to eq({ tip: 20000,
50
+ receiver_addr: "mfZ1415XX782179875331XX1XXXXXgtzWu",
51
+ data: "ITCRTE\x01d\x10Item Description\x01c\x03BTC\x01p\xFE\x00\xE1\xF5\x05\x01e\x06".force_encoding('ASCII-8BIT') })
52
+ end
53
+ end
54
+
55
+ describe "database" do
56
+ after{ clear_blockchain! }
57
+
58
+ it ".save() and .find()" do
59
+ item_id = Dropzone::Item.sham!(:build).save!(test_privkey)
60
+
61
+ expect(item_id).to be_kind_of(String)
62
+
63
+ item = Dropzone::Item.find(item_id)
64
+
65
+ expect(item.description).to eq("Item Description")
66
+ expect(item.price_currency).to eq('BTC')
67
+ expect(item.price_in_units).to eq(100_000_000)
68
+ expect(item.expiration_in).to eq(6)
69
+ expect(item.latitude).to eq(51.500782)
70
+ expect(item.longitude).to eq(-0.124669)
71
+ expect(item.radius).to eq(1000)
72
+ expect(item.receiver_addr).to eq('mfZ1415XX782179875331XX1XXXXXgtzWu')
73
+ expect(Bitcoin.valid_address?(item.receiver_addr)).to be_truthy
74
+ expect(item.sender_addr).to eq(test_pubkey)
75
+ end
76
+
77
+ it "updates must be addressed to self" do
78
+ item_id = Dropzone::Item.sham!(:build).save!(test_privkey)
79
+
80
+ update_id = Dropzone::Item.new(create_txid: item_id,
81
+ description: 'xyz').save! test_privkey
82
+
83
+ update_item = Dropzone::Item.find update_id
84
+
85
+ expect(update_item.description).to eq("xyz")
86
+ expect(update_item.message_type).to eq('ITUPDT')
87
+ expect(update_item.sender_addr).to eq(test_pubkey)
88
+ expect(update_item.receiver_addr).to eq(test_pubkey)
89
+ end
90
+ end
91
+
92
+ describe "validations" do
93
+ it "validates default build" do
94
+ expect(Dropzone::Item.sham!(:build).valid?).to eq(true)
95
+ end
96
+
97
+ it "validates minimal item" do
98
+ minimal_item = Dropzone::Item.new radius: 1, latitude: 51.500782,
99
+ longitude: -0.124669
100
+
101
+ expect(minimal_item.valid?).to eq(true)
102
+ end
103
+
104
+ it "requires output address" do
105
+ no_address = Dropzone::Item.sham! latitude: nil, longitude: nil, radius: nil
106
+
107
+ expect(no_address.valid?).to eq(false)
108
+ expect(no_address.errors.count).to eq(4)
109
+ expect(no_address.errors.on(:receiver_addr)).to eq(['is not present'])
110
+ end
111
+
112
+ it "requires latitude" do
113
+ item = Dropzone::Item.sham! latitude: nil
114
+
115
+ expect(item.valid?).to eq(false)
116
+ expect(item.errors.count).to eq(2)
117
+ expect(item.errors.on(:latitude)).to eq(['is not a number'])
118
+ end
119
+
120
+ it "requires latitude is gte -90" do
121
+ item = Dropzone::Item.sham! latitude: -90.000001
122
+
123
+ expect(item.valid?).to eq(false)
124
+ expect(item.errors.count).to eq(1)
125
+ expect(item.errors.on(:latitude)).to eq(['must be greater than or equal to -90'])
126
+ end
127
+
128
+ it "requires latitude is lte 90" do
129
+ item = Dropzone::Item.sham! latitude: 90.000001
130
+
131
+ expect(item.valid?).to eq(false)
132
+ expect(item.errors.count).to eq(1)
133
+ expect(item.errors.on(:latitude)).to eq(['must be less than or equal to 90'])
134
+ end
135
+
136
+ it "requires longitude" do
137
+ item = Dropzone::Item.sham! longitude: nil
138
+
139
+ expect(item.valid?).to eq(false)
140
+ expect(item.errors.count).to eq(2)
141
+ expect(item.errors.on(:longitude)).to eq(['is not a number'])
142
+ end
143
+
144
+ it "requires longitude is gte -180" do
145
+ item = Dropzone::Item.sham! longitude: -180.000001
146
+
147
+ expect(item.valid?).to eq(false)
148
+ expect(item.errors.count).to eq(1)
149
+ expect(item.errors.on(:longitude)).to eq(['must be greater than or equal to -180'])
150
+ end
151
+
152
+ it "requires longitude is lte 180" do
153
+ item = Dropzone::Item.sham! longitude: 180.000001
154
+
155
+ expect(item.valid?).to eq(false)
156
+ expect(item.errors.count).to eq(1)
157
+ expect(item.errors.on(:longitude)).to eq(['must be less than or equal to 180'])
158
+ end
159
+
160
+ it "requires radius" do
161
+ item = Dropzone::Item.sham! radius: nil
162
+
163
+ expect(item.valid?).to eq(false)
164
+ expect(item.errors.count).to eq(2)
165
+ expect(item.errors.on(:radius)).to eq(['is not a number'])
166
+ end
167
+
168
+ it "requires radius is gte 0" do
169
+ item = Dropzone::Item.sham! radius: -1
170
+
171
+ expect(item.valid?).to eq(false)
172
+ expect(item.errors.count).to eq(1)
173
+ expect(item.errors.on(:radius)).to eq(['must be greater than or equal to 0'])
174
+ end
175
+
176
+ it "requires radius is lt 1000000" do
177
+ item = Dropzone::Item.sham! radius: 1000000
178
+
179
+ expect(item.valid?).to eq(false)
180
+ expect(item.errors.count).to eq(1)
181
+ expect(item.errors.on(:radius)).to eq(['must be less than 1000000'])
182
+ end
183
+
184
+ it "requires message_type" do
185
+ item = Dropzone::Item.sham! message_type: 'INVALD'
186
+
187
+ expect(item.valid?).to eq(false)
188
+ expect(item.errors.count).to eq(1)
189
+ expect(item.errors.on(:message_type)).to eq(['is not valid'])
190
+ end
191
+
192
+ it "descriptions must be text" do
193
+ item = Dropzone::Item.sham! description: 5
194
+
195
+ expect(item.valid?).to eq(false)
196
+ expect(item.errors.count).to eq(1)
197
+ expect(item.errors.on(:description)).to eq(['is not a string'])
198
+ end
199
+
200
+ it "price_in_units must be numeric" do
201
+ item = Dropzone::Item.sham! price_in_units: 'abc',
202
+ price_currency: 'USD'
203
+
204
+ expect(item.valid?).to eq(false)
205
+ expect(item.errors.count).to eq(2)
206
+ expect(item.errors.on(:price_in_units)).to eq(['is not a number',
207
+ "must be greater than or equal to 0"])
208
+ end
209
+
210
+ it "expiration_in must be numeric" do
211
+ item = Dropzone::Item.sham! expiration_in: 'abc'
212
+
213
+ expect(item.valid?).to eq(false)
214
+ expect(item.errors.count).to eq(2)
215
+ expect(item.errors.on(:expiration_in)).to eq(['is not a number',
216
+ "must be greater than or equal to 0"])
217
+ end
218
+
219
+ it "price_currency must be present if price is present" do
220
+ item = Dropzone::Item.sham! price_in_units: 100, price_currency: nil
221
+
222
+ expect(item.valid?).to eq(false)
223
+ expect(item.errors.count).to eq(1)
224
+ expect(item.errors.on(:price_currency)).to eq(['is required if price is specified'])
225
+ end
226
+
227
+ end
228
+
229
+ describe "distance calculations" do
230
+ it "calculates distance in meters between two points" do
231
+ # New York to London:
232
+ nyc_to_london = Dropzone::Item.distance_between 40.712784, -74.005941,
233
+ 51.507351, -0.127758
234
+ texas = Dropzone::Item.distance_between 31.428663, -99.096680,
235
+ 36.279707, -102.568359
236
+ hong_kong = Dropzone::Item.distance_between 22.396428, 114.109497,
237
+ 22.408489, 113.906937
238
+
239
+ expect(nyc_to_london.round).to eq(5570224)
240
+ expect(texas.round).to eq(627363)
241
+ expect(hong_kong.round).to eq(20867)
242
+ end
243
+ end
244
+
245
+ describe 'finders' do
246
+ after{ clear_blockchain! }
247
+
248
+ before do
249
+ # < 20 km from shinjuku
250
+ fuchu_id = Dropzone::Item.sham!(:build, :description => 'Fuchu',
251
+ :radius => 20_000, :latitude => 35.688533,
252
+ :longitude => 139.471436).save! test_privkey
253
+
254
+ increment_block_height!
255
+
256
+ # 36 km from shinjuku
257
+ Dropzone::Item.sham!(:build, :description => 'Abiko', :radius => 20_000,
258
+ :latitude => 35.865683, :longitude => 140.031738).save! TESTER2_PRIVATE_KEY
259
+
260
+ # 3 km from shinjuku
261
+ Dropzone::Item.sham!(:build, :description => 'Nakano', :radius => 20_000,
262
+ :latitude => 35.708050, :longitude => 139.664383).save! TESTER3_PRIVATE_KEY
263
+
264
+ increment_block_height!
265
+
266
+ # 38.5 km from shinjuku
267
+ Dropzone::Item.sham!(:build, :description => 'Chiba', :radius => 20_000,
268
+ :latitude => 35.604835, :longitude => 140.105209).save! test_privkey
269
+
270
+ # This shouldn't actually be returned, since it's an update, and
271
+ # find_creates_since_block only looks for creates:
272
+ Dropzone::Item.new(create_txid: fuchu_id,
273
+ description: 'xyz').save! test_privkey
274
+ end
275
+
276
+ it ".find_creates_since_block()" do
277
+ items = Dropzone::Item.find_creates_since_block block_height, block_height
278
+
279
+ expect(items.length).to eq(4)
280
+ expect(items.collect(&:description)).to eq(['Chiba', 'Nakano', 'Abiko',
281
+ 'Fuchu'])
282
+ end
283
+
284
+ it ".find_in_radius()" do
285
+ # Twenty km around Shinjuku:
286
+ items = Dropzone::Item.find_in_radius block_height, block_height,
287
+ 35.689487, 139.691706, 20_000
288
+ expect(items.length).to eq(2)
289
+ expect(items.collect(&:description)).to eq(['Nakano', 'Fuchu'])
290
+ end
291
+ end
292
+
293
+
294
+ end
@@ -0,0 +1,97 @@
1
+ require 'sequel'
2
+
3
+ class FakeBitcoinConnection
4
+ attr_accessor :height, :transactions
5
+
6
+ DB ||= Sequel.sqlite # logger: Logger.new(STDOUT)
7
+
8
+ def initialize
9
+ DB.create_table :transactions do
10
+ primary_key :id
11
+ File :data
12
+ String :receiver_addr
13
+ String :sender_addr
14
+ Integer :tip
15
+ Integer :block_height
16
+ end unless DB.table_exists?(:transactions)
17
+
18
+ @height = 0
19
+ @transactions = DB[:transactions]
20
+ end
21
+
22
+ def is_testing?; true; end
23
+ def privkey_to_addr(key); Bitcoin::Key.from_base58(key).addr; end
24
+ def hash160_to_address(hash160); Bitcoin.hash160_to_address hash160; end
25
+ def hash160_from_address(addr); Bitcoin.hash160_from_address addr; end
26
+ def valid_address?(addr); Bitcoin.valid_address? addr; end
27
+
28
+ # NOTE:
29
+ # - This needs to return the messages in Descending order by block
30
+ # In the case that two transactions are in the same block, it goes by time
31
+ # - This should return only 'valid' messages. Not all transactions
32
+ def messages_by_addr(addr, options = {})
33
+ filter_messages transactions.where(
34
+ Sequel.expr(receiver_addr: addr) | Sequel.expr(sender_addr: addr) ),
35
+ options
36
+ end
37
+
38
+ def messages_in_block(block_height, options = {})
39
+ filter_messages transactions.where(
40
+ Sequel.expr(block_height: block_height) ), options
41
+ end
42
+
43
+ def tx_by_id(id)
44
+ record_to_tx transactions[id: id.to_i]
45
+ end
46
+
47
+ # We ignore the private key in this connection. We return the database id
48
+ # in lieue of transaction id.
49
+ def save!(tx, private_key)
50
+ transactions.insert(tx.tap{ |et|
51
+ et[:block_height] = @height
52
+ et[:sender_addr] = privkey_to_addr(private_key)
53
+ et[:data] = Sequel.blob et[:data]
54
+ }).to_s
55
+ end
56
+
57
+ # This aids test mode:
58
+ def clear_transactions!
59
+ transactions.delete
60
+ @height = 0
61
+ end
62
+
63
+ def increment_block_height!
64
+ @height += 1
65
+ end
66
+
67
+ private
68
+
69
+ def record_to_tx(record)
70
+ record.tap{|r| r[:txid] = r.delete(:id).to_s } if record
71
+ end
72
+
73
+ def filter_messages(messages, options = {})
74
+ if options.has_key?(:start_block)
75
+ messages = messages.where{block_height >= options[:start_block]}
76
+ end
77
+ if options.has_key?(:end_block)
78
+ messages = messages.where{block_height <= options[:end_block]}
79
+ end
80
+
81
+ ret = messages.order(Sequel.desc(:block_height)).order(Sequel.desc(:id)).to_a
82
+ ret = ret.collect{ |tx|
83
+ msg = Dropzone::MessageBase.new_message_from record_to_tx(tx)
84
+ msg.valid? ? msg : nil
85
+ }.compact
86
+
87
+ ret = ret.find_all{|msg| msg.message_type == options[:type]} if ret && options.has_key?(:type)
88
+
89
+ if options.has_key?(:between)
90
+ ret = ret.find_all{|c|
91
+ [c.receiver_addr, c.sender_addr].all?{|a| options[:between].include?(a) } }
92
+ end
93
+
94
+ (ret) ? ret : []
95
+ end
96
+ end
97
+