glueby 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-gemset +1 -1
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +3 -2
  5. data/README.md +27 -17
  6. data/glueby.gemspec +1 -1
  7. data/lib/generators/glueby/{initializer_generator.rb → contract/initializer_generator.rb} +0 -0
  8. data/lib/generators/glueby/contract/templates/initializer.rb.erb +3 -0
  9. data/lib/generators/glueby/contract/templates/key_table.rb.erb +15 -0
  10. data/lib/generators/glueby/{templates → contract/templates}/timestamp_table.rb.erb +2 -1
  11. data/lib/generators/glueby/contract/templates/utxo_table.rb.erb +15 -0
  12. data/lib/generators/glueby/contract/templates/wallet_table.rb.erb +10 -0
  13. data/lib/generators/glueby/contract/timestamp_generator.rb +26 -0
  14. data/lib/generators/glueby/contract/wallet_adapter_generator.rb +46 -0
  15. data/lib/glueby.rb +18 -1
  16. data/lib/glueby/contract.rb +3 -14
  17. data/lib/glueby/contract/active_record/timestamp.rb +8 -5
  18. data/lib/glueby/contract/errors.rb +6 -0
  19. data/lib/glueby/contract/payment.rb +54 -0
  20. data/lib/glueby/contract/timestamp.rb +39 -38
  21. data/lib/glueby/contract/token.rb +193 -0
  22. data/lib/glueby/contract/tx_builder.rb +197 -31
  23. data/lib/glueby/generator.rb +5 -0
  24. data/lib/glueby/generator/migrate_generator.rb +38 -0
  25. data/lib/glueby/internal.rb +6 -0
  26. data/lib/glueby/internal/rpc.rb +35 -0
  27. data/lib/glueby/internal/wallet.rb +122 -0
  28. data/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +131 -0
  29. data/lib/glueby/internal/wallet/active_record.rb +15 -0
  30. data/lib/glueby/internal/wallet/active_record/key.rb +72 -0
  31. data/lib/glueby/internal/wallet/active_record/utxo.rb +50 -0
  32. data/lib/glueby/internal/wallet/active_record/wallet.rb +54 -0
  33. data/lib/glueby/internal/wallet/active_record_wallet_adapter.rb +133 -0
  34. data/lib/glueby/internal/wallet/errors.rb +11 -0
  35. data/lib/glueby/internal/wallet/tapyrus_core_wallet_adapter.rb +158 -0
  36. data/lib/glueby/version.rb +1 -1
  37. data/lib/glueby/wallet.rb +51 -0
  38. data/lib/tasks/glueby/contract/timestamp.rake +5 -5
  39. data/lib/tasks/glueby/contract/wallet_adapter.rake +42 -0
  40. metadata +30 -10
  41. data/lib/generators/glueby/templates/initializer.rb.erb +0 -4
  42. data/lib/generators/glueby/timestamp_generator.rb +0 -57
  43. data/lib/glueby/contract/rpc.rb +0 -15
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Glueby
6
+ module Internal
7
+ class Wallet
8
+ # ActiveRecordWalletAdapter
9
+ #
10
+ # This class represents a wallet adapter that use Active Record to manage wallet and utxos.
11
+ # To use this wallet adapter, you should do the following steps:
12
+ #
13
+ # (1) Generate migrations for wallets, keys, utxos tables.
14
+ # The generator `glueby:contract:wallet_adapter` is available for migration.
15
+ # ```
16
+ # rails g glueby:contract:wallet_adapter
17
+ # ```
18
+ # this generator generates 3 files to create tables used by ActiveRecordWalletAdatper.
19
+ # then, migrate them
20
+ # ```
21
+ # $ rails db:migrate
22
+ # ```
23
+ #
24
+ # (2) Add configuration for activerecord
25
+ # ```
26
+ # config = {adapter: 'activerecord', schema: 'http', host: '127.0.0.1', port: 12381, user: 'user', password: 'pass'}
27
+ # Glueby::Wallet.configure(config)
28
+ # ```
29
+ #
30
+ # (3) Generate wallet and receive address
31
+ # ```
32
+ # alice_wallet = Glueby::Wallet.create
33
+ # address = alice_wallet.internal_wallet.receive_address
34
+ # ```
35
+ # `Glueby::Internal::Wallet#receive_address` returns Base58 encoded Tapyrus address like '1CY6TSSARn8rAFD9chCghX5B7j4PKR8S1a'.
36
+ #
37
+ # (4) Send TPC to created address and import into wallet.
38
+ # In general, ActiveRecordWalletAdapter handle only utxos generated by glueby.
39
+ # So, to start to use wallet, some external utxo should be imported into the wallet first.
40
+ # This step should be done by external transaction without Glueby::Wallet, such as 'sendtoaddress' or 'generatetoaddress' RPC command of Tapyrus Core
41
+ # ```
42
+ # $ tapyrus-cli sendtoaddress 1CY6TSSARn8rAFD9chCghX5B7j4PKR8S1a 1
43
+ # 1740af9f65ffd8537bdd06ccfa911bf1f4d6833222807d29c99d72b47838917d
44
+ # ```
45
+ #
46
+ # then, import into wallet by rake task `glueby:contract:wallet_adapter:import_tx` or `glueby:contract:wallet_adapter:import_block`
47
+ # ```
48
+ # $ rails "glueby:contract:wallet_adapter:import_tx[1740af9f65ffd8537bdd06ccfa911bf1f4d6833222807d29c99d72b47838917d]""
49
+ # ```
50
+ #
51
+ # (5) You are ready to use ActiveRecordWalletAdatper, check `Glueby::Internal::Wallet#list_unspent` or `Glueby::Wallet#balances`
52
+ # ```
53
+ # alice_wallet = Glueby::Wallet.create
54
+ # alice_wallet.balances
55
+ # ```
56
+ class ActiveRecordWalletAdapter < AbstractWalletAdapter
57
+ def create_wallet
58
+ wallet_id = SecureRandom.hex(16)
59
+ wallet = AR::Wallet.create(wallet_id: wallet_id)
60
+ wallet_id
61
+ end
62
+
63
+ def delete_wallet(wallet_id)
64
+ AR::Wallet.destroy_by(wallet_id: wallet_id)
65
+ end
66
+
67
+ def load_wallet(wallet_id)
68
+ end
69
+
70
+ def unload_wallet(wallet_id)
71
+ end
72
+
73
+ def wallets
74
+ AR::Wallet.all.map(&:wallet_id).sort
75
+ end
76
+
77
+ def balance(wallet_id, only_finalized = true)
78
+ wallet = AR::Wallet.find_by(wallet_id: wallet_id)
79
+ utxos = wallet.utxos
80
+ utxos = utxos.where(status: :finalized) if only_finalized
81
+ utxos.sum(&:value)
82
+ end
83
+
84
+ def list_unspent(wallet_id, only_finalized = true)
85
+ wallet = AR::Wallet.find_by(wallet_id: wallet_id)
86
+ utxos = wallet.utxos
87
+ utxos = utxos.where(status: :finalized) if only_finalized
88
+ utxos.map do |utxo|
89
+ {
90
+ txid: utxo.txid,
91
+ vout: utxo.index,
92
+ script_pubkey: utxo.script_pubkey,
93
+ color_id: utxo.color_id,
94
+ amount: utxo.value,
95
+ finalized: utxo.status == 'finalized'
96
+ }
97
+ end
98
+ end
99
+
100
+ def sign_tx(wallet_id, tx, prevtxs = [])
101
+ wallet = AR::Wallet.find_by(wallet_id: wallet_id)
102
+ wallet.sign(tx, prevtxs)
103
+ end
104
+
105
+ def broadcast(wallet_id, tx)
106
+ ::ActiveRecord::Base.transaction do
107
+ AR::Utxo.destroy_for_inputs(tx)
108
+ AR::Utxo.create_or_update_for_outputs(tx, status: :broadcasted)
109
+ Glueby::Internal::RPC.client.sendrawtransaction(tx.to_hex)
110
+ end
111
+ end
112
+
113
+ def receive_address(wallet_id)
114
+ wallet = AR::Wallet.find_by(wallet_id: wallet_id)
115
+ key = wallet.keys.create(purpose: :receive)
116
+ key.address
117
+ end
118
+
119
+ def change_address(wallet_id)
120
+ wallet = AR::Wallet.find_by(wallet_id: wallet_id)
121
+ key = wallet.keys.create(purpose: :change)
122
+ key.address
123
+ end
124
+
125
+ def create_pubkey(wallet_id)
126
+ wallet = AR::Wallet.find_by(wallet_id: wallet_id)
127
+ key = wallet.keys.create(purpose: :receive)
128
+ Tapyrus::Key.new(pubkey: key.public_key)
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,11 @@
1
+ module Glueby
2
+ module Internal
3
+ class Wallet
4
+ module Errors
5
+ class ShouldInitializeWalletAdapter < StandardError; end
6
+ class WalletUnloaded < StandardError; end
7
+ class WalletAlreadyLoaded < StandardError; end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'bigdecimal'
5
+
6
+ module Glueby
7
+ module Internal
8
+ class Wallet
9
+ class TapyrusCoreWalletAdapter < AbstractWalletAdapter
10
+ module Util
11
+ module_function
12
+
13
+ # Convert TPC to tapyrus. 1 TPC is 10**8 tapyrus.
14
+ # @param [String] tpc
15
+ # @return [Integer] tapyrus
16
+ #
17
+ # Example) "0.00000010" to 10.
18
+ def tpc_to_tapyrus(tpc)
19
+ (BigDecimal(tpc) * 10**8).to_i
20
+ end
21
+ end
22
+
23
+ include Glueby::Internal::Wallet::TapyrusCoreWalletAdapter::Util
24
+
25
+ WALLET_PREFIX = 'wallet-'
26
+ ADDRESS_TYPE = 'legacy'
27
+
28
+ RPC_WALLET_ERROR_ERROR_CODE = -4 # Unspecified problem with wallet (key not found etc.)
29
+ RPC_WALLET_NOT_FOUND_ERROR_CODE = -18 # Invalid wallet specified
30
+
31
+ def create_wallet
32
+ wallet_id = SecureRandom.hex(16)
33
+ RPC.client.createwallet(wallet_name(wallet_id))
34
+ wallet_id
35
+ end
36
+
37
+ # On TapyrusCoreWallet, there are no way to delete real wallet via RPC.
38
+ # So, here just unload.
39
+ def delete_wallet(wallet_id)
40
+ unload_wallet(wallet_id)
41
+ end
42
+
43
+ def load_wallet(wallet_id)
44
+ RPC.client.loadwallet(wallet_name(wallet_id))
45
+ rescue RuntimeError => ex
46
+ json = JSON.parse(ex.message)
47
+ if json.is_a?(Hash) && json['code'] == RPC_WALLET_ERROR_ERROR_CODE && /Duplicate -wallet filename specified/ =~ ex.message
48
+ raise Errors::WalletAlreadyLoaded, "Wallet #{wallet_id} has been already loaded."
49
+ else
50
+ raise ex
51
+ end
52
+ end
53
+
54
+ def unload_wallet(wallet_id)
55
+ RPC.client.unloadwallet(wallet_name(wallet_id))
56
+ end
57
+
58
+ def wallets
59
+ RPC.client.listwallets.map do |wallet_name|
60
+ match = /\A#{WALLET_PREFIX}(?<wallet_id>[0-9A-Fa-f]{32})\z/.match(wallet_name)
61
+ next unless match
62
+
63
+ match[:wallet_id]
64
+ end.compact
65
+ end
66
+
67
+ def balance(wallet_id, only_finalized = true)
68
+ perform_as(wallet_id) do |client|
69
+ confirmed = tpc_to_tapyrus(client.getbalance)
70
+ return confirmed if only_finalized
71
+
72
+ confirmed + tpc_to_tapyrus(client.getunconfirmedbalance)
73
+ end
74
+ end
75
+
76
+ def list_unspent(wallet_id, only_finalized = true)
77
+ perform_as(wallet_id) do |client|
78
+ min_conf = only_finalized ? 1 : 0
79
+ res = client.listunspent(min_conf)
80
+
81
+ res.map do |i|
82
+ script = Tapyrus::Script.parse_from_payload(i['scriptPubKey'].htb)
83
+ color_id = if script.cp2pkh? || script.cp2sh?
84
+ Tapyrus::Color::ColorIdentifier.parse_from_payload(script.chunks[0].pushed_data).to_hex
85
+ end
86
+ {
87
+ txid: i['txid'],
88
+ vout: i['vout'],
89
+ script_pubkey: i['scriptPubKey'],
90
+ color_id: color_id,
91
+ amount: tpc_to_tapyrus(i['amount']),
92
+ finalized: i['confirmations'] != 0
93
+ }
94
+ end
95
+ end
96
+ end
97
+
98
+ def sign_tx(wallet_id, tx, prevtxs = [])
99
+ perform_as(wallet_id) do |client|
100
+ res = client.signrawtransactionwithwallet(tx.to_hex, prevtxs)
101
+ if res['complete']
102
+ Tapyrus::Tx.parse_from_payload(res['hex'].htb)
103
+ else
104
+ raise res['errors'].to_json
105
+ end
106
+ end
107
+ end
108
+
109
+ def broadcast(wallet_id, tx)
110
+ perform_as(wallet_id) do |client|
111
+ client.sendrawtransaction(tx.to_hex)
112
+ end
113
+ end
114
+
115
+ def receive_address(wallet_id)
116
+ perform_as(wallet_id) do |client|
117
+ client.getnewaddress('', ADDRESS_TYPE)
118
+ end
119
+ end
120
+
121
+ def change_address(wallet_id)
122
+ perform_as(wallet_id) do |client|
123
+ client.getrawchangeaddress(ADDRESS_TYPE)
124
+ end
125
+ end
126
+
127
+ def create_pubkey(wallet_id)
128
+ perform_as(wallet_id) do |client|
129
+ address = client.getnewaddress('', ADDRESS_TYPE)
130
+ info = client.getaddressinfo(address)
131
+ Tapyrus::Key.new(pubkey: info['pubkey'])
132
+ end
133
+ end
134
+
135
+ private
136
+
137
+ def perform_as(wallet_id)
138
+ RPC.perform_as(wallet_name(wallet_id)) do |client|
139
+ begin
140
+ yield(client)
141
+ rescue RuntimeError => ex
142
+ json = JSON.parse(ex.message)
143
+ if json.is_a?(Hash) && json['code'] == RPC_WALLET_NOT_FOUND_ERROR_CODE
144
+ raise Errors::WalletUnloaded, "The wallet #{wallet_id} is unloaded. You should load before use it."
145
+ else
146
+ raise ex
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ def wallet_name(wallet_id)
153
+ "#{WALLET_PREFIX}#{wallet_id}"
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -1,3 +1,3 @@
1
1
  module Glueby
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glueby
4
+ class Wallet
5
+ attr_reader :internal_wallet
6
+
7
+ class << self
8
+ def create
9
+ new(Glueby::Internal::Wallet.create)
10
+ end
11
+
12
+ def load(wallet_id)
13
+ new(Glueby::Internal::Wallet.load(wallet_id))
14
+ end
15
+
16
+ def configure(config)
17
+ case config[:adapter]
18
+ when 'core'
19
+ Glueby::Internal::RPC.configure(config)
20
+ Glueby::Internal::Wallet.wallet_adapter = Glueby::Internal::Wallet::TapyrusCoreWalletAdapter.new
21
+ when 'activerecord'
22
+ Glueby::Internal::RPC.configure(config)
23
+ Glueby::Internal::Wallet.wallet_adapter = Glueby::Internal::Wallet::ActiveRecordWalletAdapter.new
24
+ else
25
+ raise 'Not implemented'
26
+ end
27
+ end
28
+ end
29
+
30
+ def id
31
+ @internal_wallet.id
32
+ end
33
+
34
+ # @return [HashMap] hash of balances which key is color_id or empty string, and value is amount
35
+ def balances
36
+ utxos = @internal_wallet.list_unspent
37
+ utxos.inject({}) do |balances, output|
38
+ key = output[:color_id] || ''
39
+ balances[key] ||= 0
40
+ balances[key] += output[:amount]
41
+ balances
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def initialize(internal_wallet)
48
+ @internal_wallet = internal_wallet
49
+ end
50
+ end
51
+ end
@@ -11,12 +11,12 @@ module Glueby
11
11
  timestamps.each do |t|
12
12
  begin
13
13
  ::ActiveRecord::Base.transaction do
14
- tx = create_tx(t.prefix, t.content_hash, Glueby::Contract::FixedFeeProvider.new)
15
- signed_tx = sign_tx(tx)
16
- t.update(txid: signed_tx.txid, status: :unconfirmed)
14
+ wallet = Glueby::Wallet.load(t.wallet_id)
15
+ tx = create_tx(wallet, t.prefix, t.content_hash, Glueby::Contract::FixedFeeProvider.new)
16
+ t.update(txid: tx.txid, status: :unconfirmed)
17
17
 
18
- broadcast_tx(signed_tx)
19
- puts "broadcasted (id=#{t.id}, txid=#{signed_tx.txid})"
18
+ wallet.internal_wallet.broadcast(tx)
19
+ puts "broadcasted (id=#{t.id}, txid=#{tx.txid})"
20
20
  end
21
21
  rescue => e
22
22
  puts "failed to broadcast (id=#{t.id}, reason=#{e.message})"
@@ -0,0 +1,42 @@
1
+ module Glueby
2
+ module Contract
3
+ module Task
4
+ module WalletAdapter
5
+ def import_block(block_hash)
6
+ block = Glueby::Internal::RPC.client.getblock(block_hash, 0)
7
+ block = Tapyrus::Block.parse_from_payload(block.htb)
8
+ block.transactions.each { |tx| import_tx(tx.txid) }
9
+ end
10
+
11
+ def import_tx(txid)
12
+ tx = Glueby::Internal::RPC.client.getrawtransaction(txid)
13
+ tx = Tapyrus::Tx.parse_from_payload(tx.htb)
14
+ Glueby::Internal::Wallet::AR::Utxo.destroy_for_inputs(tx)
15
+ Glueby::Internal::Wallet::AR::Utxo.create_or_update_for_outputs(tx)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ namespace :glueby do
23
+ namespace :contract do
24
+ namespace :wallet_adapter do
25
+ include Glueby::Contract::Task::WalletAdapter
26
+
27
+ desc 'import block into database'
28
+ task :import_block, [:block_hash] => [:environment] do |_, args|
29
+ block_hash = args[:block_hash]
30
+
31
+ ::ActiveRecord::Base.transaction { import_block(block_hash) }
32
+ end
33
+
34
+ desc 'import transaction into database'
35
+ task :import_tx, [:txid] => [:environment] do |_, args|
36
+ txid = args[:txid]
37
+
38
+ ::ActiveRecord::Base.transaction { import_tx(txid) }
39
+ end
40
+ end
41
+ end
42
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glueby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-07-03 00:00:00.000000000 Z
11
+ date: 2021-02-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tapyrus
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 0.2.6
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: 0.2.6
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activerecord
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -72,22 +72,42 @@ files:
72
72
  - bin/console
73
73
  - bin/setup
74
74
  - glueby.gemspec
75
- - lib/generators/glueby/initializer_generator.rb
76
- - lib/generators/glueby/templates/initializer.rb.erb
77
- - lib/generators/glueby/templates/timestamp_table.rb.erb
78
- - lib/generators/glueby/timestamp_generator.rb
75
+ - lib/generators/glueby/contract/initializer_generator.rb
76
+ - lib/generators/glueby/contract/templates/initializer.rb.erb
77
+ - lib/generators/glueby/contract/templates/key_table.rb.erb
78
+ - lib/generators/glueby/contract/templates/timestamp_table.rb.erb
79
+ - lib/generators/glueby/contract/templates/utxo_table.rb.erb
80
+ - lib/generators/glueby/contract/templates/wallet_table.rb.erb
81
+ - lib/generators/glueby/contract/timestamp_generator.rb
82
+ - lib/generators/glueby/contract/wallet_adapter_generator.rb
79
83
  - lib/glueby.rb
80
84
  - lib/glueby/contract.rb
81
85
  - lib/glueby/contract/active_record.rb
82
86
  - lib/glueby/contract/active_record/timestamp.rb
83
87
  - lib/glueby/contract/errors.rb
84
88
  - lib/glueby/contract/fee_provider.rb
85
- - lib/glueby/contract/rpc.rb
89
+ - lib/glueby/contract/payment.rb
86
90
  - lib/glueby/contract/timestamp.rb
91
+ - lib/glueby/contract/token.rb
87
92
  - lib/glueby/contract/tx_builder.rb
93
+ - lib/glueby/generator.rb
94
+ - lib/glueby/generator/migrate_generator.rb
95
+ - lib/glueby/internal.rb
96
+ - lib/glueby/internal/rpc.rb
97
+ - lib/glueby/internal/wallet.rb
98
+ - lib/glueby/internal/wallet/abstract_wallet_adapter.rb
99
+ - lib/glueby/internal/wallet/active_record.rb
100
+ - lib/glueby/internal/wallet/active_record/key.rb
101
+ - lib/glueby/internal/wallet/active_record/utxo.rb
102
+ - lib/glueby/internal/wallet/active_record/wallet.rb
103
+ - lib/glueby/internal/wallet/active_record_wallet_adapter.rb
104
+ - lib/glueby/internal/wallet/errors.rb
105
+ - lib/glueby/internal/wallet/tapyrus_core_wallet_adapter.rb
88
106
  - lib/glueby/version.rb
107
+ - lib/glueby/wallet.rb
89
108
  - lib/tasks/glueby/contract.rake
90
109
  - lib/tasks/glueby/contract/timestamp.rake
110
+ - lib/tasks/glueby/contract/wallet_adapter.rake
91
111
  homepage: https://github.com/chaintope/glueby
92
112
  licenses:
93
113
  - MIT
@@ -110,7 +130,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
130
  - !ruby/object:Gem::Version
111
131
  version: '0'
112
132
  requirements: []
113
- rubygems_version: 3.0.3
133
+ rubygems_version: 3.2.3
114
134
  signing_key:
115
135
  specification_version: 4
116
136
  summary: A Ruby library of smart contracts that can be used on Tapyrus.