dropzone_ruby 0.1

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.
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
+