glueby 0.4.4 → 0.5.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.
- checksums.yaml +4 -4
- data/README.md +463 -387
- data/glueby.gemspec +33 -33
- data/lib/generators/glueby/contract/templates/initializer.rb.erb +8 -8
- data/lib/generators/glueby/contract/templates/key_table.rb.erb +16 -16
- data/lib/generators/glueby/contract/templates/utxo_table.rb.erb +16 -16
- data/lib/glueby/block_syncer.rb +97 -97
- data/lib/glueby/configuration.rb +26 -2
- data/lib/glueby/contract/timestamp/syncer.rb +13 -13
- data/lib/glueby/contract/timestamp.rb +108 -102
- data/lib/glueby/contract/token.rb +270 -244
- data/lib/glueby/contract/tx_builder.rb +97 -30
- data/lib/glueby/fee_provider/tasks.rb +140 -140
- data/lib/glueby/fee_provider.rb +11 -0
- data/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +151 -151
- data/lib/glueby/internal/wallet/active_record/utxo.rb +51 -51
- data/lib/glueby/internal/wallet/active_record_wallet_adapter/syncer.rb +13 -13
- data/lib/glueby/internal/wallet/active_record_wallet_adapter.rb +151 -151
- data/lib/glueby/internal/wallet/tapyrus_core_wallet_adapter.rb +186 -186
- data/lib/glueby/internal/wallet.rb +163 -162
- data/lib/glueby/railtie.rb +10 -9
- data/lib/glueby/utxo_provider/tasks.rb +135 -0
- data/lib/glueby/utxo_provider.rb +85 -0
- data/lib/glueby/version.rb +3 -3
- data/lib/glueby.rb +40 -39
- data/lib/tasks/glueby/block_syncer.rake +28 -28
- data/lib/tasks/glueby/contract/timestamp.rake +46 -39
- data/lib/tasks/glueby/fee_provider.rake +18 -18
- data/lib/tasks/glueby/utxo_provider.rake +18 -0
- metadata +5 -2
@@ -1,162 +1,163 @@
|
|
1
|
-
module Glueby
|
2
|
-
module Internal
|
3
|
-
# # Glueby::Internal::Wallet
|
4
|
-
#
|
5
|
-
# This module provides the way to deal about wallet that includes key management, address management, getting UTXOs.
|
6
|
-
#
|
7
|
-
# ## How to use
|
8
|
-
#
|
9
|
-
# First, you need to configure which wallet implementation is used in Glueby::Internal::Wallet. For now, below wallets are
|
10
|
-
# supported.
|
11
|
-
#
|
12
|
-
# * [Tapyrus Core](https://github.com/chaintope/tapyrus-core)
|
13
|
-
#
|
14
|
-
# Here shows an example to use Tapyrus Core wallet.
|
15
|
-
#
|
16
|
-
# ```ruby
|
17
|
-
# # Setup Tapyrus Core RPC connection
|
18
|
-
# config = {schema: 'http', host: '127.0.0.1', port: 12381, user: 'user', password: 'pass'}
|
19
|
-
# Glueby::Internal::RPC.configure(config)
|
20
|
-
#
|
21
|
-
# # Setup wallet adapter
|
22
|
-
# Glueby::Internal::Wallet.wallet_adapter = Glueby::Internal::Wallet::TapyrusCoreWalletAdapter.new
|
23
|
-
#
|
24
|
-
# # Create wallet
|
25
|
-
# wallet = Glueby::Internal::Wallet.create
|
26
|
-
# wallet.balance # => 0
|
27
|
-
# wallet.list_unspent
|
28
|
-
# ```
|
29
|
-
class Wallet
|
30
|
-
autoload :AbstractWalletAdapter, 'glueby/internal/wallet/abstract_wallet_adapter'
|
31
|
-
autoload :AR, 'glueby/internal/wallet/active_record'
|
32
|
-
autoload :TapyrusCoreWalletAdapter, 'glueby/internal/wallet/tapyrus_core_wallet_adapter'
|
33
|
-
autoload :ActiveRecordWalletAdapter, 'glueby/internal/wallet/active_record_wallet_adapter'
|
34
|
-
autoload :Errors, 'glueby/internal/wallet/errors'
|
35
|
-
|
36
|
-
class << self
|
37
|
-
def create(wallet_id = nil)
|
38
|
-
begin
|
39
|
-
wallet_id = wallet_adapter.create_wallet(wallet_id)
|
40
|
-
rescue Errors::WalletAlreadyCreated => _
|
41
|
-
# Ignore when wallet is already created.
|
42
|
-
end
|
43
|
-
new(wallet_id)
|
44
|
-
end
|
45
|
-
|
46
|
-
def load(wallet_id)
|
47
|
-
begin
|
48
|
-
wallet_adapter.load_wallet(wallet_id)
|
49
|
-
rescue Errors::WalletAlreadyLoaded => _
|
50
|
-
# Ignore when wallet is already loaded.
|
51
|
-
end
|
52
|
-
new(wallet_id)
|
53
|
-
end
|
54
|
-
|
55
|
-
def wallets
|
56
|
-
wallet_adapter.wallets.map { |id| new(id) }
|
57
|
-
end
|
58
|
-
|
59
|
-
def wallet_adapter=(adapter)
|
60
|
-
if adapter.is_a?(ActiveRecordWalletAdapter)
|
61
|
-
BlockSyncer.register_syncer(ActiveRecordWalletAdapter::Syncer)
|
62
|
-
else
|
63
|
-
BlockSyncer.unregister_syncer(ActiveRecordWalletAdapter::Syncer)
|
64
|
-
end
|
65
|
-
|
66
|
-
@wallet_adapter = adapter
|
67
|
-
end
|
68
|
-
|
69
|
-
def wallet_adapter
|
70
|
-
@wallet_adapter or
|
71
|
-
raise Errors::ShouldInitializeWalletAdapter, 'You should initialize wallet adapter using `Glueby::Internal::Wallet.wallet_adapter = some wallet adapter instance`.'
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
attr_reader :id
|
76
|
-
|
77
|
-
def initialize(wallet_id)
|
78
|
-
@id = wallet_id
|
79
|
-
end
|
80
|
-
|
81
|
-
def balance(only_finalized = true)
|
82
|
-
wallet_adapter.balance(id, only_finalized)
|
83
|
-
end
|
84
|
-
|
85
|
-
# @param only_finalized [Boolean] The flag to get a UTXO with status only finalized
|
86
|
-
# @param label [String] This label is used to filtered the UTXOs with labeled if a key or Utxo is labeled.
|
87
|
-
# - If label is not specified (label=nil), all UTXOs will be returned.
|
88
|
-
# - If label=:unlabeled, only unlabeled UTXOs will be returned.
|
89
|
-
def list_unspent(only_finalized = true, label = nil)
|
90
|
-
wallet_adapter.list_unspent(id, only_finalized, label)
|
91
|
-
end
|
92
|
-
|
93
|
-
def delete
|
94
|
-
wallet_adapter.delete_wallet(id)
|
95
|
-
end
|
96
|
-
|
97
|
-
# @param [Tapyrus::Tx] tx The tx that is signed
|
98
|
-
# @param [Array<Hash>] prev_txs An array of hash that represents unbroadcasted transaction outputs used by signing tx
|
99
|
-
# @option prev_txs [String] :txid
|
100
|
-
# @option prev_txs [Integer] :vout
|
101
|
-
# @option prev_txs [String] :scriptPubkey
|
102
|
-
# @option prev_txs [Integer] :amount
|
103
|
-
# @param [Boolean] for_fee_provider_input The flag to notify whether the caller is FeeProvider and called for signing a input that is by FeeProvider.
|
104
|
-
def sign_tx(tx, prev_txs = [], for_fee_provider_input: false)
|
105
|
-
sighashtype = Tapyrus::SIGHASH_TYPE[:all]
|
106
|
-
|
107
|
-
if !for_fee_provider_input && Glueby.configuration.fee_provider_bears?
|
108
|
-
sighashtype |= Tapyrus::SIGHASH_TYPE[:anyonecanpay]
|
109
|
-
end
|
110
|
-
|
111
|
-
wallet_adapter.sign_tx(id, tx, prev_txs, sighashtype: sighashtype)
|
112
|
-
end
|
113
|
-
|
114
|
-
# Broadcast a transaction via Tapyrus Core RPC
|
115
|
-
# @param [Tapyrus::Tx] tx The tx that would be broadcasted
|
116
|
-
# @option [Boolean] without_fee_provider The flag to avoid to use FeeProvider temporary.
|
117
|
-
# @param [Proc] block The block that is called before broadcasting. It can be used to handle tx that is modified by FeeProvider.
|
118
|
-
def broadcast(tx, without_fee_provider: false, &block)
|
119
|
-
tx = FeeProvider.provide(tx) if !without_fee_provider && Glueby.configuration.fee_provider_bears?
|
120
|
-
wallet_adapter.broadcast(id, tx, &block)
|
121
|
-
tx
|
122
|
-
end
|
123
|
-
|
124
|
-
def receive_address(label = nil)
|
125
|
-
wallet_adapter.receive_address(id, label)
|
126
|
-
end
|
127
|
-
|
128
|
-
def change_address
|
129
|
-
wallet_adapter.change_address(id)
|
130
|
-
end
|
131
|
-
|
132
|
-
def create_pubkey
|
133
|
-
wallet_adapter.create_pubkey(id)
|
134
|
-
end
|
135
|
-
|
136
|
-
def collect_uncolored_outputs(amount, label = nil, only_finalized = true)
|
137
|
-
utxos = list_unspent(only_finalized, label)
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
end
|
1
|
+
module Glueby
|
2
|
+
module Internal
|
3
|
+
# # Glueby::Internal::Wallet
|
4
|
+
#
|
5
|
+
# This module provides the way to deal about wallet that includes key management, address management, getting UTXOs.
|
6
|
+
#
|
7
|
+
# ## How to use
|
8
|
+
#
|
9
|
+
# First, you need to configure which wallet implementation is used in Glueby::Internal::Wallet. For now, below wallets are
|
10
|
+
# supported.
|
11
|
+
#
|
12
|
+
# * [Tapyrus Core](https://github.com/chaintope/tapyrus-core)
|
13
|
+
#
|
14
|
+
# Here shows an example to use Tapyrus Core wallet.
|
15
|
+
#
|
16
|
+
# ```ruby
|
17
|
+
# # Setup Tapyrus Core RPC connection
|
18
|
+
# config = {schema: 'http', host: '127.0.0.1', port: 12381, user: 'user', password: 'pass'}
|
19
|
+
# Glueby::Internal::RPC.configure(config)
|
20
|
+
#
|
21
|
+
# # Setup wallet adapter
|
22
|
+
# Glueby::Internal::Wallet.wallet_adapter = Glueby::Internal::Wallet::TapyrusCoreWalletAdapter.new
|
23
|
+
#
|
24
|
+
# # Create wallet
|
25
|
+
# wallet = Glueby::Internal::Wallet.create
|
26
|
+
# wallet.balance # => 0
|
27
|
+
# wallet.list_unspent
|
28
|
+
# ```
|
29
|
+
class Wallet
|
30
|
+
autoload :AbstractWalletAdapter, 'glueby/internal/wallet/abstract_wallet_adapter'
|
31
|
+
autoload :AR, 'glueby/internal/wallet/active_record'
|
32
|
+
autoload :TapyrusCoreWalletAdapter, 'glueby/internal/wallet/tapyrus_core_wallet_adapter'
|
33
|
+
autoload :ActiveRecordWalletAdapter, 'glueby/internal/wallet/active_record_wallet_adapter'
|
34
|
+
autoload :Errors, 'glueby/internal/wallet/errors'
|
35
|
+
|
36
|
+
class << self
|
37
|
+
def create(wallet_id = nil)
|
38
|
+
begin
|
39
|
+
wallet_id = wallet_adapter.create_wallet(wallet_id)
|
40
|
+
rescue Errors::WalletAlreadyCreated => _
|
41
|
+
# Ignore when wallet is already created.
|
42
|
+
end
|
43
|
+
new(wallet_id)
|
44
|
+
end
|
45
|
+
|
46
|
+
def load(wallet_id)
|
47
|
+
begin
|
48
|
+
wallet_adapter.load_wallet(wallet_id)
|
49
|
+
rescue Errors::WalletAlreadyLoaded => _
|
50
|
+
# Ignore when wallet is already loaded.
|
51
|
+
end
|
52
|
+
new(wallet_id)
|
53
|
+
end
|
54
|
+
|
55
|
+
def wallets
|
56
|
+
wallet_adapter.wallets.map { |id| new(id) }
|
57
|
+
end
|
58
|
+
|
59
|
+
def wallet_adapter=(adapter)
|
60
|
+
if adapter.is_a?(ActiveRecordWalletAdapter)
|
61
|
+
BlockSyncer.register_syncer(ActiveRecordWalletAdapter::Syncer)
|
62
|
+
else
|
63
|
+
BlockSyncer.unregister_syncer(ActiveRecordWalletAdapter::Syncer)
|
64
|
+
end
|
65
|
+
|
66
|
+
@wallet_adapter = adapter
|
67
|
+
end
|
68
|
+
|
69
|
+
def wallet_adapter
|
70
|
+
@wallet_adapter or
|
71
|
+
raise Errors::ShouldInitializeWalletAdapter, 'You should initialize wallet adapter using `Glueby::Internal::Wallet.wallet_adapter = some wallet adapter instance`.'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
attr_reader :id
|
76
|
+
|
77
|
+
def initialize(wallet_id)
|
78
|
+
@id = wallet_id
|
79
|
+
end
|
80
|
+
|
81
|
+
def balance(only_finalized = true)
|
82
|
+
wallet_adapter.balance(id, only_finalized)
|
83
|
+
end
|
84
|
+
|
85
|
+
# @param only_finalized [Boolean] The flag to get a UTXO with status only finalized
|
86
|
+
# @param label [String] This label is used to filtered the UTXOs with labeled if a key or Utxo is labeled.
|
87
|
+
# - If label is not specified (label=nil), all UTXOs will be returned.
|
88
|
+
# - If label=:unlabeled, only unlabeled UTXOs will be returned.
|
89
|
+
def list_unspent(only_finalized = true, label = nil)
|
90
|
+
wallet_adapter.list_unspent(id, only_finalized, label)
|
91
|
+
end
|
92
|
+
|
93
|
+
def delete
|
94
|
+
wallet_adapter.delete_wallet(id)
|
95
|
+
end
|
96
|
+
|
97
|
+
# @param [Tapyrus::Tx] tx The tx that is signed
|
98
|
+
# @param [Array<Hash>] prev_txs An array of hash that represents unbroadcasted transaction outputs used by signing tx
|
99
|
+
# @option prev_txs [String] :txid
|
100
|
+
# @option prev_txs [Integer] :vout
|
101
|
+
# @option prev_txs [String] :scriptPubkey
|
102
|
+
# @option prev_txs [Integer] :amount
|
103
|
+
# @param [Boolean] for_fee_provider_input The flag to notify whether the caller is FeeProvider and called for signing a input that is by FeeProvider.
|
104
|
+
def sign_tx(tx, prev_txs = [], for_fee_provider_input: false)
|
105
|
+
sighashtype = Tapyrus::SIGHASH_TYPE[:all]
|
106
|
+
|
107
|
+
if !for_fee_provider_input && Glueby.configuration.fee_provider_bears?
|
108
|
+
sighashtype |= Tapyrus::SIGHASH_TYPE[:anyonecanpay]
|
109
|
+
end
|
110
|
+
|
111
|
+
wallet_adapter.sign_tx(id, tx, prev_txs, sighashtype: sighashtype)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Broadcast a transaction via Tapyrus Core RPC
|
115
|
+
# @param [Tapyrus::Tx] tx The tx that would be broadcasted
|
116
|
+
# @option [Boolean] without_fee_provider The flag to avoid to use FeeProvider temporary.
|
117
|
+
# @param [Proc] block The block that is called before broadcasting. It can be used to handle tx that is modified by FeeProvider.
|
118
|
+
def broadcast(tx, without_fee_provider: false, &block)
|
119
|
+
tx = FeeProvider.provide(tx) if !without_fee_provider && Glueby.configuration.fee_provider_bears?
|
120
|
+
wallet_adapter.broadcast(id, tx, &block)
|
121
|
+
tx
|
122
|
+
end
|
123
|
+
|
124
|
+
def receive_address(label = nil)
|
125
|
+
wallet_adapter.receive_address(id, label)
|
126
|
+
end
|
127
|
+
|
128
|
+
def change_address
|
129
|
+
wallet_adapter.change_address(id)
|
130
|
+
end
|
131
|
+
|
132
|
+
def create_pubkey
|
133
|
+
wallet_adapter.create_pubkey(id)
|
134
|
+
end
|
135
|
+
|
136
|
+
def collect_uncolored_outputs(amount, label = nil, only_finalized = true, shuffle = false)
|
137
|
+
utxos = list_unspent(only_finalized, label)
|
138
|
+
utxos.shuffle! if shuffle
|
139
|
+
|
140
|
+
utxos.inject([0, []]) do |sum, output|
|
141
|
+
next sum if output[:color_id]
|
142
|
+
|
143
|
+
new_sum = sum[0] + output[:amount]
|
144
|
+
new_outputs = sum[1] << output
|
145
|
+
return [new_sum, new_outputs] if new_sum >= amount
|
146
|
+
|
147
|
+
[new_sum, new_outputs]
|
148
|
+
end
|
149
|
+
raise Glueby::Contract::Errors::InsufficientFunds
|
150
|
+
end
|
151
|
+
|
152
|
+
def get_addresses(label = nil)
|
153
|
+
wallet_adapter.get_addresses(id, label)
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def wallet_adapter
|
159
|
+
self.class.wallet_adapter
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
data/lib/glueby/railtie.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
-
module Glueby
|
2
|
-
class Railtie < ::Rails::Railtie
|
3
|
-
rake_tasks do
|
4
|
-
load "tasks/glueby/contract.rake"
|
5
|
-
load "tasks/glueby/contract/timestamp.rake"
|
6
|
-
load "tasks/glueby/block_syncer.rake"
|
7
|
-
load "tasks/glueby/fee_provider.rake"
|
8
|
-
|
9
|
-
|
1
|
+
module Glueby
|
2
|
+
class Railtie < ::Rails::Railtie
|
3
|
+
rake_tasks do
|
4
|
+
load "tasks/glueby/contract.rake"
|
5
|
+
load "tasks/glueby/contract/timestamp.rake"
|
6
|
+
load "tasks/glueby/block_syncer.rake"
|
7
|
+
load "tasks/glueby/fee_provider.rake"
|
8
|
+
load "tasks/glueby/utxo_provider.rake"
|
9
|
+
end
|
10
|
+
end
|
10
11
|
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module Glueby
|
2
|
+
class UtxoProvider
|
3
|
+
class Tasks
|
4
|
+
include Glueby::Contract::TxBuilder
|
5
|
+
|
6
|
+
attr_reader :utxo_provider
|
7
|
+
|
8
|
+
STATUS = {
|
9
|
+
# UtxoProvider is ready to pay tpcs.
|
10
|
+
ready: 'Ready',
|
11
|
+
# UtxoProvider is ready to pay tpcs, but it doesn't have enough amount to fill the UTXO pool by UTXOs which is for paying tpcs.
|
12
|
+
insufficient_amount: 'Insufficient Amount',
|
13
|
+
# UtxoProvider is not ready to pay tpcs. It has no UTXOs for paying amounts.
|
14
|
+
not_ready: 'Not Ready'
|
15
|
+
}
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@utxo_provider = Glueby::UtxoProvider.new
|
19
|
+
end
|
20
|
+
|
21
|
+
# Create UTXOs for paying tpc
|
22
|
+
#
|
23
|
+
# UtxoProvider have the UTXO pool. the pool is manged to keep some number of UTXOs that have fixed value. The
|
24
|
+
# value is configurable by :default_value. This method do the management to the pool.
|
25
|
+
def manage_utxo_pool
|
26
|
+
txb = Tapyrus::TxBuilder.new
|
27
|
+
|
28
|
+
sum, utxos = collect_outputs
|
29
|
+
return if utxos.empty?
|
30
|
+
|
31
|
+
utxos.each { |utxo| txb.add_utxo(utxo) }
|
32
|
+
address = wallet.receive_address
|
33
|
+
|
34
|
+
shortage = [utxo_provider.utxo_pool_size - current_utxo_pool_size, 0].max
|
35
|
+
return if shortage == 0
|
36
|
+
|
37
|
+
added_outputs = 0
|
38
|
+
shortage.times do
|
39
|
+
fee = utxo_provider.fee_estimator.fee(dummy_tx(txb.build))
|
40
|
+
break if (sum - fee) < utxo_provider.default_value
|
41
|
+
txb.pay(address, utxo_provider.default_value)
|
42
|
+
sum -= utxo_provider.default_value
|
43
|
+
added_outputs += 1
|
44
|
+
end
|
45
|
+
|
46
|
+
return if added_outputs == 0
|
47
|
+
|
48
|
+
fee = utxo_provider.fee_estimator.fee(dummy_tx(txb.build))
|
49
|
+
tx = txb.change_address(address)
|
50
|
+
.fee(fee)
|
51
|
+
.build
|
52
|
+
tx = wallet.sign_tx(tx)
|
53
|
+
wallet.broadcast(tx)
|
54
|
+
ensure
|
55
|
+
status
|
56
|
+
end
|
57
|
+
|
58
|
+
# Show the status of the UTXO pool
|
59
|
+
def status
|
60
|
+
status = :ready
|
61
|
+
|
62
|
+
if current_utxo_pool_size < utxo_provider.utxo_pool_size
|
63
|
+
if tpc_amount < value_to_fill_utxo_pool
|
64
|
+
status = :insufficient_amount
|
65
|
+
message = <<~MESSAGE
|
66
|
+
1. Please replenishment TPC which is for paying tpc to UtxoProvider.
|
67
|
+
UtxoProvider needs #{value_to_fill_utxo_pool} tapyrus in UTXO pool.
|
68
|
+
UtxoProvider wallet's address is '#{wallet.receive_address}'
|
69
|
+
2. Then create UTXOs for paying in UTXO pool with 'rake glueby:utxo_provider:manage_utxo_pool'
|
70
|
+
MESSAGE
|
71
|
+
else
|
72
|
+
message = "Please create UTXOs for paying in UTXO pool with 'rake glueby:utxo_provider:manage_utxo_pool'\n"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
status = :not_ready if current_utxo_pool_size == 0
|
77
|
+
|
78
|
+
puts <<~EOS
|
79
|
+
Status: #{STATUS[status]}
|
80
|
+
TPC amount: #{delimit(tpc_amount)}
|
81
|
+
UTXO pool size: #{delimit(current_utxo_pool_size)}
|
82
|
+
#{"\n" if message}#{message}
|
83
|
+
Configuration:
|
84
|
+
default_value = #{delimit(utxo_provider.default_value)}
|
85
|
+
utxo_pool_size = #{delimit(utxo_provider.utxo_pool_size)}
|
86
|
+
EOS
|
87
|
+
end
|
88
|
+
|
89
|
+
# Show the address of Utxo Provider
|
90
|
+
def print_address
|
91
|
+
puts wallet.receive_address
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def tpc_amount
|
97
|
+
wallet.balance(false)
|
98
|
+
end
|
99
|
+
|
100
|
+
def collect_outputs
|
101
|
+
wallet.list_unspent.inject([0, []]) do |sum, output|
|
102
|
+
next sum if output[:color_id] || output[:amount] == utxo_provider.default_value
|
103
|
+
|
104
|
+
new_sum = sum[0] + output[:amount]
|
105
|
+
new_outputs = sum[1] << {
|
106
|
+
txid: output[:txid],
|
107
|
+
script_pubkey: output[:script_pubkey],
|
108
|
+
value: output[:amount],
|
109
|
+
index: output[:vout] ,
|
110
|
+
finalized: output[:finalized]
|
111
|
+
}
|
112
|
+
[new_sum, new_outputs]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def current_utxo_pool_size
|
117
|
+
wallet
|
118
|
+
.list_unspent(false)
|
119
|
+
.count { |o| !o[:color_id] && o[:amount] == utxo_provider.default_value }
|
120
|
+
end
|
121
|
+
|
122
|
+
def value_to_fill_utxo_pool
|
123
|
+
utxo_provider.default_value * utxo_provider.utxo_pool_size
|
124
|
+
end
|
125
|
+
|
126
|
+
def wallet
|
127
|
+
utxo_provider.wallet
|
128
|
+
end
|
129
|
+
|
130
|
+
def delimit(num)
|
131
|
+
num.to_s.reverse.scan(/.{1,3}/).join('_').reverse
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Glueby
|
2
|
+
class UtxoProvider
|
3
|
+
include Glueby::Contract::TxBuilder
|
4
|
+
|
5
|
+
autoload :Tasks, 'glueby/utxo_provider/tasks'
|
6
|
+
|
7
|
+
WALLET_ID = 'UTXO_PROVIDER_WALLET'
|
8
|
+
DEFAULT_VALUE = 1_000
|
9
|
+
DEFAULT_UTXO_POOL_SIZE = 20
|
10
|
+
MAX_UTXO_POOL_SIZE = 2_000
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_reader :config
|
14
|
+
|
15
|
+
# @param [Hash] config
|
16
|
+
# @option config [Integer] :default_value
|
17
|
+
# @option opts [Integer] :utxo_pool_size
|
18
|
+
# @option opts [Glueby::Contract::FeeEstimator] :fee_estimator
|
19
|
+
def configure(config)
|
20
|
+
@config = config
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@wallet = load_wallet
|
26
|
+
validate_config!
|
27
|
+
@fee_estimator = (UtxoProvider.config && UtxoProvider.config[:fee_estimator]) || Glueby::Contract::FixedFeeEstimator.new
|
28
|
+
@default_value = (UtxoProvider.config && UtxoProvider.config[:default_value]) || DEFAULT_VALUE
|
29
|
+
@utxo_pool_size = (UtxoProvider.config && UtxoProvider.config[:utxo_pool_size]) || DEFAULT_UTXO_POOL_SIZE
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :wallet, :fee_estimator, :default_value, :utxo_pool_size
|
33
|
+
|
34
|
+
# Provide a UTXO
|
35
|
+
# @param [Tapyrus::Script] script_pubkey The script to be provided
|
36
|
+
# @param [Integer] value The tpc amount to be provided
|
37
|
+
# @return [Array<(Tapyrus::Tx, Integer)>]
|
38
|
+
# The tx that has a UTXO to be provided in its outputs.
|
39
|
+
# The output index in the tx to indicate the place of a provided UTXO.
|
40
|
+
# @raise [Glueby::Contract::Errors::InsufficientFunds] if provider does not have any utxo which has specified value.
|
41
|
+
def get_utxo(script_pubkey, value = DEFAULT_VALUE)
|
42
|
+
txb = Tapyrus::TxBuilder.new
|
43
|
+
txb.pay(script_pubkey.addresses.first, value)
|
44
|
+
|
45
|
+
fee = fee_estimator.fee(dummy_tx(txb.build))
|
46
|
+
# The outputs need to be shuffled so that no utxos are spent twice as possible.
|
47
|
+
sum, outputs = wallet.collect_uncolored_outputs(fee + value, nil, true, true)
|
48
|
+
|
49
|
+
outputs.each do |utxo|
|
50
|
+
txb.add_utxo({
|
51
|
+
script_pubkey: Tapyrus::Script.parse_from_payload(utxo[:script_pubkey].htb),
|
52
|
+
txid: utxo[:txid],
|
53
|
+
index: utxo[:vout],
|
54
|
+
value: utxo[:amount]
|
55
|
+
})
|
56
|
+
end
|
57
|
+
|
58
|
+
txb.fee(fee).change_address(wallet.change_address)
|
59
|
+
|
60
|
+
tx = txb.build
|
61
|
+
signed_tx = wallet.sign_tx(tx)
|
62
|
+
[signed_tx, 0]
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# Create wallet for provider
|
68
|
+
def load_wallet
|
69
|
+
begin
|
70
|
+
Glueby::Internal::Wallet.load(WALLET_ID)
|
71
|
+
rescue Glueby::Internal::Wallet::Errors::WalletNotFound => _
|
72
|
+
Glueby::Internal::Wallet.create(WALLET_ID)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_config!
|
77
|
+
if UtxoProvider.config
|
78
|
+
utxo_pool_size = UtxoProvider.config[:utxo_pool_size]
|
79
|
+
if utxo_pool_size && (!utxo_pool_size.is_a?(Integer) || utxo_pool_size > MAX_UTXO_POOL_SIZE)
|
80
|
+
raise Glueby::Configuration::Errors::InvalidConfiguration, "utxo_pool_size(#{utxo_pool_size}) should not be greater than #{MAX_UTXO_POOL_SIZE}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/glueby/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module Glueby
|
2
|
-
VERSION = "0.
|
3
|
-
end
|
1
|
+
module Glueby
|
2
|
+
VERSION = "0.5.0"
|
3
|
+
end
|
data/lib/glueby.rb
CHANGED
@@ -1,39 +1,40 @@
|
|
1
|
-
require "glueby/version"
|
2
|
-
require 'tapyrus'
|
3
|
-
|
4
|
-
module Glueby
|
5
|
-
autoload :Contract, 'glueby/contract'
|
6
|
-
autoload :Generator, 'glueby/generator'
|
7
|
-
autoload :Wallet, 'glueby/wallet'
|
8
|
-
autoload :Internal, 'glueby/internal'
|
9
|
-
autoload :AR, 'glueby/active_record'
|
10
|
-
autoload :FeeProvider, 'glueby/fee_provider'
|
11
|
-
autoload :Configuration, 'glueby/configuration'
|
12
|
-
autoload :BlockSyncer, 'glueby/block_syncer'
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
# config.
|
35
|
-
#
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
1
|
+
require "glueby/version"
|
2
|
+
require 'tapyrus'
|
3
|
+
|
4
|
+
module Glueby
|
5
|
+
autoload :Contract, 'glueby/contract'
|
6
|
+
autoload :Generator, 'glueby/generator'
|
7
|
+
autoload :Wallet, 'glueby/wallet'
|
8
|
+
autoload :Internal, 'glueby/internal'
|
9
|
+
autoload :AR, 'glueby/active_record'
|
10
|
+
autoload :FeeProvider, 'glueby/fee_provider'
|
11
|
+
autoload :Configuration, 'glueby/configuration'
|
12
|
+
autoload :BlockSyncer, 'glueby/block_syncer'
|
13
|
+
autoload :UtxoProvider, 'glueby/utxo_provider'
|
14
|
+
|
15
|
+
if defined? ::Rails::Railtie
|
16
|
+
require 'glueby/railtie'
|
17
|
+
end
|
18
|
+
|
19
|
+
# Add prefix to activerecord table names
|
20
|
+
def self.table_name_prefix
|
21
|
+
'glueby_'
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the global [Configuration](RSpec/Core/Configuration) object.
|
25
|
+
def self.configuration
|
26
|
+
@configuration ||= Glueby::Configuration.new
|
27
|
+
end
|
28
|
+
|
29
|
+
# Yields the global configuration to a block.
|
30
|
+
# @yield [Configuration] global configuration
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# Glueby.configure do |config|
|
34
|
+
# config.wallet_adapter = :activerecord
|
35
|
+
# config.rpc_config = { schema: 'http', host: '127.0.0.1', port: 12381, user: 'user', password: 'pass' }
|
36
|
+
# end
|
37
|
+
def self.configure
|
38
|
+
yield configuration if block_given?
|
39
|
+
end
|
40
|
+
end
|