glueby 0.2.0 → 0.4.2

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +35 -0
  3. data/Gemfile +1 -0
  4. data/README.md +224 -16
  5. data/glueby.gemspec +2 -2
  6. data/lib/generators/glueby/contract/block_syncer_generator.rb +26 -0
  7. data/lib/generators/glueby/contract/reissuable_token_generator.rb +26 -0
  8. data/lib/generators/glueby/contract/templates/initializer.rb.erb +4 -2
  9. data/lib/generators/glueby/contract/templates/key_table.rb.erb +4 -3
  10. data/lib/generators/glueby/contract/templates/reissuable_token_table.rb.erb +10 -0
  11. data/lib/generators/glueby/contract/templates/system_information_table.rb.erb +12 -0
  12. data/lib/generators/glueby/contract/templates/timestamp_table.rb.erb +1 -1
  13. data/lib/generators/glueby/contract/templates/utxo_table.rb.erb +3 -2
  14. data/lib/generators/glueby/contract/templates/wallet_table.rb.erb +2 -2
  15. data/lib/glueby.rb +28 -11
  16. data/lib/glueby/active_record.rb +8 -0
  17. data/lib/glueby/active_record/system_information.rb +15 -0
  18. data/lib/glueby/block_syncer.rb +98 -0
  19. data/lib/glueby/configuration.rb +62 -0
  20. data/lib/glueby/contract.rb +2 -2
  21. data/lib/glueby/contract/active_record.rb +1 -0
  22. data/lib/glueby/contract/active_record/reissuable_token.rb +26 -0
  23. data/lib/glueby/contract/fee_estimator.rb +38 -0
  24. data/lib/glueby/contract/payment.rb +10 -6
  25. data/lib/glueby/contract/timestamp.rb +8 -6
  26. data/lib/glueby/contract/timestamp/syncer.rb +13 -0
  27. data/lib/glueby/contract/token.rb +78 -26
  28. data/lib/glueby/contract/tx_builder.rb +23 -20
  29. data/lib/glueby/fee_provider.rb +73 -0
  30. data/lib/glueby/fee_provider/tasks.rb +141 -0
  31. data/lib/glueby/generator/migrate_generator.rb +1 -1
  32. data/lib/glueby/internal/wallet.rb +56 -13
  33. data/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +25 -6
  34. data/lib/glueby/internal/wallet/active_record/utxo.rb +1 -0
  35. data/lib/glueby/internal/wallet/active_record/wallet.rb +15 -5
  36. data/lib/glueby/internal/wallet/active_record_wallet_adapter.rb +25 -8
  37. data/lib/glueby/internal/wallet/active_record_wallet_adapter/syncer.rb +14 -0
  38. data/lib/glueby/internal/wallet/errors.rb +3 -0
  39. data/lib/glueby/internal/wallet/tapyrus_core_wallet_adapter.rb +42 -14
  40. data/lib/glueby/railtie.rb +14 -0
  41. data/lib/glueby/version.rb +1 -1
  42. data/lib/glueby/wallet.rb +3 -2
  43. data/lib/tasks/glueby/block_syncer.rake +29 -0
  44. data/lib/tasks/glueby/contract/timestamp.rake +4 -26
  45. data/lib/tasks/glueby/fee_provider.rake +18 -0
  46. metadata +26 -11
  47. data/.travis.yml +0 -7
  48. data/lib/glueby/contract/fee_provider.rb +0 -21
  49. data/lib/tasks/glueby/contract/wallet_adapter.rake +0 -42
@@ -0,0 +1,13 @@
1
+ module Glueby
2
+ module Contract
3
+ class Timestamp
4
+ class Syncer
5
+ def block_sync(block)
6
+ Glueby::Contract::AR::Timestamp
7
+ .where(txid: block.transactions.map(&:txid), status: :unconfirmed)
8
+ .update_all(status: :confirmed)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require 'active_record'
2
3
 
3
4
  module Glueby
4
5
  module Contract
@@ -13,13 +14,17 @@ module Glueby
13
14
  # alice = Glueby::Wallet.create
14
15
  # bob = Glueby::Wallet.create
15
16
  #
17
+ # Use `Glueby::Internal::Wallet#receive_address` to generate the address of bob
18
+ # bob.internal_wallet.receive_address
19
+ # => '1CY6TSSARn8rAFD9chCghX5B7j4PKR8S1a'
20
+ #
16
21
  # Issue
17
22
  # token = Token.issue!(issuer: alice, amount: 100)
18
23
  # token.amount(wallet: alice)
19
24
  # => 100
20
25
  #
21
26
  # Send
22
- # token.transfer!(sender: alice, receiver: bob, amount: 1)
27
+ # token.transfer!(sender: alice, receiver_address: '1CY6TSSARn8rAFD9chCghX5B7j4PKR8S1a', amount: 1)
23
28
  # token.amount(wallet: alice)
24
29
  # => 99
25
30
  # token.amount(wallet: bob)
@@ -51,14 +56,14 @@ module Glueby
51
56
  # @param issuer [Glueby::Wallet]
52
57
  # @param token_type [TokenTypes]
53
58
  # @param amount [Integer]
54
- # @return [Token] token
59
+ # @return [Array<token, Array<tx>>] Tuple of tx array and token object
55
60
  # @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
56
61
  # @raise [InvalidAmount] if amount is not positive integer.
57
62
  # @raise [UnspportedTokenType] if token is not supported.
58
63
  def issue!(issuer:, token_type: Tapyrus::Color::TokenTypes::REISSUABLE, amount: 1)
59
64
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
60
65
 
61
- txs, color_id, script_pubkey = case token_type
66
+ txs, color_id = case token_type
62
67
  when Tapyrus::Color::TokenTypes::REISSUABLE
63
68
  issue_reissuable_token(issuer: issuer, amount: amount)
64
69
  when Tapyrus::Color::TokenTypes::NON_REISSUABLE
@@ -68,33 +73,44 @@ module Glueby
68
73
  else
69
74
  raise Glueby::Contract::Errors::UnsupportedTokenType
70
75
  end
71
- txs.each { |tx| issuer.internal_wallet.broadcast(tx) }
72
- new(color_id: color_id, script_pubkey: script_pubkey)
76
+
77
+ [new(color_id: color_id), txs]
73
78
  end
74
79
 
75
80
  private
76
81
 
77
82
  def issue_reissuable_token(issuer:, amount:)
78
- estimated_fee = FixedFeeProvider.new.fee(Tapyrus::Tx.new)
79
- funding_tx = create_funding_tx(wallet: issuer, amount: estimated_fee)
80
- tx = create_issue_tx_for_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount)
83
+ funding_tx = create_funding_tx(wallet: issuer)
81
84
  script_pubkey = funding_tx.outputs.first.script_pubkey
82
85
  color_id = Tapyrus::Color::ColorIdentifier.reissuable(script_pubkey)
83
- [[funding_tx, tx], color_id, script_pubkey]
86
+
87
+ ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
88
+ # Store the script_pubkey for reissue the token.
89
+ Glueby::Contract::AR::ReissuableToken.create!(color_id: color_id.to_hex, script_pubkey: script_pubkey.to_hex)
90
+
91
+ funding_tx = issuer.internal_wallet.broadcast(funding_tx)
92
+ tx = create_issue_tx_for_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount)
93
+ tx = issuer.internal_wallet.broadcast(tx)
94
+ [[funding_tx, tx], color_id]
95
+ end
84
96
  end
85
97
 
86
98
  def issue_non_reissuable_token(issuer:, amount:)
87
99
  tx = create_issue_tx_for_non_reissuable_token(issuer: issuer, amount: amount)
100
+ tx = issuer.internal_wallet.broadcast(tx)
101
+
88
102
  out_point = tx.inputs.first.out_point
89
103
  color_id = Tapyrus::Color::ColorIdentifier.non_reissuable(out_point)
90
- [[tx], color_id, nil]
104
+ [[tx], color_id]
91
105
  end
92
106
 
93
107
  def issue_nft_token(issuer:)
94
108
  tx = create_issue_tx_for_nft_token(issuer: issuer)
109
+ tx = issuer.internal_wallet.broadcast(tx)
110
+
95
111
  out_point = tx.inputs.first.out_point
96
112
  color_id = Tapyrus::Color::ColorIdentifier.nft(out_point)
97
- [[tx], color_id, nil]
113
+ [[tx], color_id]
98
114
  end
99
115
  end
100
116
 
@@ -104,6 +120,7 @@ module Glueby
104
120
  # A wallet can issue the token only when it is REISSUABLE token.
105
121
  # @param issuer [Glueby::Wallet]
106
122
  # @param amount [Integer]
123
+ # @return [Array<String, tx>] Tuple of color_id and tx object
107
124
  # @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
108
125
  # @raise [InvalidAmount] if amount is not positive integer.
109
126
  # @raise [InvalidTokenType] if token is not reissuable.
@@ -111,28 +128,34 @@ module Glueby
111
128
  def reissue!(issuer:, amount:)
112
129
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
113
130
  raise Glueby::Contract::Errors::InvalidTokenType unless token_type == Tapyrus::Color::TokenTypes::REISSUABLE
114
- raise Glueby::Contract::Errors::UnknownScriptPubkey unless @script_pubkey
115
131
 
116
- estimated_fee = FixedFeeProvider.new.fee(Tapyrus::Tx.new)
117
- funding_tx = create_funding_tx(wallet: issuer, amount: estimated_fee, script: @script_pubkey)
118
- tx = create_reissue_tx(funding_tx: funding_tx, issuer: issuer, amount: amount, color_id: color_id)
119
- [funding_tx, tx].each { |tx| issuer.internal_wallet.broadcast(tx) }
132
+ if validate_reissuer(wallet: issuer)
133
+ funding_tx = create_funding_tx(wallet: issuer, script: @script_pubkey)
134
+ funding_tx = issuer.internal_wallet.broadcast(funding_tx)
135
+ tx = create_reissue_tx(funding_tx: funding_tx, issuer: issuer, amount: amount, color_id: color_id)
136
+ tx = issuer.internal_wallet.broadcast(tx)
137
+
138
+ [color_id, tx]
139
+ else
140
+ raise Glueby::Contract::Errors::UnknownScriptPubkey
141
+ end
120
142
  end
121
143
 
122
144
  # Send the token to other wallet
123
145
  #
124
146
  # @param sender [Glueby::Wallet] wallet to send this token
125
- # @param receiver [Glueby::Wallet] wallet to receive this token
147
+ # @param receiver_address [String] address to receive this token
126
148
  # @param amount [Integer]
127
- # @return [Token] receiver token
149
+ # @return [Array<String, tx>] Tuple of color_id and tx object
128
150
  # @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
129
151
  # @raise [InsufficientTokens] if wallet does not have enough token to send.
130
152
  # @raise [InvalidAmount] if amount is not positive integer.
131
- def transfer!(sender:, receiver:, amount: 1)
153
+ def transfer!(sender:, receiver_address:, amount: 1)
132
154
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
133
155
 
134
- tx = create_transfer_tx(color_id: color_id, sender: sender, receiver: receiver, amount: amount)
156
+ tx = create_transfer_tx(color_id: color_id, sender: sender, receiver_address: receiver_address, amount: amount)
135
157
  sender.internal_wallet.broadcast(tx)
158
+ [color_id, tx]
136
159
  end
137
160
 
138
161
  # Burn token
@@ -166,12 +189,19 @@ module Glueby
166
189
  color_id.type
167
190
  end
168
191
 
192
+ # Return the script_pubkey of the token from ActiveRecord
193
+ # @return [String] script_pubkey
194
+ def script_pubkey
195
+ @script_pubkey ||= Glueby::Contract::AR::ReissuableToken.script_pubkey(@color_id.to_hex)
196
+ end
197
+
169
198
  # Return serialized payload
170
199
  # @return [String] payload
171
200
  def to_payload
172
201
  payload = +''
173
202
  payload << @color_id.to_payload
174
- payload << @script_pubkey.to_payload if @script_pubkey
203
+ payload << @script_pubkey.to_payload if script_pubkey
204
+ payload
175
205
  end
176
206
 
177
207
  # Restore token from payload
@@ -180,14 +210,36 @@ module Glueby
180
210
  def self.parse_from_payload(payload)
181
211
  color_id, script_pubkey = payload.unpack('a33a*')
182
212
  color_id = Tapyrus::Color::ColorIdentifier.parse_from_payload(color_id)
183
- script_pubkey = Tapyrus::Script.parse_from_payload(script_pubkey) if script_pubkey
184
- new(color_id: color_id, script_pubkey: script_pubkey)
213
+ if color_id.type == Tapyrus::Color::TokenTypes::REISSUABLE
214
+ raise ArgumentError, 'script_pubkey should not be empty' if script_pubkey.empty?
215
+ script_pubkey = Tapyrus::Script.parse_from_payload(script_pubkey)
216
+ Glueby::Contract::AR::ReissuableToken.create!(color_id: color_id.to_hex, script_pubkey: script_pubkey.to_hex)
217
+ end
218
+ new(color_id: color_id)
185
219
  end
186
220
 
187
- def initialize(color_id:, script_pubkey:nil)
221
+ # Generate Token Instance
222
+ # @param color_id [String]
223
+ def initialize(color_id:)
188
224
  @color_id = color_id
189
- @script_pubkey = script_pubkey
225
+ end
226
+
227
+ private
228
+
229
+ # Verify that wallet is the issuer of the reissuable token
230
+ # reutrn [Boolean]
231
+ def validate_reissuer(wallet:)
232
+ addresses = wallet.internal_wallet.get_addresses
233
+ addresses.each do |address|
234
+ decoded_address = Tapyrus.decode_base58_address(address)
235
+ pubkey_hash_from_address = decoded_address[0]
236
+ pubkey_hash_from_script = Tapyrus::Script.parse_from_payload(script_pubkey.chunks[2])
237
+ if pubkey_hash_from_address == pubkey_hash_from_script.to_s
238
+ return true
239
+ end
240
+ end
241
+ false
190
242
  end
191
243
  end
192
244
  end
193
- end
245
+ end
@@ -3,26 +3,29 @@
3
3
  module Glueby
4
4
  module Contract
5
5
  module TxBuilder
6
+ # The amount of output in funding tx for Reissuable token.
7
+ FUNDING_TX_AMOUNT = 10_000
8
+
6
9
  def receive_address(wallet:)
7
10
  wallet.receive_address
8
11
  end
9
12
 
10
13
  # Create new public key, and new transaction that sends TPC to it
11
- def create_funding_tx(wallet:, amount:, script: nil, fee_provider: FixedFeeProvider.new)
14
+ def create_funding_tx(wallet:, script: nil, fee_estimator: FixedFeeEstimator.new)
12
15
  tx = Tapyrus::Tx.new
13
- fee = fee_provider.fee(dummy_tx(tx))
16
+ fee = fee_estimator.fee(dummy_tx(tx))
14
17
 
15
- sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(fee + amount)
18
+ sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(fee + FUNDING_TX_AMOUNT)
16
19
  fill_input(tx, outputs)
17
20
 
18
21
  receiver_script = script ? script : Tapyrus::Script.parse_from_addr(wallet.internal_wallet.receive_address)
19
- tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_script)
22
+ tx.outputs << Tapyrus::TxOut.new(value: FUNDING_TX_AMOUNT, script_pubkey: receiver_script)
20
23
 
21
- fill_change_tpc(tx, wallet, sum - fee - amount)
24
+ fill_change_tpc(tx, wallet, sum - fee - FUNDING_TX_AMOUNT)
22
25
  wallet.internal_wallet.sign_tx(tx)
23
26
  end
24
27
 
25
- def create_issue_tx_for_reissuable_token(funding_tx:, issuer:, amount:, fee_provider: FixedFeeProvider.new)
28
+ def create_issue_tx_for_reissuable_token(funding_tx:, issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
26
29
  tx = Tapyrus::Tx.new
27
30
 
28
31
  out_point = Tapyrus::OutPoint.from_txid(funding_tx.txid, 0)
@@ -34,7 +37,7 @@ module Glueby
34
37
  receiver_colored_script = receiver_script.add_color(color_id)
35
38
  tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_colored_script)
36
39
 
37
- fee = fee_provider.fee(dummy_tx(tx))
40
+ fee = fee_estimator.fee(dummy_tx(tx))
38
41
  fill_change_tpc(tx, issuer, output.value - fee)
39
42
  prev_txs = [{
40
43
  txid: funding_tx.txid,
@@ -45,18 +48,18 @@ module Glueby
45
48
  issuer.internal_wallet.sign_tx(tx, prev_txs)
46
49
  end
47
50
 
48
- def create_issue_tx_for_non_reissuable_token(issuer:, amount:, fee_provider: FixedFeeProvider.new)
49
- create_issue_tx_from_out_point(token_type: Tapyrus::Color::TokenTypes::NON_REISSUABLE, issuer: issuer, amount: amount, fee_provider: fee_provider)
51
+ def create_issue_tx_for_non_reissuable_token(issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
52
+ create_issue_tx_from_out_point(token_type: Tapyrus::Color::TokenTypes::NON_REISSUABLE, issuer: issuer, amount: amount, fee_estimator: fee_estimator)
50
53
  end
51
54
 
52
- def create_issue_tx_for_nft_token(issuer:, fee_provider: FixedFeeProvider.new)
53
- create_issue_tx_from_out_point(token_type: Tapyrus::Color::TokenTypes::NFT, issuer: issuer, amount: 1, fee_provider: fee_provider)
55
+ def create_issue_tx_for_nft_token(issuer:, fee_estimator: FixedFeeEstimator.new)
56
+ create_issue_tx_from_out_point(token_type: Tapyrus::Color::TokenTypes::NFT, issuer: issuer, amount: 1, fee_estimator: fee_estimator)
54
57
  end
55
58
 
56
- def create_issue_tx_from_out_point(token_type:, issuer:, amount:, fee_provider: FixedFeeProvider.new)
59
+ def create_issue_tx_from_out_point(token_type:, issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
57
60
  tx = Tapyrus::Tx.new
58
61
 
59
- fee = fee_provider.fee(dummy_issue_tx_from_out_point)
62
+ fee = fee_estimator.fee(dummy_issue_tx_from_out_point)
60
63
  sum, outputs = issuer.internal_wallet.collect_uncolored_outputs(fee)
61
64
  fill_input(tx, outputs)
62
65
 
@@ -78,7 +81,7 @@ module Glueby
78
81
  issuer.internal_wallet.sign_tx(tx)
79
82
  end
80
83
 
81
- def create_reissue_tx(funding_tx:, issuer:, amount:, color_id:, fee_provider: FixedFeeProvider.new)
84
+ def create_reissue_tx(funding_tx:, issuer:, amount:, color_id:, fee_estimator: FixedFeeEstimator.new)
82
85
  tx = Tapyrus::Tx.new
83
86
 
84
87
  out_point = Tapyrus::OutPoint.from_txid(funding_tx.txid, 0)
@@ -89,7 +92,7 @@ module Glueby
89
92
  receiver_colored_script = receiver_script.add_color(color_id)
90
93
  tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_colored_script)
91
94
 
92
- fee = fee_provider.fee(dummy_tx(tx))
95
+ fee = fee_estimator.fee(dummy_tx(tx))
93
96
  fill_change_tpc(tx, issuer, output.value - fee)
94
97
  prev_txs = [{
95
98
  txid: funding_tx.txid,
@@ -100,20 +103,20 @@ module Glueby
100
103
  issuer.internal_wallet.sign_tx(tx, prev_txs)
101
104
  end
102
105
 
103
- def create_transfer_tx(color_id:, sender:, receiver:, amount:, fee_provider: FixedFeeProvider.new)
106
+ def create_transfer_tx(color_id:, sender:, receiver_address:, amount:, fee_estimator: FixedFeeEstimator.new)
104
107
  tx = Tapyrus::Tx.new
105
108
 
106
109
  utxos = sender.internal_wallet.list_unspent
107
110
  sum_token, outputs = collect_colored_outputs(utxos, color_id, amount)
108
111
  fill_input(tx, outputs)
109
112
 
110
- receiver_script = Tapyrus::Script.parse_from_addr(receiver.internal_wallet.receive_address)
113
+ receiver_script = Tapyrus::Script.parse_from_addr(receiver_address)
111
114
  receiver_colored_script = receiver_script.add_color(color_id)
112
115
  tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_colored_script)
113
116
 
114
117
  fill_change_token(tx, sender, sum_token - amount, color_id)
115
118
 
116
- fee = fee_provider.fee(dummy_tx(tx))
119
+ fee = fee_estimator.fee(dummy_tx(tx))
117
120
  sum_tpc, outputs = sender.internal_wallet.collect_uncolored_outputs(fee)
118
121
  fill_input(tx, outputs)
119
122
 
@@ -121,7 +124,7 @@ module Glueby
121
124
  sender.internal_wallet.sign_tx(tx)
122
125
  end
123
126
 
124
- def create_burn_tx(color_id:, sender:, amount: 0, fee_provider: FixedFeeProvider.new)
127
+ def create_burn_tx(color_id:, sender:, amount: 0, fee_estimator: FixedFeeEstimator.new)
125
128
  tx = Tapyrus::Tx.new
126
129
 
127
130
  utxos = sender.internal_wallet.list_unspent
@@ -130,7 +133,7 @@ module Glueby
130
133
 
131
134
  fill_change_token(tx, sender, sum_token - amount, color_id) if amount.positive?
132
135
 
133
- fee = fee_provider.fee(dummy_tx(tx))
136
+ fee = fee_estimator.fee(dummy_tx(tx))
134
137
 
135
138
  dust = 600 # in case that the wallet has output which has just fee amount.
136
139
  sum_tpc, outputs = sender.internal_wallet.collect_uncolored_outputs(fee + dust)
@@ -0,0 +1,73 @@
1
+ module Glueby
2
+ class FeeProvider
3
+
4
+ autoload :Tasks, 'glueby/fee_provider/tasks'
5
+
6
+ class NoUtxosInUtxoPool < StandardError; end
7
+
8
+ WALLET_ID = 'FEE_PROVIDER_WALLET'
9
+ DEFAULT_FIXED_FEE = 1000
10
+ DEFAULT_UTXO_POOL_SIZE = 20
11
+
12
+ attr_reader :fixed_fee, :utxo_pool_size, :wallet,
13
+
14
+ class << self
15
+ attr_reader :config
16
+
17
+ # @param [Hash] config
18
+ # @option config [Integer] :fixed_fee The amount of fee which FeeProvider would provide to the txs
19
+ # @option opts [Integer] :utxo_pool_size The number of UTXOs in UTXO pool that is managed by FeeProvider
20
+ def configure(config)
21
+ @config = config
22
+ end
23
+
24
+ def provide(tx)
25
+ new.provide(tx)
26
+ end
27
+ end
28
+
29
+ def initialize
30
+ @wallet = begin
31
+ Internal::Wallet.load(WALLET_ID)
32
+ rescue Internal::Wallet::Errors::WalletNotFound => _
33
+ Internal::Wallet.create(WALLET_ID)
34
+ end
35
+
36
+ @fixed_fee = (FeeProvider.config && FeeProvider.config[:fixed_fee]) || DEFAULT_FIXED_FEE
37
+ @utxo_pool_size = (FeeProvider.config && FeeProvider.config[:utxo_pool_size]) || DEFAULT_UTXO_POOL_SIZE
38
+ end
39
+
40
+ # Provide an input for fee to the tx.
41
+ # @param [Tapyrus::Tx] tx - The tx that is provided fee as a input. It should be signed with ANYONECANPAY flag.
42
+ # @return [Tapyrus::Tx]
43
+ # @raise [ArgumentError] If the signatures that the tx inputs has don't have ANYONECANPAY flag.
44
+ # @raise [Glueby::FeeProvider::NoUtxosInUtxoPool] If there are no UTXOs for paying fee in FeeProvider's UTXO pool
45
+ def provide(tx)
46
+ tx.inputs.each do |txin|
47
+ sig = get_signature(txin.script_sig)
48
+ unless sig[-1].unpack1('C') & Tapyrus::SIGHASH_TYPE[:anyonecanpay] == Tapyrus::SIGHASH_TYPE[:anyonecanpay]
49
+ raise ArgumentError, 'All the signatures that the tx inputs has should have ANYONECANPAY flag.'
50
+ end
51
+ end
52
+
53
+ utxo = utxo_for_fee
54
+ out_point = Tapyrus::OutPoint.new(utxo[:txid].rhex, utxo[:vout])
55
+ tx.inputs << Tapyrus::TxIn.new(out_point: out_point)
56
+
57
+ wallet.sign_tx(tx, for_fee_provider_input: true)
58
+ end
59
+
60
+ private
61
+
62
+ def utxo_for_fee
63
+ utxo = wallet.list_unspent.select { |o| !o[:color_id] && o[:amount] == fixed_fee }.sample
64
+ raise NoUtxosInUtxoPool, 'No UTXOs in Fee Provider UTXO pool. UTXOs should be created with "glueby:fee_provider:manage_utxo_pool" rake task' unless utxo
65
+ utxo
66
+ end
67
+
68
+ # Get Signature from P2PKH or CP2PKH script sig
69
+ def get_signature(script_sig)
70
+ script_sig.chunks.first.pushed_data
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,141 @@
1
+ module Glueby
2
+ class FeeProvider
3
+ class Tasks
4
+ attr_reader :fee_provider
5
+
6
+ STATUS = {
7
+ # FeeProvider is ready to pay fees.
8
+ ready: 'Ready',
9
+ # FeeProvider is ready to pay fees, but it doesn't have enough amount to fill the UTXO pool by UTXOs which is for paying fees.
10
+ insufficient_amount: 'Insufficient Amount',
11
+ # FeeProvider is not ready to pay fees. It has no UTXOs for paying fee and amounts.
12
+ not_ready: 'Not Ready'
13
+ }
14
+
15
+ def initialize
16
+ @fee_provider = Glueby::FeeProvider.new
17
+ end
18
+
19
+ # Create UTXOs for paying fee from TPC amount of the wallet FeeProvider has. Then show the status.
20
+ #
21
+ # About the UTXO Pool
22
+ # FeeProvider have the UTXO pool. the pool is manged to keep some number of UTXOs that have fixed fee value. The
23
+ # value is configurable by :fixed_fee. This method do the management to the pool.
24
+ def manage_utxo_pool
25
+ txb = Tapyrus::TxBuilder.new
26
+
27
+ sum, utxos = collect_outputs
28
+ return if utxos.empty?
29
+
30
+ utxos.each { |utxo| txb.add_utxo(utxo) }
31
+ address = wallet.receive_address
32
+
33
+ shortage = [fee_provider.utxo_pool_size - current_utxo_pool_size, 0].max
34
+ can_create = (sum - fee_provider.fixed_fee) / fee_provider.fixed_fee
35
+ fee_outputs_count_to_be_created = [shortage, can_create].min
36
+
37
+ return if fee_outputs_count_to_be_created == 0
38
+
39
+ fee_outputs_count_to_be_created.times do
40
+ txb.pay(address, fee_provider.fixed_fee)
41
+ end
42
+
43
+ tx = txb.change_address(address)
44
+ .fee(fee_provider.fixed_fee)
45
+ .build
46
+ tx = wallet.sign_tx(tx)
47
+ wallet.broadcast(tx, without_fee_provider: true)
48
+ ensure
49
+ status
50
+ end
51
+
52
+ # Show the status of the UTXO pool
53
+ def status
54
+ status = :ready
55
+
56
+ if current_utxo_pool_size < fee_provider.utxo_pool_size
57
+ if tpc_amount < value_to_fill_utxo_pool
58
+ status = :insufficient_amount
59
+ message = <<~MESSAGE
60
+ 1. Please replenishment TPC which is for paying fee to FeeProvider.
61
+ FeeProvider needs #{value_to_fill_utxo_pool} tapyrus at least for paying 20 transaction fees.
62
+ FeeProvider wallet's address is '#{wallet.receive_address}'
63
+ 2. Then create UTXOs for paying in UTXO pool with 'rake glueby:fee_provider:manage_utxo_pool'
64
+ MESSAGE
65
+ else
66
+ message = "Please create UTXOs for paying in UTXO pool with 'rake glueby:fee_provider:manage_utxo_pool'\n"
67
+ end
68
+ end
69
+
70
+ status = :not_ready if current_utxo_pool_size == 0
71
+
72
+ puts <<~EOS
73
+ Status: #{STATUS[status]}
74
+ TPC amount: #{delimit(tpc_amount)}
75
+ UTXO pool size: #{delimit(current_utxo_pool_size)}
76
+ #{"\n" if message}#{message}
77
+ Configuration:
78
+ fixed_fee = #{delimit(fee_provider.fixed_fee)}
79
+ utxo_pool_size = #{delimit(fee_provider.utxo_pool_size)}
80
+ EOS
81
+ end
82
+
83
+ # Show the address of Fee Provider
84
+ def address
85
+ puts wallet.receive_address
86
+ end
87
+
88
+ private
89
+
90
+ def check_wallet_amount!
91
+ if tpc_amount < fee_provider.fixed_fee
92
+ raise InsufficientTPC, <<~MESSAGE
93
+ FeeProvider has insufficient TPC to create fee outputs to fill the UTXO pool.
94
+ 1. Please replenishment TPC which is for paying fee to FeeProvider. FeeProvider needs #{fee_provider.utxo_pool_size * fee_provider.fixed_fee} tapyrus at least. FeeProvider wallet's address is '#{wallet.receive_address}'
95
+ 2. Then create UTXOs for paying in UTXO pool with 'rake glueby:fee_provider:manage_utxo_pool'
96
+ MESSAGE
97
+ end
98
+ end
99
+
100
+ def tpc_amount
101
+ wallet.balance(false)
102
+ end
103
+
104
+ def collect_outputs
105
+ wallet.list_unspent.inject([0, []]) do |sum, output|
106
+ next sum if output[:color_id] || output[:amount] == fee_provider.fixed_fee
107
+
108
+ new_sum = sum[0] + output[:amount]
109
+ new_outputs = sum[1] << {
110
+ txid: output[:txid],
111
+ script_pubkey: output[:script_pubkey],
112
+ value: output[:amount],
113
+ index: output[:vout] ,
114
+ finalized: output[:finalized]
115
+ }
116
+ return [new_sum, new_outputs] if new_sum >= value_to_fill_utxo_pool
117
+
118
+ [new_sum, new_outputs]
119
+ end
120
+ end
121
+
122
+ def current_utxo_pool_size
123
+ wallet
124
+ .list_unspent(false)
125
+ .count { |o| !o[:color_id] && o[:amount] == fee_provider.fixed_fee }
126
+ end
127
+
128
+ def value_to_fill_utxo_pool
129
+ fee_provider.fixed_fee * (fee_provider.utxo_pool_size + 1) # +1 is for paying fee
130
+ end
131
+
132
+ def wallet
133
+ fee_provider.wallet
134
+ end
135
+
136
+ def delimit(num)
137
+ num.to_s.reverse.scan(/.{1,3}/).join('_').reverse
138
+ end
139
+ end
140
+ end
141
+ end