glueby 0.1.0 → 0.2.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 (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.