voucher 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: db6ad65ab3763dd7415d13b1434520b27b93df9f
4
+ data.tar.gz: 4194ea556956ac26c40005e1b941bcd1747a4739
5
+ SHA512:
6
+ metadata.gz: 2bf05edda50401d85888e02efcabdb1493e46d283c3f31c180cdf7825e8a0609903f056f72668d78056f055d98316577458dd1136463732e977457d5f2d5a8b2
7
+ data.tar.gz: 54c550b9cd4813acdfd5e9e5655f3c7518fb7ac362a103a5bdfbfb3b333fc8ae48a286721ee8cf548e4e623371e6f86be3ead701bb18138d3889b13fe2302963
data/.editorconfig ADDED
@@ -0,0 +1,11 @@
1
+ # Unifying editor configurations for all developers:
2
+ # For details see http://editorconfig.org/
3
+
4
+ root = true
5
+
6
+ [*]
7
+ end_of_line = lf
8
+ indent_size = 2
9
+ indent_style = space
10
+ insert_final_newline = true
11
+ trim_trailing_whitespace = true
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,45 @@
1
+
2
+ AllCops:
3
+ Exclude:
4
+ - 'bin/*'
5
+ - 'doc/*'
6
+ - 'exe/*'
7
+ - 'Gemfile'
8
+ - 'voucher.gemspec'
9
+ - 'Rakefile'
10
+ TargetRubyVersion: 2.4
11
+
12
+ Style/Documentation:
13
+ Enabled: false
14
+
15
+ Style/SignalException:
16
+ EnforcedStyle: only_raise
17
+
18
+ Style/Lambda:
19
+ Enabled: false
20
+
21
+ Metrics/BlockLength:
22
+ Exclude:
23
+ - 'voucher.gemspec'
24
+ ExcludedMethods: ['describe', 'context']
25
+
26
+ Metrics/LineLength:
27
+ Max: 100
28
+
29
+ Metrics/MethodLength:
30
+ Max: 20
31
+
32
+ Performance/Casecmp:
33
+ Enabled: false
34
+
35
+ Style/PercentLiteralDelimiters:
36
+ PreferredDelimiters:
37
+ default: ()
38
+ '%i': ()
39
+ '%w': ()
40
+
41
+ Style/RedundantReturn:
42
+ Enabled: false
43
+
44
+ Style/ExpandPathArguments:
45
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.4.3
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.4.3
7
+ before_install: gem install bundler -v 1.16.3
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
+ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.0] - 2018-08-14
8
+ ### Added:
9
+ - Ability to run voucher as a CLI tool
10
+ - Ability to run voucher embedded in your app
11
+ - Ability to set options when running the script
12
+ - Reading and parsing of segments file
13
+ - Validate segments against Ethereum
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,101 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ voucher (0.1.0)
5
+ activesupport (~> 5.2)
6
+ ethereum.rb (~> 2.2)
7
+ hashtastic
8
+ murky_waters
9
+ thor
10
+ virtus
11
+
12
+ GEM
13
+ remote: https://rubygems.org/
14
+ specs:
15
+ activesupport (5.2.1)
16
+ concurrent-ruby (~> 1.0, >= 1.0.2)
17
+ i18n (>= 0.7, < 2)
18
+ minitest (~> 5.1)
19
+ tzinfo (~> 1.1)
20
+ ast (2.4.0)
21
+ axiom-types (0.1.1)
22
+ descendants_tracker (~> 0.0.4)
23
+ ice_nine (~> 0.11.0)
24
+ thread_safe (~> 0.3, >= 0.3.1)
25
+ coderay (1.1.2)
26
+ coercible (1.0.0)
27
+ descendants_tracker (~> 0.0.1)
28
+ concurrent-ruby (1.0.5)
29
+ descendants_tracker (0.0.4)
30
+ thread_safe (~> 0.3, >= 0.3.1)
31
+ diff-lcs (1.3)
32
+ digest-sha3 (1.1.0)
33
+ equalizer (0.0.11)
34
+ ethereum.rb (2.2)
35
+ activesupport (>= 4.0)
36
+ digest-sha3 (~> 1.1)
37
+ hashtastic (0.1.2)
38
+ activesupport (~> 5.0)
39
+ digest-sha3 (~> 1.1)
40
+ i18n (1.1.0)
41
+ concurrent-ruby (~> 1.0)
42
+ ice_nine (0.11.2)
43
+ jaro_winkler (1.5.1)
44
+ method_source (0.9.0)
45
+ minitest (5.11.3)
46
+ murky_waters (0.1.4)
47
+ parallel (1.12.1)
48
+ parser (2.5.1.2)
49
+ ast (~> 2.4.0)
50
+ powerpack (0.1.2)
51
+ pry (0.11.3)
52
+ coderay (~> 1.1.0)
53
+ method_source (~> 0.9.0)
54
+ rainbow (3.0.0)
55
+ rake (10.5.0)
56
+ rspec (3.7.0)
57
+ rspec-core (~> 3.7.0)
58
+ rspec-expectations (~> 3.7.0)
59
+ rspec-mocks (~> 3.7.0)
60
+ rspec-core (3.7.1)
61
+ rspec-support (~> 3.7.0)
62
+ rspec-expectations (3.7.0)
63
+ diff-lcs (>= 1.2.0, < 2.0)
64
+ rspec-support (~> 3.7.0)
65
+ rspec-mocks (3.7.0)
66
+ diff-lcs (>= 1.2.0, < 2.0)
67
+ rspec-support (~> 3.7.0)
68
+ rspec-support (3.7.1)
69
+ rubocop (0.58.2)
70
+ jaro_winkler (~> 1.5.1)
71
+ parallel (~> 1.10)
72
+ parser (>= 2.5, != 2.5.1.1)
73
+ powerpack (~> 0.1)
74
+ rainbow (>= 2.2.2, < 4.0)
75
+ ruby-progressbar (~> 1.7)
76
+ unicode-display_width (~> 1.0, >= 1.0.1)
77
+ ruby-progressbar (1.10.0)
78
+ thor (0.20.0)
79
+ thread_safe (0.3.6)
80
+ tzinfo (1.2.5)
81
+ thread_safe (~> 0.1)
82
+ unicode-display_width (1.4.0)
83
+ virtus (1.0.5)
84
+ axiom-types (~> 0.1)
85
+ coercible (~> 1.0)
86
+ descendants_tracker (~> 0.0, >= 0.0.3)
87
+ equalizer (~> 0.0, >= 0.0.9)
88
+
89
+ PLATFORMS
90
+ ruby
91
+
92
+ DEPENDENCIES
93
+ bundler (~> 1.16)
94
+ pry (~> 0.11.3)
95
+ rake (~> 10.0)
96
+ rspec (~> 3.0)
97
+ rubocop
98
+ voucher!
99
+
100
+ BUNDLED WITH
101
+ 1.16.3
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Lucidity
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # Voucher
2
+
3
+ Validate that the segments exist in an Election through the Ethereum blockchain.
4
+
5
+ Ingest a file full of segments encoded in JSON and validates that each one exists in an Election through the Ethereum blockchain.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'voucher'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```sh
18
+ $ bundle
19
+ ```
20
+
21
+ Or install it yourself as:
22
+
23
+ ```sh
24
+ $ gem install voucher
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ Validate the segments in a given election
30
+
31
+ Run it as a CLI tool
32
+
33
+ ```sh
34
+ voucher validate segments.json -h http://your.node.url:8545
35
+ ```
36
+
37
+ Embedded in your application
38
+
39
+ ```rb
40
+ require 'voucher'
41
+
42
+ Voucher.configure(host: 'http://your.node.url:8545')
43
+ Voucher::Validator.call('segments.json')
44
+
45
+ => true
46
+ ```
47
+
48
+ ## Development
49
+
50
+ ### Requirements
51
+
52
+ ```sh
53
+ gem install bundle
54
+
55
+ git clone git@github.com:luciditytech/voucher.git
56
+ cd voucher
57
+ ```
58
+
59
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
60
+
61
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
62
+
63
+ ## Contributing
64
+
65
+ Bug reports and pull requests are welcome on GitHub at https://github.com/luciditytech/voucher.
66
+
67
+ ## License
68
+
69
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "voucher"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/voucher ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'voucher/cli'
4
+
5
+ Voucher::CLI.start
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'voucher'
5
+
6
+ module Voucher
7
+ class CLI < Thor
8
+ desc 'validate FILE', 'Validate that the segments in a given json file exist in ' \
9
+ 'an Election through the Ethereum blockchain'
10
+ long_desc <<-D
11
+ Ingest a file full of segments encoded in JSON and validates that each one
12
+ exists in an Election through the Ethereum blockchain.
13
+ D
14
+ method_option :host, type: :string, aliases: '-h'
15
+ def validate(segments_file)
16
+ configure
17
+ Voucher::Validator.call(segments_file)
18
+ end
19
+
20
+ private
21
+
22
+ def configure
23
+ Voucher.configure do |config|
24
+ config.ethereum_url = options['host'] if options['host']
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,210 @@
1
+ [
2
+ {
3
+ "constant": true,
4
+ "inputs": [],
5
+ "name": "endsAt",
6
+ "outputs": [{"name": "", "type": "uint256"}],
7
+ "payable": false,
8
+ "stateMutability": "view",
9
+ "type": "function"
10
+ },
11
+ {
12
+ "constant": true,
13
+ "inputs": [],
14
+ "name": "chairperson",
15
+ "outputs": [{"name": "", "type": "address"}],
16
+ "payable": false,
17
+ "stateMutability": "view",
18
+ "type": "function"
19
+ },
20
+ {
21
+ "constant": true,
22
+ "inputs": [],
23
+ "name": "revealed",
24
+ "outputs": [{"name": "", "type": "bool"}],
25
+ "payable": false,
26
+ "stateMutability": "view",
27
+ "type": "function"
28
+ },
29
+ {
30
+ "constant": true,
31
+ "inputs": [],
32
+ "name": "blockNumber",
33
+ "outputs": [{"name": "", "type": "uint256"}],
34
+ "payable": false,
35
+ "stateMutability": "view",
36
+ "type": "function"
37
+ },
38
+ {
39
+ "constant": true,
40
+ "inputs": [{"name": "", "type": "bytes32"}],
41
+ "name": "blindedProposals",
42
+ "outputs": [{"name": "", "type": "bool"}],
43
+ "payable": false,
44
+ "stateMutability": "view",
45
+ "type": "function"
46
+ },
47
+ {
48
+ "constant": true,
49
+ "inputs": [],
50
+ "name": "chainAddress",
51
+ "outputs": [{"name": "", "type": "address"}],
52
+ "payable": false,
53
+ "stateMutability": "view",
54
+ "type": "function"
55
+ },
56
+ {
57
+ "constant": true,
58
+ "inputs": [],
59
+ "name": "votingEnd",
60
+ "outputs": [{"name": "", "type": "uint256"}],
61
+ "payable": false,
62
+ "stateMutability": "view",
63
+ "type": "function"
64
+ },
65
+ {
66
+ "constant": true,
67
+ "inputs": [],
68
+ "name": "previousRoot",
69
+ "outputs": [{"name": "", "type": "bytes32"}],
70
+ "payable": false,
71
+ "stateMutability": "view",
72
+ "type": "function"
73
+ },
74
+ {
75
+ "constant": true,
76
+ "inputs": [{"name": "", "type": "address"}],
77
+ "name": "voters",
78
+ "outputs": [
79
+ {"name": "voted", "type": "bool"},
80
+ {"name": "blindedProposal", "type": "bytes32"},
81
+ {"name": "shard", "type": "uint256"},
82
+ {"name": "proposal", "type": "bytes32"},
83
+ {"name": "revealed", "type": "bool"}
84
+ ],
85
+ "payable": false,
86
+ "stateMutability": "view",
87
+ "type": "function"
88
+ },
89
+ {
90
+ "constant": true,
91
+ "inputs": [],
92
+ "name": "revealEnd",
93
+ "outputs": [{"name": "", "type": "uint256"}],
94
+ "payable": false,
95
+ "stateMutability": "view",
96
+ "type": "function"
97
+ },
98
+ {
99
+ "constant": true,
100
+ "inputs": [],
101
+ "name": "startsAt",
102
+ "outputs": [{"name": "", "type": "uint256"}],
103
+ "payable": false,
104
+ "stateMutability": "view",
105
+ "type": "function"
106
+ },
107
+ {
108
+ "constant": true,
109
+ "inputs": [{"name": "", "type": "uint256"}],
110
+ "name": "roots",
111
+ "outputs": [{"name": "", "type": "bytes32"}],
112
+ "payable": false,
113
+ "stateMutability": "view",
114
+ "type": "function"
115
+ },
116
+ {
117
+ "constant": true,
118
+ "inputs": [],
119
+ "name": "registryAddress",
120
+ "outputs": [{"name": "", "type": "address"}],
121
+ "payable": false,
122
+ "stateMutability": "view",
123
+ "type": "function"
124
+ },
125
+ {
126
+ "constant": true,
127
+ "inputs": [{"name": "", "type": "uint256"}],
128
+ "name": "addresses",
129
+ "outputs": [{"name": "", "type": "address"}],
130
+ "payable": false,
131
+ "stateMutability": "view",
132
+ "type": "function"
133
+ },
134
+ {
135
+ "constant": true,
136
+ "inputs": [],
137
+ "name": "counted",
138
+ "outputs": [{"name": "", "type": "bool"}],
139
+ "payable": false,
140
+ "stateMutability": "view",
141
+ "type": "function"
142
+ },
143
+ {
144
+ "constant": true,
145
+ "inputs": [
146
+ {"name": "", "type": "uint256"},
147
+ {"name": "", "type": "bytes32"}
148
+ ],
149
+ "name": "counts",
150
+ "outputs": [{"name": "", "type": "uint256"}],
151
+ "payable": false,
152
+ "stateMutability": "view",
153
+ "type": "function"
154
+ },
155
+ {
156
+ "inputs": [
157
+ {"name": "_registryAddress", "type": "address"},
158
+ {"name": "_chainAddress", "type": "address"},
159
+ {"name": "_chairperson", "type": "address"},
160
+ {"name": "_blockNumber", "type": "uint256"},
161
+ {"name": "_previousRoot", "type": "bytes32"},
162
+ {"name": "_startsAt", "type": "uint256"},
163
+ {"name": "_endsAt", "type": "uint256"},
164
+ {"name": "_votingTime", "type": "uint256"},
165
+ {"name": "_revealTime", "type": "uint256"}
166
+ ],
167
+ "payable": false,
168
+ "stateMutability": "nonpayable",
169
+ "type": "constructor"
170
+ },
171
+ {
172
+ "constant": true,
173
+ "inputs": [],
174
+ "name": "getNumberOfVerifiers",
175
+ "outputs": [{"name": "", "type": "uint256"}],
176
+ "payable": false,
177
+ "stateMutability": "view",
178
+ "type": "function"
179
+ },
180
+ {
181
+ "constant": false,
182
+ "inputs": [{"name": "_blindedProposal", "type": "bytes32"}],
183
+ "name": "vote",
184
+ "outputs": [],
185
+ "payable": false,
186
+ "stateMutability": "nonpayable",
187
+ "type": "function"
188
+ },
189
+ {
190
+ "constant": false,
191
+ "inputs": [
192
+ {"name": "_proposal", "type": "bytes32"},
193
+ {"name": "_secret", "type": "bytes32"}
194
+ ],
195
+ "name": "reveal",
196
+ "outputs": [],
197
+ "payable": false,
198
+ "stateMutability": "nonpayable",
199
+ "type": "function"
200
+ },
201
+ {
202
+ "constant": false,
203
+ "inputs": [],
204
+ "name": "count",
205
+ "outputs": [],
206
+ "payable": false,
207
+ "stateMutability": "nonpayable",
208
+ "type": "function"
209
+ }
210
+ ]
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Voucher
4
+ class Config
5
+ ABI_FILE_NAME = "#{Dir.pwd}/lib/voucher/config/json/election.json"
6
+
7
+ attr_accessor :ethereum_url, :election_abi
8
+
9
+ def initialize
10
+ @ethereum_url = ENV['ETHEREUM_URL'] || 'http://172.31.80.135:8545'
11
+ @election_abi = File.read(ABI_FILE_NAME)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Voucher
4
+ class ElectionContract
5
+ attr_reader :contract, :ethereum
6
+
7
+ def initialize(options = {})
8
+ @ethereum = Ethereum::HttpClient.new(options[:ethereum_url])
9
+
10
+ @contract = Ethereum::Contract.create(
11
+ name: options[:name] || 'ElectionContractClient',
12
+ address: options[:address],
13
+ abi: options[:abi],
14
+ client: ethereum
15
+ )
16
+
17
+ @roots = {}
18
+ @formatter = Ethereum::Formatter.new
19
+ end
20
+
21
+ def winning_hash(shard)
22
+ @roots[shard] ||= find_root(shard)
23
+ end
24
+
25
+ private
26
+
27
+ def find_root(shard)
28
+ root = contract.call.roots(shard)
29
+
30
+ raise RootHashNotFound, shard if root.blank?
31
+
32
+ eth_format(root)
33
+ end
34
+
35
+ def eth_format(value)
36
+ hex = @formatter.from_ascii(value)
37
+
38
+ "0x#{hex}"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Voucher
4
+ class VoucherError < StandardError; end
5
+
6
+ class InvalidSegment < VoucherError
7
+ def initialize
8
+ super('Segment not found in the winning hash')
9
+ end
10
+ end
11
+
12
+ class RootHashNotFound < VoucherError
13
+ def initialize(shard)
14
+ super("Root hash from shard #{shard} was not found.")
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Voucher
4
+ class Election
5
+ include Virtus.model
6
+
7
+ attribute :starts_at, DateTime
8
+ attribute :ends_at, DateTime
9
+ attribute :address, String
10
+ attribute :block_number, Integer
11
+ attribute :poll_status, String
12
+ attribute :segments_count, Integer
13
+ attribute :created_at, DateTime
14
+ end
15
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Voucher
4
+ class Segment
5
+ include Virtus.model
6
+
7
+ FIELDS = %w(
8
+ advertiser_id
9
+ agency_id
10
+ app_id
11
+ campaign_id
12
+ channel_id
13
+ clicks
14
+ conversions
15
+ decision_cost
16
+ decision_win_impressions
17
+ exchange_id
18
+ impressions
19
+ loaded_impressions
20
+ publisher_id
21
+ site_id
22
+ win_cost
23
+ ).sort.freeze
24
+
25
+ attribute :advertiser_id, String
26
+ attribute :agency_id, String
27
+ attribute :app_id, String
28
+ attribute :campaign_id, String
29
+ attribute :channel_id, String
30
+ attribute :clicks, Integer
31
+ attribute :conversions, Integer
32
+ attribute :decision_cost, Integer
33
+ attribute :decision_win_impressions, Integer
34
+ attribute :exchange_id, String
35
+ attribute :impressions, Integer
36
+ attribute :loaded_impressions, Integer
37
+ attribute :publisher_id, String
38
+ attribute :site_id, String
39
+ attribute :win_cost, Integer
40
+ attribute :shard, Integer
41
+ attribute :siblings, Array[String]
42
+
43
+ def hash
44
+ Hashtastic::DictionaryValuesHasher.call(attributes, FIELDS)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Voucher
4
+ class SegmentsBuilder
5
+ def self.call(data)
6
+ new(data).call
7
+ end
8
+
9
+ def initialize(data)
10
+ @data = data
11
+ end
12
+
13
+ def call
14
+ build
15
+ end
16
+
17
+ private
18
+
19
+ def build
20
+ { election: election, segments: segments }
21
+ end
22
+
23
+ def election
24
+ attributes = @data.dig(:election, :attributes) || {}
25
+
26
+ Election.new(attributes)
27
+ end
28
+
29
+ def segments
30
+ raise ArgumentError, 'Segments data must be an array' unless @data[:segments].is_a?(Array)
31
+
32
+ @data[:segments].map do |segment|
33
+ Segment.new(segment[:attributes] || {})
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Voucher
4
+ class SegmentsParser
5
+ def self.call(json)
6
+ new(json).call
7
+ end
8
+
9
+ def initialize(json)
10
+ @json = json
11
+ end
12
+
13
+ def call
14
+ parse
15
+ end
16
+
17
+ private
18
+
19
+ def parse
20
+ content = segments_from_json
21
+
22
+ SegmentsBuilder.call(
23
+ election: content[:included]&.first,
24
+ segments: content[:data]
25
+ )
26
+ end
27
+
28
+ def segments_from_json
29
+ parsed_json = JSON.parse(@json)
30
+
31
+ underscore_keys(parsed_json).with_indifferent_access
32
+ end
33
+
34
+ def underscore_keys(parsed_json)
35
+ parsed_json.deep_transform_keys { |key| key.to_s.underscore }
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Voucher
4
+ class SegmentsReader
5
+ attr_reader :file_name
6
+
7
+ def self.call(file_name)
8
+ new(file_name).call
9
+ end
10
+
11
+ def initialize(file_name)
12
+ @file_name = file_name
13
+ end
14
+
15
+ def call
16
+ read_file
17
+ end
18
+
19
+ private
20
+
21
+ def read_file
22
+ path = File.expand_path(file_name)
23
+
24
+ File.read(path)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Voucher
4
+ class Validator
5
+ def self.call(segments_file)
6
+ new(segments_file).call
7
+ end
8
+
9
+ def initialize(segments_file)
10
+ @segments_file = segments_file
11
+ end
12
+
13
+ def call
14
+ segments.each do |segment|
15
+ raise InvalidSegment unless verify(segment)
16
+ end
17
+
18
+ true
19
+ end
20
+
21
+ private
22
+
23
+ def verify(segment)
24
+ Murky.verify(
25
+ contract.winning_hash(segment.shard),
26
+ segment.siblings,
27
+ segment.hash,
28
+ digest: Hashtastic::EthereumSHA3
29
+ )
30
+ end
31
+
32
+ def contract
33
+ @contract ||= ElectionContract.new(
34
+ address: election.address,
35
+ ethereum_url: Voucher.config.ethereum_url,
36
+ abi: Voucher.config.election_abi
37
+ )
38
+ end
39
+
40
+ def election
41
+ @election ||= data[:election]
42
+ end
43
+
44
+ def segments
45
+ @segments ||= data[:segments]
46
+ end
47
+
48
+ def data
49
+ @data ||= read_and_parse
50
+ end
51
+
52
+ def read_and_parse
53
+ json = SegmentsReader.call(@segments_file)
54
+
55
+ SegmentsParser.call(json)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Voucher
4
+ VERSION = '0.1.0'
5
+ end
data/lib/voucher.rb ADDED
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/all'
4
+ require 'virtus'
5
+ require 'ethereum.rb'
6
+ require 'hashtastic'
7
+ require 'murky'
8
+ require 'pry'
9
+
10
+ require 'voucher/version'
11
+ require 'voucher/config'
12
+ require 'voucher/errors'
13
+ require 'voucher/model/election'
14
+ require 'voucher/model/segment'
15
+ require 'voucher/election_contract'
16
+ require 'voucher/segments_reader'
17
+ require 'voucher/segments_builder'
18
+ require 'voucher/segments_parser'
19
+ require 'voucher/validator'
20
+
21
+ module Voucher
22
+ class << self
23
+ attr_writer :config
24
+ end
25
+
26
+ def self.config
27
+ @config ||= Config.new
28
+ end
29
+
30
+ def self.reset
31
+ @config = Config.new
32
+ end
33
+
34
+ def self.configure(options = {})
35
+ return yield(config) if block_given?
36
+
37
+ options = options.with_indifferent_access
38
+
39
+ config.ethereum_url = options['host'] if options['host']
40
+
41
+ config
42
+ end
43
+ end
data/voucher.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'voucher/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'voucher'
7
+ spec.version = Voucher::VERSION
8
+ spec.authors = ['lucidity']
9
+ spec.email = ['lucidity@lucidity.tech']
10
+
11
+ spec.summary = 'Validate that the segments exist in an Election through the Ethereum blockchain'
12
+ spec.description = 'Ingest a file full of segments encoded in JSON and validates that each one exists in an Election through the Ethereum blockchain'
13
+ spec.homepage = 'https://github.com/luciditytech/voucher'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
17
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ end
19
+ spec.bindir = 'exe'
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_dependency 'activesupport', '~> 5.2'
24
+ spec.add_dependency 'thor'
25
+ spec.add_dependency 'virtus'
26
+ spec.add_dependency 'ethereum.rb', '~> 2.2'
27
+ spec.add_dependency 'hashtastic'
28
+ spec.add_dependency 'murky_waters'
29
+
30
+ spec.add_development_dependency 'bundler', '~> 1.16'
31
+ spec.add_development_dependency 'pry', '~> 0.11.3'
32
+ spec.add_development_dependency 'rake', '~> 10.0'
33
+ spec.add_development_dependency 'rspec', '~> 3.0'
34
+ spec.add_development_dependency 'rubocop'
35
+ end
metadata ADDED
@@ -0,0 +1,229 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: voucher
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - lucidity
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-08-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: virtus
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: ethereum.rb
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: hashtastic
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: murky_waters
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: bundler
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.16'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.16'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.11.3
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.11.3
125
+ - !ruby/object:Gem::Dependency
126
+ name: rake
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '10.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '10.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubocop
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description: Ingest a file full of segments encoded in JSON and validates that each
168
+ one exists in an Election through the Ethereum blockchain
169
+ email:
170
+ - lucidity@lucidity.tech
171
+ executables:
172
+ - voucher
173
+ extensions: []
174
+ extra_rdoc_files: []
175
+ files:
176
+ - ".editorconfig"
177
+ - ".gitignore"
178
+ - ".rspec"
179
+ - ".rubocop.yml"
180
+ - ".ruby-version"
181
+ - ".travis.yml"
182
+ - CHANGELOG.md
183
+ - Gemfile
184
+ - Gemfile.lock
185
+ - LICENSE.txt
186
+ - README.md
187
+ - Rakefile
188
+ - bin/console
189
+ - bin/setup
190
+ - exe/voucher
191
+ - lib/voucher.rb
192
+ - lib/voucher/cli.rb
193
+ - lib/voucher/config.rb
194
+ - lib/voucher/config/json/election.json
195
+ - lib/voucher/election_contract.rb
196
+ - lib/voucher/errors.rb
197
+ - lib/voucher/model/election.rb
198
+ - lib/voucher/model/segment.rb
199
+ - lib/voucher/segments_builder.rb
200
+ - lib/voucher/segments_parser.rb
201
+ - lib/voucher/segments_reader.rb
202
+ - lib/voucher/validator.rb
203
+ - lib/voucher/version.rb
204
+ - voucher.gemspec
205
+ homepage: https://github.com/luciditytech/voucher
206
+ licenses:
207
+ - MIT
208
+ metadata: {}
209
+ post_install_message:
210
+ rdoc_options: []
211
+ require_paths:
212
+ - lib
213
+ required_ruby_version: !ruby/object:Gem::Requirement
214
+ requirements:
215
+ - - ">="
216
+ - !ruby/object:Gem::Version
217
+ version: '0'
218
+ required_rubygems_version: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
223
+ requirements: []
224
+ rubyforge_project:
225
+ rubygems_version: 2.6.14
226
+ signing_key:
227
+ specification_version: 4
228
+ summary: Validate that the segments exist in an Election through the Ethereum blockchain
229
+ test_files: []