ruby-ethereum 0.9.0

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 (73) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +40 -0
  4. data/lib/ethereum.rb +53 -0
  5. data/lib/ethereum/abi.rb +333 -0
  6. data/lib/ethereum/abi/contract_translator.rb +174 -0
  7. data/lib/ethereum/abi/type.rb +117 -0
  8. data/lib/ethereum/account.rb +72 -0
  9. data/lib/ethereum/address.rb +60 -0
  10. data/lib/ethereum/base_convert.rb +53 -0
  11. data/lib/ethereum/block.rb +1311 -0
  12. data/lib/ethereum/block_header.rb +211 -0
  13. data/lib/ethereum/bloom.rb +83 -0
  14. data/lib/ethereum/cached_block.rb +36 -0
  15. data/lib/ethereum/chain.rb +400 -0
  16. data/lib/ethereum/constant.rb +26 -0
  17. data/lib/ethereum/core_ext/array/safe_slice.rb +18 -0
  18. data/lib/ethereum/core_ext/module/lru_cache.rb +20 -0
  19. data/lib/ethereum/core_ext/numeric/denominations.rb +45 -0
  20. data/lib/ethereum/core_ext/object/truth.rb +47 -0
  21. data/lib/ethereum/db.rb +6 -0
  22. data/lib/ethereum/db/base_db.rb +9 -0
  23. data/lib/ethereum/db/ephem_db.rb +63 -0
  24. data/lib/ethereum/db/overlay_db.rb +72 -0
  25. data/lib/ethereum/db/refcount_db.rb +188 -0
  26. data/lib/ethereum/env.rb +64 -0
  27. data/lib/ethereum/ethash.rb +78 -0
  28. data/lib/ethereum/ethash_ruby.rb +38 -0
  29. data/lib/ethereum/ethash_ruby/cache.rb +47 -0
  30. data/lib/ethereum/ethash_ruby/hashimoto.rb +75 -0
  31. data/lib/ethereum/ethash_ruby/utils.rb +53 -0
  32. data/lib/ethereum/exceptions.rb +28 -0
  33. data/lib/ethereum/external_call.rb +173 -0
  34. data/lib/ethereum/fast_rlp.rb +81 -0
  35. data/lib/ethereum/fast_vm.rb +625 -0
  36. data/lib/ethereum/fast_vm/call_data.rb +44 -0
  37. data/lib/ethereum/fast_vm/message.rb +29 -0
  38. data/lib/ethereum/fast_vm/state.rb +25 -0
  39. data/lib/ethereum/ffi/openssl.rb +390 -0
  40. data/lib/ethereum/index.rb +97 -0
  41. data/lib/ethereum/log.rb +43 -0
  42. data/lib/ethereum/miner.rb +84 -0
  43. data/lib/ethereum/opcodes.rb +131 -0
  44. data/lib/ethereum/private_key.rb +92 -0
  45. data/lib/ethereum/pruning_trie.rb +28 -0
  46. data/lib/ethereum/public_key.rb +88 -0
  47. data/lib/ethereum/receipt.rb +53 -0
  48. data/lib/ethereum/secp256k1.rb +58 -0
  49. data/lib/ethereum/secure_trie.rb +49 -0
  50. data/lib/ethereum/sedes.rb +42 -0
  51. data/lib/ethereum/special_contract.rb +95 -0
  52. data/lib/ethereum/spv.rb +79 -0
  53. data/lib/ethereum/spv/proof.rb +31 -0
  54. data/lib/ethereum/spv/proof_constructor.rb +19 -0
  55. data/lib/ethereum/spv/proof_verifier.rb +24 -0
  56. data/lib/ethereum/tester.rb +14 -0
  57. data/lib/ethereum/tester/abi_contract.rb +65 -0
  58. data/lib/ethereum/tester/fixture.rb +31 -0
  59. data/lib/ethereum/tester/language.rb +30 -0
  60. data/lib/ethereum/tester/log_recorder.rb +13 -0
  61. data/lib/ethereum/tester/solidity_wrapper.rb +144 -0
  62. data/lib/ethereum/tester/state.rb +194 -0
  63. data/lib/ethereum/transaction.rb +196 -0
  64. data/lib/ethereum/transient_trie.rb +28 -0
  65. data/lib/ethereum/trie.rb +549 -0
  66. data/lib/ethereum/trie/nibble_key.rb +184 -0
  67. data/lib/ethereum/utils.rb +191 -0
  68. data/lib/ethereum/version.rb +5 -0
  69. data/lib/ethereum/vm.rb +606 -0
  70. data/lib/ethereum/vm/call_data.rb +40 -0
  71. data/lib/ethereum/vm/message.rb +30 -0
  72. data/lib/ethereum/vm/state.rb +25 -0
  73. metadata +284 -0
@@ -0,0 +1,64 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module Ethereum
4
+ class Env
5
+
6
+ DEFAULT_CONFIG = {
7
+ # Genesis constants
8
+ genesis_difficulty: 131072,
9
+ genesis_gas_limit: 3141592,
10
+ genesis_prevhash: Constant::HASH_ZERO,
11
+ genesis_coinbase: Address::ZERO,
12
+ genesis_nonce: Utils.zpad_int(42, 8),
13
+ genesis_mixhash: Constant::HASH_ZERO,
14
+ genesis_timestamp: 0,
15
+ genesis_extra_data: Constant::BYTE_EMPTY,
16
+ genesis_initial_alloc: {},
17
+
18
+ # Gas limit adjustment algo:
19
+ #
20
+ # block.gas_limit = block.parent.gas_limit * 1023/1024 +
21
+ # (block.gas.used * 6/5) / 1024
22
+ min_gas_limit: 5000,
23
+ gaslimit_ema_factor: 1024,
24
+ gaslimit_adjmax_factor: 1024,
25
+ blklim_factor_nom: 3,
26
+ blklim_factor_den: 2,
27
+
28
+ block_reward: 5000.finney,
29
+ nephew_reward: 5000.finney/32, # block_reward/32
30
+
31
+ # GHOST constants
32
+ uncle_depth_penalty_factor: 8,
33
+ max_uncle_depth: 6, # max (block.number - uncle.number)
34
+ max_uncles: 2,
35
+
36
+ diff_adjustment_cutoff: 13,
37
+ block_diff_factor: 2048,
38
+ min_diff: 131072,
39
+
40
+ pow_epoch_length: 30000,
41
+
42
+ max_extradata_length: 32,
43
+
44
+ expdiff_period: 100000,
45
+ expdiff_free_periods: 2,
46
+
47
+ account_initial_nonce: 0,
48
+
49
+ homestead_fork_blknum: 1150000,
50
+ homestead_diff_adjustment_cutoff: 10
51
+ }.freeze
52
+
53
+ attr :db, :config, :global_config
54
+
55
+ def initialize(db, config: nil, global_config: {})
56
+ @db = db
57
+ @config = config || DEFAULT_CONFIG
58
+ @global_config = global_config
59
+
60
+ raise "invalid nephew/block reward config" unless @config[:nephew_reward] == @config[:block_reward]/32
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,78 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ require 'ethash'
4
+ require 'ethereum/ethash_ruby'
5
+
6
+ module Ethereum
7
+ module Ethash
8
+
9
+ EPOCH_LENGTH = ::Ethash::EPOCH_LENGTH
10
+
11
+ CACHE_BY_SEED_MAX = 32
12
+
13
+ class <<self
14
+ def seeds
15
+ @seeds ||= ["\x00"*32]
16
+ end
17
+
18
+ def get_seed(block_number)
19
+ epoch_no = block_number / EPOCH_LENGTH
20
+ while seeds.size <= epoch_no
21
+ seeds.push Ethereum::Utils.keccak256(seeds.last)
22
+ end
23
+
24
+ seeds[epoch_no]
25
+ end
26
+
27
+ def cache_by_seed
28
+ @cache_by_seed ||= {} # ordered hash
29
+ end
30
+
31
+ def cache_by_file(block_number, data=nil)
32
+ path = "/tmp/ruby_ethereum_hashimoto_cache_#{block_number}"
33
+ if data
34
+ File.open(path, 'wb') {|f| f.write Marshal.dump(data) }
35
+ else
36
+ if File.exist?(path)
37
+ File.open(path, 'rb') {|f| Marshal.load f.read }
38
+ else
39
+ nil
40
+ end
41
+ end
42
+ end
43
+
44
+ def get_cache(blknum)
45
+ seed = get_seed blknum
46
+
47
+ if cache_by_seed.has_key?(seed)
48
+ c = cache_by_seed.delete seed # pop
49
+ cache_by_seed[seed] = c # and append at end
50
+ return c
51
+ end
52
+
53
+ if c = cache_by_file(Utils.encode_hex(seed))
54
+ cache_by_seed[seed] = c
55
+ return c
56
+ end
57
+
58
+ # Use c++ implementation or ethash_ruby
59
+ c = ::Ethash.mkcache_bytes blknum
60
+ #c = EthashRuby::Cache.new(blknum).to_a
61
+
62
+ cache_by_seed[seed] = c
63
+ cache_by_file Utils.encode_hex(seed), c
64
+ if cache_by_seed.size > CACHE_BY_SEED_MAX
65
+ cache_by_seed.delete cache_by_seed.keys.first # remove last recently accessed
66
+ end
67
+
68
+ c
69
+ end
70
+
71
+ def hashimoto_light(blknum, cache, mining_hash, bin_nonce)
72
+ nonce = Utils.big_endian_to_int(bin_nonce)
73
+ ::Ethash.hashimoto_light blknum, cache, mining_hash, nonce
74
+ end
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,38 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ require 'ethereum/ethash_ruby/utils'
4
+ require 'ethereum/ethash_ruby/cache'
5
+ require 'ethereum/ethash_ruby/hashimoto'
6
+
7
+ module Ethereum
8
+ module EthashRuby
9
+
10
+ EPOCH_LENGTH = 30000 # blocks per epoch
11
+ ACCESSES = 64 # number of accesses in hashimoto loop
12
+
13
+ DATASET_BYTES_INIT = 2**30 # bytes in dataset at genesis
14
+ DATASET_BYTES_GROWTH = 2**23 # growth per epoch (~ 7GB per year)
15
+ DATASET_PARENTS = 256 # number of parents of each dataset element
16
+
17
+ CACHE_BYTES_INIT = 2**24 # size of the dataset relative to the cache
18
+ CACHE_BYTES_GROWTH = 2**17 # size of the dataset relative to the cache
19
+ CACHE_ROUNDS = 3 # number of rounds in cache production
20
+
21
+ WORD_BYTES = 4 # bytes in word
22
+ MIX_BYTES = 128 # width of mix
23
+ HASH_BYTES = 64 # hash length in bytes
24
+
25
+ FNV_PRIME = 0x01000193
26
+
27
+ class <<self
28
+ def hashimoto_light(*args)
29
+ ::Ethereum::Ethash::Hashimoto.new.light(*args)
30
+ end
31
+
32
+ def get_cache(*args)
33
+ ::Ethereum::Ethash::Cache.get(*args)
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,47 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ require 'prime'
4
+
5
+ module Ethereum
6
+ module EthashRuby
7
+
8
+ class Cache
9
+ include Utils
10
+
11
+ def initialize(block_number)
12
+ @block_number = block_number
13
+ end
14
+
15
+ def to_a
16
+ n = size / HASH_BYTES
17
+
18
+ o = [keccak512(seed)]
19
+ (1...n).each {|i| o.push keccak512(o.last) }
20
+
21
+ CACHE_ROUNDS.times do
22
+ n.times do |i|
23
+ v = o[i][0] % n
24
+ xor = o[(i-1+n) % n].zip(o[v]).map {|(a,b)| a^b }
25
+ o[i] = keccak512 xor
26
+ end
27
+ end
28
+
29
+ o
30
+ end
31
+
32
+ def seed
33
+ @seed ||= self.class.get_seed(@block_number)
34
+ end
35
+
36
+ def size
37
+ sz = CACHE_BYTES_INIT + CACHE_BYTES_GROWTH * (@block_number / EPOCH_LENGTH)
38
+ sz -= HASH_BYTES
39
+
40
+ sz -= 2 * HASH_BYTES while !Prime.prime?(sz / HASH_BYTES)
41
+ sz
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,75 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ require 'prime'
4
+
5
+ module Ethereum
6
+ module EthashRuby
7
+
8
+ class Hashimoto
9
+ include Utils
10
+
11
+ def light(block_number, cache, header, nonce)
12
+ run header, nonce, get_full_size(block_number) do |x|
13
+ calc_dataset_item(cache, x)
14
+ end
15
+ end
16
+
17
+ def run(header, nonce, full_size, &dataset_lookup)
18
+ n = full_size / HASH_BYTES
19
+ w = MIX_BYTES / WORD_BYTES
20
+ mixhashes = MIX_BYTES / HASH_BYTES
21
+
22
+ s = keccak512(header + nonce.reverse)
23
+
24
+ mix = []
25
+ mixhashes.times { mix.concat s }
26
+
27
+ ACCESSES.times do |i|
28
+ p = fnv(i ^ s[0], mix[i % w]) % (n / mixhashes) * mixhashes
29
+
30
+ newdata = []
31
+ mixhashes.times {|j| newdata.concat dataset_lookup.call(p + j) }
32
+ mix = mix.zip(newdata).map {|(a,b)| fnv(a, b) }
33
+ end
34
+
35
+ cmix = []
36
+ (mix.size / WORD_BYTES).times do |i|
37
+ i *= WORD_BYTES
38
+ cmix.push fnv(fnv(fnv(mix[i], mix[i+1]), mix[i+2]), mix[i+3])
39
+ end
40
+
41
+ { mixhash: serialize_hash(cmix),
42
+ result: serialize_hash(keccak256(s + cmix)) }
43
+ end
44
+
45
+ def calc_dataset_item(cache, i)
46
+ n = cache.size
47
+ r = HASH_BYTES / WORD_BYTES
48
+
49
+ mix = cache[i % n].dup
50
+ mix[0] ^= i
51
+ mix = keccak512 mix
52
+
53
+ DATASET_PARENTS.times do |j|
54
+ cache_index = fnv(i ^ j, mix[j % r])
55
+ mix = mix.zip(cache[cache_index % n]).map {|(v1,v2)| fnv(v1, v2) }
56
+ end
57
+
58
+ keccak512(mix)
59
+ end
60
+
61
+ def fnv(v1, v2)
62
+ (v1 * FNV_PRIME ^ v2) % Constant::TT32
63
+ end
64
+
65
+ def get_full_size(block_number)
66
+ sz = DATASET_BYTES_INIT + DATASET_BYTES_GROWTH * (block_number / EPOCH_LENGTH)
67
+ sz -= MIX_BYTES
68
+ sz -= 2 * MIX_BYTES while !Prime.prime?(sz / MIX_BYTES)
69
+ sz
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,53 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module Ethereum
4
+ module EthashRuby
5
+ module Utils
6
+
7
+ # sha3 hash function, outputs 64 bytes
8
+ def keccak512(x)
9
+ hash_words(x) do |v|
10
+ Ethereum::Utils.keccak512(v)
11
+ end
12
+ end
13
+
14
+ def keccak256(x)
15
+ hash_words(x) do |v|
16
+ Ethereum::Utils.keccak256(v)
17
+ end
18
+ end
19
+
20
+ def hash_words(x, &block)
21
+ x = serialize_hash(x) if x.instance_of?(Array)
22
+ y = block.call(x)
23
+ deserialize_hash(y)
24
+ end
25
+
26
+ def serialize_hash(h)
27
+ h.map {|x| zpad(encode_int(x), WORD_BYTES) }.join
28
+ end
29
+
30
+ def deserialize_hash(h)
31
+ (h.size / WORD_BYTES).times.map do |i|
32
+ i *= WORD_BYTES
33
+ decode_int h[i, WORD_BYTES]
34
+ end
35
+ end
36
+
37
+ def encode_int(i)
38
+ # `pack('L<`) will introduce leading zeros
39
+ Ethereum::Utils.int_to_big_endian(i).reverse
40
+ end
41
+
42
+ # Assumes little endian bit ordering (same as Intel architectures)
43
+ def decode_int(s)
44
+ s && !s.empty? ? s.unpack('L<').first : 0
45
+ end
46
+
47
+ def zpad(s, len)
48
+ s + "\x00" * [0, len - s.size].max
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module Ethereum
4
+
5
+ class DeprecatedError < StandardError; end
6
+ class ChecksumError < StandardError; end
7
+ class FormatError < StandardError; end
8
+ class ValidationError < StandardError; end
9
+ class ValueError < StandardError; end
10
+ class AssertError < StandardError; end
11
+
12
+ class UnknownParentError < StandardError; end
13
+ class InvalidBlock < ValidationError; end
14
+ class InvalidUncles < ValidationError; end
15
+
16
+ class InvalidTransaction < ValidationError; end
17
+ class UnsignedTransactionError < InvalidTransaction; end
18
+ class InvalidNonce < InvalidTransaction; end
19
+ class InsufficientStartGas < InvalidTransaction; end
20
+ class InsufficientBalance < InvalidTransaction; end
21
+ class BlockGasLimitReached < InvalidTransaction; end
22
+
23
+ class InvalidSPVProof < ValidationError; end
24
+
25
+ class ContractCreationFailed < StandardError; end
26
+ class TransactionFailed < StandardError; end
27
+
28
+ end
@@ -0,0 +1,173 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ require 'forwardable'
4
+
5
+ module Ethereum
6
+
7
+ ##
8
+ # External calls that can be made from inside the VM. To use the EVM with a
9
+ # different blockchain system, database, set parameters for testing, just
10
+ # swap out the functions here.
11
+ #
12
+ class ExternalCall
13
+
14
+ extend Forwardable
15
+ def_delegators :@block, :get_code, :get_balance, :set_balance, :get_storage_data, :set_storage_data, :add_refund, :account_exists
16
+
17
+ def initialize(block, tx)
18
+ @block = block
19
+ @tx = tx
20
+ end
21
+
22
+ def log_storage(x)
23
+ @block.account_to_dict(x)[:storage]
24
+ end
25
+
26
+ def add_suicide(x)
27
+ @block.suicides.push x
28
+ end
29
+
30
+ def block_hash(x)
31
+ d = @block.number - x
32
+ if d > 0 && d <= 256
33
+ @block.get_ancestor_hash d
34
+ else
35
+ Constant::BYTE_EMPTY
36
+ end
37
+ end
38
+
39
+ def block_coinbase
40
+ @block.coinbase
41
+ end
42
+
43
+ def block_timestamp
44
+ @block.timestamp
45
+ end
46
+
47
+ def block_number
48
+ @block.number
49
+ end
50
+
51
+ def block_difficulty
52
+ @block.difficulty
53
+ end
54
+
55
+ def block_gas_limit
56
+ @block.gas_limit
57
+ end
58
+
59
+ def log(addr, topics, data)
60
+ @block.add_log Log.new(addr, topics, data)
61
+ end
62
+
63
+ def tx_origin
64
+ @tx.sender
65
+ end
66
+
67
+ def tx_gasprice
68
+ @tx.gasprice
69
+ end
70
+
71
+ def post_homestead_hardfork
72
+ @block.number >= @block.config[:homestead_fork_blknum]
73
+ end
74
+
75
+ def create(msg)
76
+ log_msg.debug 'CONTRACT CREATION'
77
+
78
+ sender = Utils.normalize_address(msg.sender, allow_blank: true)
79
+
80
+ @block.increment_nonce msg.sender if tx_origin != msg.sender
81
+
82
+ nonce = Utils.encode_int(@block.get_nonce(msg.sender) - 1)
83
+ msg.to = Utils.mk_contract_address sender, nonce
84
+
85
+ balance = get_balance(msg.to)
86
+ if balance > 0
87
+ set_balance msg.to, balance
88
+ @block.set_nonce msg.to, 0
89
+ @block.set_code msg.to, Constant::BYTE_EMPTY
90
+ @block.reset_storage msg.to
91
+ end
92
+
93
+ msg.is_create = true
94
+
95
+ code = msg.data.extract_all
96
+ msg.data = VM::CallData.new [], 0, 0
97
+ snapshot = @block.snapshot
98
+
99
+ res, gas, dat = apply_msg msg, code
100
+
101
+ if res.true?
102
+ return 1, gas, msg.to if dat.empty?
103
+
104
+ gcost = dat.size * Opcodes::GCONTRACTBYTE
105
+ if gas >= gcost
106
+ gas -= gcost
107
+ else
108
+ dat = []
109
+ log_msg.debug "CONTRACT CREATION OOG", have: gas, want: gcost, block_number: @block.number
110
+
111
+ if @block.number >= @block.config[:homestead_fork_blknum]
112
+ @block.revert snapshot
113
+ return 0, 0, Constant::BYTE_EMPTY
114
+ end
115
+ end
116
+
117
+ @block.set_code msg.to, Utils.int_array_to_bytes(dat)
118
+ return 1, gas, msg.to
119
+ else
120
+ return 0, gas, Constant::BYTE_EMPTY
121
+ end
122
+ end
123
+
124
+ def apply_msg(msg, code=nil)
125
+ code ||= get_code msg.code_address
126
+
127
+ log_msg.debug "MSG APPLY", sender: Utils.encode_hex(msg.sender), to: Utils.encode_hex(msg.to), gas: msg.gas, value: msg.value, data: Utils.encode_hex(msg.data.extract_all)
128
+ log_state.trace "MSG PRE STATE SENDER", account: Utils.encode_hex(msg.sender), balance: get_balance(msg.sender), state: log_storage(msg.sender)
129
+ log_state.trace "MSG PRE STATE RECIPIENT", account: Utils.encode_hex(msg.to), balance: get_balance(msg.to), state: log_storage(msg.to)
130
+
131
+ # snapshot before execution
132
+ snapshot = @block.snapshot
133
+
134
+ # transfer value
135
+ if msg.transfers_value
136
+ unless @block.transfer_value(msg.sender, msg.to, msg.value)
137
+ log_msg.debug "MSG TRANSFER FAILED", have: get_balance(msg.to), want: msg.value
138
+ return [1, msg.gas, []]
139
+ end
140
+ end
141
+
142
+ # main loop
143
+ if SpecialContract[msg.code_address]
144
+ res, gas, dat = SpecialContract[msg.code_address].call(self, msg)
145
+ else
146
+ res, gas, dat = VM.execute self, msg, code
147
+ end
148
+
149
+ log_msg.trace "MSG APPLIED", gas_remained: gas, sender: msg.sender, to: msg.to, data: dat
150
+ log_state.trace "MSG POST STATE SENDER", account: Utils.encode_hex(msg.sender), balance: get_balance(msg.sender), state: log_storage(msg.sender)
151
+ log_state.trace "MSG POST STATE RECIPIENT", account: Utils.encode_hex(msg.to), balance: get_balance(msg.to), state: log_storage(msg.to)
152
+
153
+ if res == 0
154
+ log_msg.debug 'REVERTING'
155
+ @block.revert snapshot
156
+ end
157
+
158
+ return res, gas, dat
159
+ end
160
+
161
+ private
162
+
163
+ def log_msg
164
+ @log_msg ||= Logger.new 'eth.external_call.msg'
165
+ end
166
+
167
+ def log_state
168
+ @log_state ||= Logger.new 'eth.external_call.state'
169
+ end
170
+
171
+ end
172
+
173
+ end