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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +35 -0
- data/Gemfile +1 -0
- data/README.md +224 -16
- data/glueby.gemspec +2 -2
- data/lib/generators/glueby/contract/block_syncer_generator.rb +26 -0
- data/lib/generators/glueby/contract/reissuable_token_generator.rb +26 -0
- data/lib/generators/glueby/contract/templates/initializer.rb.erb +4 -2
- data/lib/generators/glueby/contract/templates/key_table.rb.erb +4 -3
- data/lib/generators/glueby/contract/templates/reissuable_token_table.rb.erb +10 -0
- data/lib/generators/glueby/contract/templates/system_information_table.rb.erb +12 -0
- data/lib/generators/glueby/contract/templates/timestamp_table.rb.erb +1 -1
- data/lib/generators/glueby/contract/templates/utxo_table.rb.erb +3 -2
- data/lib/generators/glueby/contract/templates/wallet_table.rb.erb +2 -2
- data/lib/glueby.rb +28 -11
- data/lib/glueby/active_record.rb +8 -0
- data/lib/glueby/active_record/system_information.rb +15 -0
- data/lib/glueby/block_syncer.rb +98 -0
- data/lib/glueby/configuration.rb +62 -0
- data/lib/glueby/contract.rb +2 -2
- data/lib/glueby/contract/active_record.rb +1 -0
- data/lib/glueby/contract/active_record/reissuable_token.rb +26 -0
- data/lib/glueby/contract/fee_estimator.rb +38 -0
- data/lib/glueby/contract/payment.rb +10 -6
- data/lib/glueby/contract/timestamp.rb +8 -6
- data/lib/glueby/contract/timestamp/syncer.rb +13 -0
- data/lib/glueby/contract/token.rb +78 -26
- data/lib/glueby/contract/tx_builder.rb +23 -20
- data/lib/glueby/fee_provider.rb +73 -0
- data/lib/glueby/fee_provider/tasks.rb +141 -0
- data/lib/glueby/generator/migrate_generator.rb +1 -1
- data/lib/glueby/internal/wallet.rb +56 -13
- data/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +25 -6
- data/lib/glueby/internal/wallet/active_record/utxo.rb +1 -0
- data/lib/glueby/internal/wallet/active_record/wallet.rb +15 -5
- data/lib/glueby/internal/wallet/active_record_wallet_adapter.rb +25 -8
- data/lib/glueby/internal/wallet/active_record_wallet_adapter/syncer.rb +14 -0
- data/lib/glueby/internal/wallet/errors.rb +3 -0
- data/lib/glueby/internal/wallet/tapyrus_core_wallet_adapter.rb +42 -14
- data/lib/glueby/railtie.rb +14 -0
- data/lib/glueby/version.rb +1 -1
- data/lib/glueby/wallet.rb +3 -2
- data/lib/tasks/glueby/block_syncer.rake +29 -0
- data/lib/tasks/glueby/contract/timestamp.rake +4 -26
- data/lib/tasks/glueby/fee_provider.rake +18 -0
- metadata +26 -11
- data/.travis.yml +0 -7
- data/lib/glueby/contract/fee_provider.rb +0 -21
- data/lib/tasks/glueby/contract/wallet_adapter.rake +0 -42
@@ -1,15 +1,16 @@
|
|
1
1
|
class CreateUtxo < ActiveRecord::Migration<%= migration_version %>
|
2
2
|
def change
|
3
|
-
create_table :
|
3
|
+
create_table :glueby_utxos<%= table_options %> do |t|
|
4
4
|
t.string :txid
|
5
5
|
t.integer :index
|
6
6
|
t.bigint :value
|
7
7
|
t.string :script_pubkey
|
8
|
+
t.string :label, index: true
|
8
9
|
t.integer :status
|
9
10
|
t.belongs_to :key, null: true
|
10
11
|
t.timestamps
|
11
12
|
end
|
12
13
|
|
13
|
-
add_index :
|
14
|
+
add_index :glueby_utxos, [:txid, :index], unique: true
|
14
15
|
end
|
15
16
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
class CreateWallet < ActiveRecord::Migration<%= migration_version %>
|
2
2
|
def change
|
3
|
-
create_table :
|
3
|
+
create_table :glueby_wallets<%= table_options %> do |t|
|
4
4
|
t.string :wallet_id
|
5
5
|
t.timestamps
|
6
6
|
end
|
7
7
|
|
8
|
-
add_index :
|
8
|
+
add_index :glueby_wallets, [:wallet_id], unique: true
|
9
9
|
end
|
10
10
|
end
|
data/lib/glueby.rb
CHANGED
@@ -6,17 +6,34 @@ module Glueby
|
|
6
6
|
autoload :Generator, 'glueby/generator'
|
7
7
|
autoload :Wallet, 'glueby/wallet'
|
8
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'
|
9
13
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
14
|
+
if defined? ::Rails::Railtie
|
15
|
+
require 'glueby/railtie'
|
16
|
+
end
|
17
|
+
|
18
|
+
# Add prefix to activerecord table names
|
19
|
+
def self.table_name_prefix
|
20
|
+
'glueby_'
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the global [Configuration](RSpec/Core/Configuration) object.
|
24
|
+
def self.configuration
|
25
|
+
@configuration ||= Glueby::Configuration.new
|
26
|
+
end
|
27
|
+
|
28
|
+
# Yields the global configuration to a block.
|
29
|
+
# @yield [Configuration] global configuration
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# Glueby.configure do |config|
|
33
|
+
# config.wallet_adapter = :activerecord
|
34
|
+
# config.rpc_config = { schema: 'http', host: '127.0.0.1', port: 12381, user: 'user', password: 'pass' }
|
35
|
+
# end
|
36
|
+
def self.configure
|
37
|
+
yield configuration if block_given?
|
21
38
|
end
|
22
39
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Glueby
|
2
|
+
# You can use BlockSyncer when you need to synchronize the state of
|
3
|
+
# an application with the state of a blockchain. When BlockSyncer
|
4
|
+
# detects the generation of a new block, it executes the registered
|
5
|
+
# syncer code on a block-by-block or transaction-by-transaction basis.
|
6
|
+
# By using this, an application can detect that the issued transaction
|
7
|
+
# has been captured in blocks, receive a new remittance, and so on.
|
8
|
+
#
|
9
|
+
# # Syncer logic registration
|
10
|
+
#
|
11
|
+
# For registration, create a class that implements the method that performs
|
12
|
+
# synchronization processing and registers it in BlockSyncer. Implement
|
13
|
+
# methods with the following name in that class.
|
14
|
+
#
|
15
|
+
# Method name | Arguments | Call conditions
|
16
|
+
# ------------------ | --------------------- | ------------------------------
|
17
|
+
# block_sync (block) | block: Tapyrus::Block | When a new block is created
|
18
|
+
# block_tx (tx) | tx: Tapyrus::Tx | When a new block is created, it is executed for each tx contained in that block.
|
19
|
+
#
|
20
|
+
# @example Register a synchronous logic
|
21
|
+
# class Syncer
|
22
|
+
# def block_sync (block)
|
23
|
+
# # sync a block
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# def tx_sync (tx)
|
27
|
+
# # sync a tx
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
# BlockSyncer.register_syncer(Syncer)
|
31
|
+
#
|
32
|
+
# @example Unregister the synchronous logic
|
33
|
+
# BlockSyncer.unregister_syncer(Syncer)
|
34
|
+
#
|
35
|
+
# # Run BlockSyncer
|
36
|
+
#
|
37
|
+
# Run the `glueby: block_syncer: start` rake task periodically with a program
|
38
|
+
# for periodic execution such as cron. If it detects the generation of a new
|
39
|
+
# block when it is executed, the synchronization process will be executed.
|
40
|
+
# Determine the execution interval according to the requirements of the application.
|
41
|
+
class BlockSyncer
|
42
|
+
# @!attribute [r] height
|
43
|
+
# @return [Integer] The block height to be synced
|
44
|
+
attr_reader :height
|
45
|
+
|
46
|
+
class << self
|
47
|
+
# @!attribute r syncers
|
48
|
+
# @return [Array<Class>] The syncer classes that is registered
|
49
|
+
attr_reader :syncers
|
50
|
+
|
51
|
+
# Register syncer class
|
52
|
+
# @param [Class] syncer The syncer to be registered.
|
53
|
+
def register_syncer(syncer)
|
54
|
+
@syncers ||= []
|
55
|
+
@syncers << syncer
|
56
|
+
end
|
57
|
+
|
58
|
+
# Unregister syncer class
|
59
|
+
# @param [Class] syncer The syncer to be unregistered.
|
60
|
+
def unregister_syncer(syncer)
|
61
|
+
@syncers ||= []
|
62
|
+
@syncers.delete(syncer)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# @param [Integer] height The block height to be synced in the instance
|
67
|
+
def initialize(height)
|
68
|
+
@height = height
|
69
|
+
end
|
70
|
+
|
71
|
+
# Run a block synchronization
|
72
|
+
def run
|
73
|
+
return if self.class.syncers.nil?
|
74
|
+
|
75
|
+
self.class.syncers.each do |syncer|
|
76
|
+
instance = syncer.new
|
77
|
+
instance.block_sync(block) if instance.respond_to?(:block_sync)
|
78
|
+
|
79
|
+
if instance.respond_to?(:tx_sync)
|
80
|
+
block.transactions.each { |tx| instance.tx_sync(tx) }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def block
|
88
|
+
@block ||= begin
|
89
|
+
block = Glueby::Internal::RPC.client.getblock(block_hash, 0)
|
90
|
+
Tapyrus::Block.parse_from_payload(block.htb)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def block_hash
|
95
|
+
@block_hash ||= Glueby::Internal::RPC.client.getblockhash(height)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Glueby
|
2
|
+
# Global configuration on runtime
|
3
|
+
#
|
4
|
+
# The global configuration treats configurations for all modules in Glueby.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# Glueby.configure do |config|
|
8
|
+
# config.wallet_adapter = :activerecord
|
9
|
+
# config.rpc_config = { schema: 'http', host: '127.0.0.1', port: 12381, user: 'user', password: 'pass' }
|
10
|
+
# end
|
11
|
+
class Configuration
|
12
|
+
|
13
|
+
attr_reader :fee_provider_bears
|
14
|
+
alias_method :fee_provider_bears?, :fee_provider_bears
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@fee_provider_bears = false
|
18
|
+
end
|
19
|
+
|
20
|
+
# Specify wallet adapter.
|
21
|
+
# @param [Symbol] adapter - The adapter type :activerecord or :core is currently supported.
|
22
|
+
def wallet_adapter=(adapter)
|
23
|
+
case adapter
|
24
|
+
when :core
|
25
|
+
Glueby::Internal::Wallet.wallet_adapter = Glueby::Internal::Wallet::TapyrusCoreWalletAdapter.new
|
26
|
+
when :activerecord
|
27
|
+
Glueby::Internal::Wallet.wallet_adapter = Glueby::Internal::Wallet::ActiveRecordWalletAdapter.new
|
28
|
+
else
|
29
|
+
raise 'Not implemented'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Specify connection information to Tapyrus Core RPC.
|
34
|
+
# @param [Hash] config
|
35
|
+
# @option config [String] :schema - http or https
|
36
|
+
# @option config [String] :host - The host of the RPC endpoint
|
37
|
+
# @option config [Integer] :port - The port of the RPC endpoint
|
38
|
+
# @Option config [String] :user - The user for Basic Authorization of the RPC endpoint
|
39
|
+
# @Option config [String] :password - The password for Basic Authorization of the RPC endpoint
|
40
|
+
def rpc_config=(config)
|
41
|
+
Glueby::Internal::RPC.configure(config)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Use This to enable to use FeeProvider to supply inputs for fees on each transaction that is created on Glueby.
|
45
|
+
def fee_provider_bears!
|
46
|
+
@fee_provider_bears = true
|
47
|
+
end
|
48
|
+
|
49
|
+
# Use This to disable to use FeeProvider
|
50
|
+
def disable_fee_provider_bears!
|
51
|
+
@fee_provider_bears = false
|
52
|
+
end
|
53
|
+
|
54
|
+
# Specify FeeProvider configuration.
|
55
|
+
# @param [Hash] config
|
56
|
+
# @option config [Integer] :fixed_fee - The fee that Fee Provider pays on each transaction.
|
57
|
+
# @option config [Integer] :utxo_pool_size - Fee Provider tries to keep the number of utxo in utxo pool as this size using `glueby:fee_provider:manage_utxo_pool` rake task
|
58
|
+
def fee_provider_config=(config)
|
59
|
+
FeeProvider.configure(config)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/glueby/contract.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module Glueby
|
2
2
|
module Contract
|
3
3
|
autoload :Errors, 'glueby/contract/errors'
|
4
|
-
autoload :
|
5
|
-
autoload :
|
4
|
+
autoload :FeeEstimator, 'glueby/contract/fee_estimator'
|
5
|
+
autoload :FixedFeeEstimator, 'glueby/contract/fee_estimator'
|
6
6
|
autoload :Payment, 'glueby/contract/payment'
|
7
7
|
autoload :Timestamp, 'glueby/contract/timestamp'
|
8
8
|
autoload :Token, 'glueby/contract/token'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Glueby
|
2
|
+
module Contract
|
3
|
+
module AR
|
4
|
+
class ReissuableToken < ::ActiveRecord::Base
|
5
|
+
|
6
|
+
# Get the script_pubkey corresponding to the color_id in Tapyrus::Script format
|
7
|
+
# @param [String] color_id
|
8
|
+
# @return [Tapyrus::Script]
|
9
|
+
def self.script_pubkey(color_id)
|
10
|
+
script_pubkey = Glueby::Contract::AR::ReissuableToken.where(color_id: color_id).pluck(:script_pubkey).first
|
11
|
+
if script_pubkey
|
12
|
+
Tapyrus::Script.parse_from_payload(script_pubkey.htb)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Check if the color_id is already stored
|
17
|
+
# @param [String] color_id
|
18
|
+
# @return [Boolean]
|
19
|
+
def self.saved?(color_id)
|
20
|
+
Glueby::Contract::AR::ReissuableToken.where(color_id: color_id).exists?
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Glueby
|
2
|
+
module Contract
|
3
|
+
module FeeEstimator
|
4
|
+
# @param [Tapyrus::Tx] tx - The target tx
|
5
|
+
# @return fee by tapyrus(not TPC).
|
6
|
+
def fee(tx)
|
7
|
+
return 0 if Glueby.configuration.fee_provider_bears?
|
8
|
+
estimate_fee(tx)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
# @private
|
14
|
+
# @abstract Override in subclasses. This is would be implemented an actual estimation logic.
|
15
|
+
# @param [Tapyrus::Tx] tx - The target tx
|
16
|
+
# @return fee by tapyrus(not TPC).
|
17
|
+
def estimate_fee(tx)
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class FixedFeeEstimator
|
23
|
+
include FeeEstimator
|
24
|
+
|
25
|
+
def initialize(fixed_fee: 10_000)
|
26
|
+
@fixed_fee = fixed_fee
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# @private
|
32
|
+
# @return fee by tapyrus(not TPC).
|
33
|
+
def estimate_fee(_tx)
|
34
|
+
@fixed_fee
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -11,6 +11,10 @@ module Glueby
|
|
11
11
|
# or
|
12
12
|
# Glueby::Wallet.create
|
13
13
|
#
|
14
|
+
# Use `Glueby::Internal::Wallet#receive_address` to generate the address of a receiver
|
15
|
+
# receiver.internal_wallet.receive_address
|
16
|
+
# => '1CY6TSSARn8rAFD9chCghX5B7j4PKR8S1a'
|
17
|
+
#
|
14
18
|
# Balance of sender and receiver before send
|
15
19
|
# sender.balances[""]
|
16
20
|
# => 100_000(tapyrus)
|
@@ -18,7 +22,7 @@ module Glueby
|
|
18
22
|
# => 0(tapyrus)
|
19
23
|
#
|
20
24
|
# Send
|
21
|
-
# Payment.transfer(sender: sender,
|
25
|
+
# Payment.transfer(sender: sender, receiver_address: '1CY6TSSARn8rAFD9chCghX5B7j4PKR8S1a', amount: 10_000)
|
22
26
|
# sender.balances[""]
|
23
27
|
# => 90_000
|
24
28
|
# receiver.balances[""]
|
@@ -28,25 +32,25 @@ module Glueby
|
|
28
32
|
extend Glueby::Contract::TxBuilder
|
29
33
|
|
30
34
|
class << self
|
31
|
-
def transfer(sender:,
|
35
|
+
def transfer(sender:, receiver_address:, amount:, fee_estimator: FixedFeeEstimator.new)
|
32
36
|
raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
|
33
37
|
|
34
38
|
tx = Tapyrus::Tx.new
|
35
|
-
dummy_fee =
|
39
|
+
dummy_fee = fee_estimator.fee(dummy_tx(tx))
|
36
40
|
|
37
41
|
sum, outputs = sender.internal_wallet.collect_uncolored_outputs(dummy_fee + amount)
|
38
42
|
fill_input(tx, outputs)
|
39
43
|
|
40
|
-
receiver_script = Tapyrus::Script.parse_from_addr(
|
44
|
+
receiver_script = Tapyrus::Script.parse_from_addr(receiver_address)
|
41
45
|
tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_script)
|
42
46
|
|
43
|
-
fee =
|
47
|
+
fee = fee_estimator.fee(tx)
|
44
48
|
|
45
49
|
fill_change_tpc(tx, sender, sum - fee - amount)
|
46
50
|
|
47
51
|
tx = sender.internal_wallet.sign_tx(tx)
|
48
52
|
|
49
|
-
|
53
|
+
sender.internal_wallet.broadcast(tx)
|
50
54
|
end
|
51
55
|
end
|
52
56
|
end
|
@@ -10,15 +10,17 @@ module Glueby
|
|
10
10
|
class Timestamp
|
11
11
|
include Glueby::Contract::TxBuilder
|
12
12
|
|
13
|
+
autoload :Syncer, 'glueby/contract/timestamp/syncer'
|
14
|
+
|
13
15
|
module Util
|
14
16
|
include Glueby::Internal::Wallet::TapyrusCoreWalletAdapter::Util
|
15
17
|
module_function
|
16
18
|
|
17
|
-
def create_tx(wallet, prefix, data,
|
19
|
+
def create_tx(wallet, prefix, data, fee_estimator)
|
18
20
|
tx = Tapyrus::Tx.new
|
19
21
|
tx.outputs << Tapyrus::TxOut.new(value: 0, script_pubkey: create_script(prefix, data))
|
20
22
|
|
21
|
-
fee =
|
23
|
+
fee = fee_estimator.fee(dummy_tx(tx))
|
22
24
|
sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(fee)
|
23
25
|
fill_input(tx, outputs)
|
24
26
|
|
@@ -50,7 +52,7 @@ module Glueby
|
|
50
52
|
|
51
53
|
# @param [String] content Data to be hashed and stored in blockchain.
|
52
54
|
# @param [String] prefix prefix of op_return data
|
53
|
-
# @param [Glueby::Contract::
|
55
|
+
# @param [Glueby::Contract::FeeEstimator] fee_estimator
|
54
56
|
# @param [Symbol] digest type which select of:
|
55
57
|
# - :sha256
|
56
58
|
# - :double_sha256
|
@@ -60,13 +62,13 @@ module Glueby
|
|
60
62
|
wallet:,
|
61
63
|
content:,
|
62
64
|
prefix: '',
|
63
|
-
|
65
|
+
fee_estimator: Glueby::Contract::FixedFeeEstimator.new,
|
64
66
|
digest: :sha256
|
65
67
|
)
|
66
68
|
@wallet = wallet
|
67
69
|
@content = content
|
68
70
|
@prefix = prefix
|
69
|
-
@
|
71
|
+
@fee_estimator = fee_estimator
|
70
72
|
@digest = digest
|
71
73
|
end
|
72
74
|
|
@@ -77,7 +79,7 @@ module Glueby
|
|
77
79
|
def save!
|
78
80
|
raise Glueby::Contract::Errors::TxAlreadyBroadcasted if @txid
|
79
81
|
|
80
|
-
@tx = create_tx(@wallet, @prefix, digest_content, @
|
82
|
+
@tx = create_tx(@wallet, @prefix, digest_content, @fee_estimator)
|
81
83
|
@txid = @wallet.internal_wallet.broadcast(@tx)
|
82
84
|
end
|
83
85
|
|