ruby-ethereum 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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