glueby 0.4.1 → 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 +192 -10
- data/glueby.gemspec +2 -1
- data/lib/generators/glueby/contract/templates/initializer.rb.erb +7 -2
- data/lib/glueby/block_syncer.rb +98 -0
- data/lib/glueby/configuration.rb +26 -2
- data/lib/glueby/contract/timestamp/syncer.rb +13 -0
- data/lib/glueby/contract/timestamp.rb +33 -25
- data/lib/glueby/contract/token.rb +34 -8
- data/lib/glueby/contract/tx_builder.rb +97 -30
- data/lib/glueby/fee_provider/tasks.rb +6 -1
- data/lib/glueby/fee_provider.rb +11 -0
- data/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +3 -1
- data/lib/glueby/internal/wallet/active_record_wallet_adapter/syncer.rb +14 -0
- data/lib/glueby/internal/wallet/active_record_wallet_adapter.rb +8 -3
- data/lib/glueby/internal/wallet/tapyrus_core_wallet_adapter.rb +11 -10
- data/lib/glueby/internal/wallet.rb +24 -7
- data/lib/glueby/railtie.rb +11 -0
- data/lib/glueby/utxo_provider/tasks.rb +135 -0
- data/lib/glueby/utxo_provider.rb +85 -0
- data/lib/glueby/version.rb +1 -1
- data/lib/glueby.rb +6 -15
- data/lib/tasks/glueby/block_syncer.rake +29 -0
- data/lib/tasks/glueby/contract/timestamp.rake +12 -27
- data/lib/tasks/glueby/fee_provider.rake +5 -0
- data/lib/tasks/glueby/utxo_provider.rake +18 -0
- metadata +28 -8
- data/lib/tasks/glueby/contract/block_syncer.rake +0 -36
- data/lib/tasks/glueby/contract/wallet_adapter.rake +0 -42
@@ -11,18 +11,32 @@ module Glueby
|
|
11
11
|
end
|
12
12
|
|
13
13
|
# Create new public key, and new transaction that sends TPC to it
|
14
|
-
def create_funding_tx(wallet:, script: nil, fee_estimator: FixedFeeEstimator.new)
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
14
|
+
def create_funding_tx(wallet:, script: nil, fee_estimator: FixedFeeEstimator.new, utxo_provider: nil)
|
15
|
+
if utxo_provider
|
16
|
+
script_pubkey = script ? script : Tapyrus::Script.parse_from_addr(wallet.internal_wallet.receive_address)
|
17
|
+
funding_tx, _index = utxo_provider.get_utxo(script_pubkey, FUNDING_TX_AMOUNT)
|
18
|
+
utxo_provider.wallet.sign_tx(funding_tx)
|
19
|
+
else
|
20
|
+
txb = Tapyrus::TxBuilder.new
|
21
|
+
fee = fee_estimator.fee(dummy_tx(txb.build))
|
22
|
+
|
23
|
+
sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(fee + FUNDING_TX_AMOUNT)
|
24
|
+
outputs.each do |utxo|
|
25
|
+
txb.add_utxo({
|
26
|
+
script_pubkey: Tapyrus::Script.parse_from_payload(utxo[:script_pubkey].htb),
|
27
|
+
txid: utxo[:txid],
|
28
|
+
index: utxo[:vout],
|
29
|
+
value: utxo[:amount]
|
30
|
+
})
|
31
|
+
end
|
32
|
+
|
33
|
+
receiver_address = script ? script.addresses.first : wallet.internal_wallet.receive_address
|
34
|
+
tx = txb.pay(receiver_address, FUNDING_TX_AMOUNT)
|
35
|
+
.change_address(wallet.internal_wallet.change_address)
|
36
|
+
.fee(fee)
|
37
|
+
.build
|
38
|
+
wallet.internal_wallet.sign_tx(tx)
|
39
|
+
end
|
26
40
|
end
|
27
41
|
|
28
42
|
def create_issue_tx_for_reissuable_token(funding_tx:, issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
|
@@ -48,21 +62,27 @@ module Glueby
|
|
48
62
|
issuer.internal_wallet.sign_tx(tx, prev_txs)
|
49
63
|
end
|
50
64
|
|
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)
|
65
|
+
def create_issue_tx_for_non_reissuable_token(funding_tx: nil, issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
|
66
|
+
create_issue_tx_from_out_point(funding_tx: funding_tx, token_type: Tapyrus::Color::TokenTypes::NON_REISSUABLE, issuer: issuer, amount: amount, fee_estimator: fee_estimator)
|
53
67
|
end
|
54
68
|
|
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)
|
69
|
+
def create_issue_tx_for_nft_token(funding_tx: nil, issuer:, fee_estimator: FixedFeeEstimator.new)
|
70
|
+
create_issue_tx_from_out_point(funding_tx: funding_tx, token_type: Tapyrus::Color::TokenTypes::NFT, issuer: issuer, amount: 1, fee_estimator: fee_estimator)
|
57
71
|
end
|
58
72
|
|
59
|
-
def create_issue_tx_from_out_point(token_type:, issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
|
73
|
+
def create_issue_tx_from_out_point(funding_tx: nil, token_type:, issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
|
60
74
|
tx = Tapyrus::Tx.new
|
61
75
|
|
62
76
|
fee = fee_estimator.fee(dummy_issue_tx_from_out_point)
|
63
|
-
sum
|
64
|
-
|
65
|
-
|
77
|
+
sum = if funding_tx
|
78
|
+
out_point = Tapyrus::OutPoint.from_txid(funding_tx.txid, 0)
|
79
|
+
tx.inputs << Tapyrus::TxIn.new(out_point: out_point)
|
80
|
+
funding_tx.outputs.first.value
|
81
|
+
else
|
82
|
+
sum, outputs = issuer.internal_wallet.collect_uncolored_outputs(fee)
|
83
|
+
fill_input(tx, outputs)
|
84
|
+
sum
|
85
|
+
end
|
66
86
|
out_point = tx.inputs.first.out_point
|
67
87
|
color_id = case token_type
|
68
88
|
when Tapyrus::Color::TokenTypes::NON_REISSUABLE
|
@@ -78,7 +98,18 @@ module Glueby
|
|
78
98
|
tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_colored_script)
|
79
99
|
|
80
100
|
fill_change_tpc(tx, issuer, sum - fee)
|
81
|
-
|
101
|
+
prev_txs = if funding_tx
|
102
|
+
output = funding_tx.outputs.first
|
103
|
+
[{
|
104
|
+
txid: funding_tx.txid,
|
105
|
+
vout: 0,
|
106
|
+
scriptPubKey: output.script_pubkey.to_hex,
|
107
|
+
amount: output.value
|
108
|
+
}]
|
109
|
+
else
|
110
|
+
[]
|
111
|
+
end
|
112
|
+
issuer.internal_wallet.sign_tx(tx, prev_txs)
|
82
113
|
end
|
83
114
|
|
84
115
|
def create_reissue_tx(funding_tx:, issuer:, amount:, color_id:, fee_estimator: FixedFeeEstimator.new)
|
@@ -103,7 +134,7 @@ module Glueby
|
|
103
134
|
issuer.internal_wallet.sign_tx(tx, prev_txs)
|
104
135
|
end
|
105
136
|
|
106
|
-
def create_transfer_tx(color_id:, sender:, receiver_address:, amount:, fee_estimator: FixedFeeEstimator.new)
|
137
|
+
def create_transfer_tx(funding_tx:nil, color_id:, sender:, receiver_address:, amount:, fee_estimator: FixedFeeEstimator.new)
|
107
138
|
tx = Tapyrus::Tx.new
|
108
139
|
|
109
140
|
utxos = sender.internal_wallet.list_unspent
|
@@ -117,14 +148,32 @@ module Glueby
|
|
117
148
|
fill_change_token(tx, sender, sum_token - amount, color_id)
|
118
149
|
|
119
150
|
fee = fee_estimator.fee(dummy_tx(tx))
|
120
|
-
sum_tpc
|
121
|
-
|
151
|
+
sum_tpc = if funding_tx
|
152
|
+
out_point = Tapyrus::OutPoint.from_txid(funding_tx.txid, 0)
|
153
|
+
tx.inputs << Tapyrus::TxIn.new(out_point: out_point)
|
154
|
+
funding_tx.outputs.first.value
|
155
|
+
else
|
156
|
+
sum_tpc, outputs = sender.internal_wallet.collect_uncolored_outputs(fee)
|
157
|
+
fill_input(tx, outputs)
|
158
|
+
sum_tpc
|
159
|
+
end
|
122
160
|
|
123
161
|
fill_change_tpc(tx, sender, sum_tpc - fee)
|
124
|
-
|
162
|
+
prev_txs = if funding_tx
|
163
|
+
output = funding_tx.outputs.first
|
164
|
+
[{
|
165
|
+
txid: funding_tx.txid,
|
166
|
+
vout: 0,
|
167
|
+
scriptPubKey: output.script_pubkey.to_hex,
|
168
|
+
amount: output.value
|
169
|
+
}]
|
170
|
+
else
|
171
|
+
[]
|
172
|
+
end
|
173
|
+
sender.internal_wallet.sign_tx(tx, prev_txs)
|
125
174
|
end
|
126
175
|
|
127
|
-
def create_burn_tx(color_id:, sender:, amount: 0, fee_estimator: FixedFeeEstimator.new)
|
176
|
+
def create_burn_tx(funding_tx:nil, color_id:, sender:, amount: 0, fee_estimator: FixedFeeEstimator.new)
|
128
177
|
tx = Tapyrus::Tx.new
|
129
178
|
|
130
179
|
utxos = sender.internal_wallet.list_unspent
|
@@ -135,12 +184,30 @@ module Glueby
|
|
135
184
|
|
136
185
|
fee = fee_estimator.fee(dummy_tx(tx))
|
137
186
|
|
138
|
-
|
139
|
-
|
140
|
-
|
187
|
+
sum_tpc = if funding_tx
|
188
|
+
out_point = Tapyrus::OutPoint.from_txid(funding_tx.txid, 0)
|
189
|
+
tx.inputs << Tapyrus::TxIn.new(out_point: out_point)
|
190
|
+
funding_tx.outputs.first.value
|
191
|
+
else
|
192
|
+
dust = 600 # in case that the wallet has output which has just fee amount.
|
193
|
+
sum_tpc, outputs = sender.internal_wallet.collect_uncolored_outputs(fee + dust)
|
194
|
+
fill_input(tx, outputs)
|
195
|
+
sum_tpc
|
196
|
+
end
|
141
197
|
|
142
198
|
fill_change_tpc(tx, sender, sum_tpc - fee)
|
143
|
-
|
199
|
+
prev_txs = if funding_tx
|
200
|
+
output = funding_tx.outputs.first
|
201
|
+
[{
|
202
|
+
txid: funding_tx.txid,
|
203
|
+
vout: 0,
|
204
|
+
scriptPubKey: output.script_pubkey.to_hex,
|
205
|
+
amount: output.value
|
206
|
+
}]
|
207
|
+
else
|
208
|
+
[]
|
209
|
+
end
|
210
|
+
sender.internal_wallet.sign_tx(tx, prev_txs)
|
144
211
|
end
|
145
212
|
|
146
213
|
def fill_input(tx, outputs)
|
@@ -44,7 +44,7 @@ module Glueby
|
|
44
44
|
.fee(fee_provider.fixed_fee)
|
45
45
|
.build
|
46
46
|
tx = wallet.sign_tx(tx)
|
47
|
-
wallet.broadcast(tx)
|
47
|
+
wallet.broadcast(tx, without_fee_provider: true)
|
48
48
|
ensure
|
49
49
|
status
|
50
50
|
end
|
@@ -80,6 +80,11 @@ module Glueby
|
|
80
80
|
EOS
|
81
81
|
end
|
82
82
|
|
83
|
+
# Show the address of Fee Provider
|
84
|
+
def print_address
|
85
|
+
puts wallet.receive_address
|
86
|
+
end
|
87
|
+
|
83
88
|
private
|
84
89
|
|
85
90
|
def check_wallet_amount!
|
data/lib/glueby/fee_provider.rb
CHANGED
@@ -8,6 +8,7 @@ module Glueby
|
|
8
8
|
WALLET_ID = 'FEE_PROVIDER_WALLET'
|
9
9
|
DEFAULT_FIXED_FEE = 1000
|
10
10
|
DEFAULT_UTXO_POOL_SIZE = 20
|
11
|
+
MAX_UTXO_POOL_SIZE = 2_000
|
11
12
|
|
12
13
|
attr_reader :fixed_fee, :utxo_pool_size, :wallet,
|
13
14
|
|
@@ -33,6 +34,7 @@ module Glueby
|
|
33
34
|
Internal::Wallet.create(WALLET_ID)
|
34
35
|
end
|
35
36
|
|
37
|
+
validate_config!
|
36
38
|
@fixed_fee = (FeeProvider.config && FeeProvider.config[:fixed_fee]) || DEFAULT_FIXED_FEE
|
37
39
|
@utxo_pool_size = (FeeProvider.config && FeeProvider.config[:utxo_pool_size]) || DEFAULT_UTXO_POOL_SIZE
|
38
40
|
end
|
@@ -69,5 +71,14 @@ module Glueby
|
|
69
71
|
def get_signature(script_sig)
|
70
72
|
script_sig.chunks.first.pushed_data
|
71
73
|
end
|
74
|
+
|
75
|
+
def validate_config!
|
76
|
+
if FeeProvider.config
|
77
|
+
utxo_pool_size = FeeProvider.config[:utxo_pool_size]
|
78
|
+
if utxo_pool_size && (!utxo_pool_size.is_a?(Integer) || utxo_pool_size > MAX_UTXO_POOL_SIZE)
|
79
|
+
raise Glueby::Configuration::Errors::InvalidConfiguration, "utxo_pool_size(#{utxo_pool_size}) should not be greater than #{MAX_UTXO_POOL_SIZE}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
72
83
|
end
|
73
84
|
end
|
@@ -101,8 +101,10 @@ module Glueby
|
|
101
101
|
#
|
102
102
|
# @param [String] wallet_id - The wallet id that is offered by `create_wallet()` method.
|
103
103
|
# @param [Tapyrus::Tx] tx - The transaction to be broadcasterd.
|
104
|
+
# @yield Option. If a block given, the block is called before actual broadcasting.
|
105
|
+
# @yieldparam [Tapyrus::Tx] tx - The tx that is going to be broadcasted as is.
|
104
106
|
# @return [String] txid
|
105
|
-
def broadcast(wallet_id, tx)
|
107
|
+
def broadcast(wallet_id, tx, &block)
|
106
108
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
107
109
|
end
|
108
110
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Glueby
|
2
|
+
module Internal
|
3
|
+
class Wallet
|
4
|
+
class ActiveRecordWalletAdapter
|
5
|
+
class Syncer
|
6
|
+
def tx_sync(tx)
|
7
|
+
Glueby::Internal::Wallet::AR::Utxo.destroy_for_inputs(tx)
|
8
|
+
Glueby::Internal::Wallet::AR::Utxo.create_or_update_for_outputs(tx)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -54,6 +54,9 @@ module Glueby
|
|
54
54
|
# alice_wallet.balances
|
55
55
|
# ```
|
56
56
|
class ActiveRecordWalletAdapter < AbstractWalletAdapter
|
57
|
+
|
58
|
+
autoload :Syncer, 'glueby/internal/wallet/active_record_wallet_adapter/syncer'
|
59
|
+
|
57
60
|
def create_wallet(wallet_id = nil)
|
58
61
|
wallet_id = SecureRandom.hex(16) unless wallet_id
|
59
62
|
begin
|
@@ -90,7 +93,8 @@ module Glueby
|
|
90
93
|
wallet = AR::Wallet.find_by(wallet_id: wallet_id)
|
91
94
|
utxos = wallet.utxos
|
92
95
|
utxos = utxos.where(status: :finalized) if only_finalized
|
93
|
-
utxos = utxos.where(label: label) if label
|
96
|
+
utxos = utxos.where(label: label) if label && (label != :unlabeled)
|
97
|
+
utxos = utxos.where(label: nil) if label == :unlabeled
|
94
98
|
utxos.map do |utxo|
|
95
99
|
{
|
96
100
|
txid: utxo.txid,
|
@@ -108,17 +112,18 @@ module Glueby
|
|
108
112
|
wallet.sign(tx, prevtxs, sighashtype: sighashtype)
|
109
113
|
end
|
110
114
|
|
111
|
-
def broadcast(wallet_id, tx)
|
115
|
+
def broadcast(wallet_id, tx, &block)
|
112
116
|
::ActiveRecord::Base.transaction do
|
113
117
|
AR::Utxo.destroy_for_inputs(tx)
|
114
118
|
AR::Utxo.create_or_update_for_outputs(tx, status: :broadcasted)
|
119
|
+
block.call(tx) if block
|
115
120
|
Glueby::Internal::RPC.client.sendrawtransaction(tx.to_hex)
|
116
121
|
end
|
117
122
|
end
|
118
123
|
|
119
124
|
def receive_address(wallet_id, label = nil)
|
120
125
|
wallet = AR::Wallet.find_by(wallet_id: wallet_id)
|
121
|
-
key = wallet.keys.create(purpose: :receive, label: label
|
126
|
+
key = wallet.keys.create(purpose: :receive, label: label)
|
122
127
|
key.address
|
123
128
|
end
|
124
129
|
|
@@ -23,7 +23,6 @@ module Glueby
|
|
23
23
|
include Glueby::Internal::Wallet::TapyrusCoreWalletAdapter::Util
|
24
24
|
|
25
25
|
WALLET_PREFIX = 'wallet-'
|
26
|
-
ADDRESS_TYPE = 'legacy'
|
27
26
|
|
28
27
|
RPC_WALLET_ERROR_ERROR_CODE = -4 # Unspecified problem with wallet (key not found etc.)
|
29
28
|
RPC_WALLET_NOT_FOUND_ERROR_CODE = -18 # Invalid wallet specified
|
@@ -33,7 +32,7 @@ module Glueby
|
|
33
32
|
begin
|
34
33
|
RPC.client.createwallet(wallet_name(wallet_id))
|
35
34
|
rescue Tapyrus::RPC::Error => ex
|
36
|
-
if ex.rpc_error['code'] == RPC_WALLET_ERROR_ERROR_CODE && /Wallet wallet
|
35
|
+
if ex.rpc_error && ex.rpc_error['code'] == RPC_WALLET_ERROR_ERROR_CODE && /Wallet wallet-#{wallet_id} already exists\./ =~ ex.rpc_error['message']
|
37
36
|
raise Errors::WalletAlreadyCreated, "Wallet #{wallet_id} has been already created."
|
38
37
|
else
|
39
38
|
raise ex
|
@@ -52,9 +51,9 @@ module Glueby
|
|
52
51
|
def load_wallet(wallet_id)
|
53
52
|
RPC.client.loadwallet(wallet_name(wallet_id))
|
54
53
|
rescue Tapyrus::RPC::Error => ex
|
55
|
-
if ex.rpc_error['code'] == RPC_WALLET_ERROR_ERROR_CODE && /Duplicate -wallet filename specified/ =~ ex.rpc_error['message']
|
54
|
+
if ex.rpc_error && ex.rpc_error['code'] == RPC_WALLET_ERROR_ERROR_CODE && /Duplicate -wallet filename specified/ =~ ex.rpc_error['message']
|
56
55
|
raise Errors::WalletAlreadyLoaded, "Wallet #{wallet_id} has been already loaded."
|
57
|
-
elsif ex.rpc_error['code'] == RPC_WALLET_NOT_FOUND_ERROR_CODE
|
56
|
+
elsif ex.rpc_error && ex.rpc_error['code'] == RPC_WALLET_NOT_FOUND_ERROR_CODE
|
58
57
|
raise Errors::WalletNotFound, "Wallet #{wallet_id} does not found"
|
59
58
|
else
|
60
59
|
raise ex
|
@@ -88,7 +87,8 @@ module Glueby
|
|
88
87
|
min_conf = only_finalized ? 1 : 0
|
89
88
|
res = client.listunspent(min_conf)
|
90
89
|
|
91
|
-
res = res.filter { |i| i['label'] == label } if label
|
90
|
+
res = res.filter { |i| i['label'] == label } if label && (label != :unlabeled)
|
91
|
+
res = res.filter { |i| i['label'] == "" } if label == :unlabeled
|
92
92
|
|
93
93
|
res.map do |i|
|
94
94
|
script = Tapyrus::Script.parse_from_payload(i['scriptPubKey'].htb)
|
@@ -118,27 +118,28 @@ module Glueby
|
|
118
118
|
end
|
119
119
|
end
|
120
120
|
|
121
|
-
def broadcast(wallet_id, tx)
|
121
|
+
def broadcast(wallet_id, tx, &block)
|
122
122
|
perform_as(wallet_id) do |client|
|
123
|
+
block.call(tx) if block
|
123
124
|
client.sendrawtransaction(tx.to_hex)
|
124
125
|
end
|
125
126
|
end
|
126
127
|
|
127
128
|
def receive_address(wallet_id, label = nil)
|
128
129
|
perform_as(wallet_id) do |client|
|
129
|
-
client.getnewaddress(label || ''
|
130
|
+
client.getnewaddress(label || '')
|
130
131
|
end
|
131
132
|
end
|
132
133
|
|
133
134
|
def change_address(wallet_id)
|
134
135
|
perform_as(wallet_id) do |client|
|
135
|
-
client.getrawchangeaddress
|
136
|
+
client.getrawchangeaddress
|
136
137
|
end
|
137
138
|
end
|
138
139
|
|
139
140
|
def create_pubkey(wallet_id)
|
140
141
|
perform_as(wallet_id) do |client|
|
141
|
-
address = client.getnewaddress(''
|
142
|
+
address = client.getnewaddress('')
|
142
143
|
info = client.getaddressinfo(address)
|
143
144
|
Tapyrus::Key.new(pubkey: info['pubkey'])
|
144
145
|
end
|
@@ -151,7 +152,7 @@ module Glueby
|
|
151
152
|
begin
|
152
153
|
yield(client)
|
153
154
|
rescue Tapyrus::RPC::Error => ex
|
154
|
-
if ex.rpc_error['code'] == RPC_WALLET_NOT_FOUND_ERROR_CODE
|
155
|
+
if ex.rpc_error && ex.rpc_error['code'] == RPC_WALLET_NOT_FOUND_ERROR_CODE
|
155
156
|
raise Errors::WalletUnloaded, "The wallet #{wallet_id} is unloaded. You should load before use it."
|
156
157
|
else
|
157
158
|
raise ex
|
@@ -34,8 +34,6 @@ module Glueby
|
|
34
34
|
autoload :Errors, 'glueby/internal/wallet/errors'
|
35
35
|
|
36
36
|
class << self
|
37
|
-
attr_writer :wallet_adapter
|
38
|
-
|
39
37
|
def create(wallet_id = nil)
|
40
38
|
begin
|
41
39
|
wallet_id = wallet_adapter.create_wallet(wallet_id)
|
@@ -58,6 +56,16 @@ module Glueby
|
|
58
56
|
wallet_adapter.wallets.map { |id| new(id) }
|
59
57
|
end
|
60
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
|
+
|
61
69
|
def wallet_adapter
|
62
70
|
@wallet_adapter or
|
63
71
|
raise Errors::ShouldInitializeWalletAdapter, 'You should initialize wallet adapter using `Glueby::Internal::Wallet.wallet_adapter = some wallet adapter instance`.'
|
@@ -74,6 +82,10 @@ module Glueby
|
|
74
82
|
wallet_adapter.balance(id, only_finalized)
|
75
83
|
end
|
76
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.
|
77
89
|
def list_unspent(only_finalized = true, label = nil)
|
78
90
|
wallet_adapter.list_unspent(id, only_finalized, label)
|
79
91
|
end
|
@@ -99,9 +111,13 @@ module Glueby
|
|
99
111
|
wallet_adapter.sign_tx(id, tx, prev_txs, sighashtype: sighashtype)
|
100
112
|
end
|
101
113
|
|
102
|
-
|
103
|
-
|
104
|
-
|
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)
|
105
121
|
tx
|
106
122
|
end
|
107
123
|
|
@@ -117,8 +133,9 @@ module Glueby
|
|
117
133
|
wallet_adapter.create_pubkey(id)
|
118
134
|
end
|
119
135
|
|
120
|
-
def collect_uncolored_outputs(amount, label = nil)
|
121
|
-
utxos = list_unspent(
|
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
|
122
139
|
|
123
140
|
utxos.inject([0, []]) do |sum, output|
|
124
141
|
next sum if output[:color_id]
|
@@ -0,0 +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
|
+
load "tasks/glueby/utxo_provider.rake"
|
9
|
+
end
|
10
|
+
end
|
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
|