Class: Hive::TransactionBuilder
- Inherits:
-
Object
- Object
- Hive::TransactionBuilder
- Includes:
- ChainConfig, Retriable, Utils
- Defined in:
- lib/hive/transaction_builder.rb
Overview
TransactionBuilder can be used to create a transaction that the NetworkBroadcastApi can broadcast to the rest of the platform. The main feature of this class is the ability to cryptographically sign the transaction so that it conforms to the consensus rules that are required by the blockchain.
wif = '5JrvPrQeBBvCRdjv29iDvkwn3EQYZ9jqfAHzrCyUvfbEbRkrYFC'
builder = Hive::TransactionBuilder.new(wif: wif)
builder.put(vote: {
voter: 'alice',
author: 'bob',
permlink: 'my-burgers',
weight: 10000
})
trx = builder.transaction
network_broadcast_api = Hive::CondenserApi.new
network_broadcast_api.broadcast_transaction_synchronous(trx: trx)
The ‘wif` value may also be an array, when signing with multiple signatures (multisig).
Constant Summary
Constants included from ChainConfig
ChainConfig::EXPIRE_IN_SECS, ChainConfig::EXPIRE_IN_SECS_PROPOSAL, ChainConfig::NETWORKS_HIVE_ADDRESS_PREFIX, ChainConfig::NETWORKS_HIVE_CHAIN_ID, ChainConfig::NETWORKS_HIVE_CORE_ASSET, ChainConfig::NETWORKS_HIVE_DEBT_ASSET, ChainConfig::NETWORKS_HIVE_DEFAULT_NODE, ChainConfig::NETWORKS_HIVE_LEGACY_CHAIN_ID, ChainConfig::NETWORKS_HIVE_VEST_ASSET, ChainConfig::NETWORKS_TEST_ADDRESS_PREFIX, ChainConfig::NETWORKS_TEST_CHAIN_ID, ChainConfig::NETWORKS_TEST_CORE_ASSET, ChainConfig::NETWORKS_TEST_DEBT_ASSET, ChainConfig::NETWORKS_TEST_DEFAULT_NODE, ChainConfig::NETWORKS_TEST_VEST_ASSET, ChainConfig::NETWORK_CHAIN_IDS
Constants included from Retriable
Retriable::MAX_BACKOFF, Retriable::MAX_RETRY_COUNT, Retriable::MAX_RETRY_ELAPSE, Retriable::RETRYABLE_EXCEPTIONS
Instance Attribute Summary collapse
-
#app_base ⇒ Object
(also: #app_base?)
Returns the value of attribute app_base.
-
#block_api ⇒ Object
Returns the value of attribute block_api.
-
#database_api ⇒ Object
Returns the value of attribute database_api.
-
#expiration ⇒ Object
Returns the value of attribute expiration.
-
#force_serialize ⇒ Object
(also: #force_serialize?)
readonly
Returns the value of attribute force_serialize.
-
#operations ⇒ Object
Returns the value of attribute operations.
-
#signed ⇒ Object
readonly
Returns the value of attribute signed.
-
#testnet ⇒ Object
(also: #testnet?)
readonly
Returns the value of attribute testnet.
-
#wif ⇒ Object
writeonly
Sets the attribute wif.
Instance Method Summary collapse
-
#initialize(options = {}) ⇒ TransactionBuilder
constructor
A new instance of TransactionBuilder.
- #inspect ⇒ Object
-
#potential_signatures ⇒ Array
All public keys that could possibly sign for a given transaction.
-
#prepare ⇒ TransactionBuilder
If the transaction can be prepared, this method will do so and set the expiration.
-
#put(type, op = nil) ⇒ TransactionBuilder
A quick and flexible way to append a new operation to the transaction.
-
#required_signatures ⇒ Array
This API will take a partially signed transaction and a set of public keys that the owner has the ability to sign for and return the minimal subset of public keys that should add signatures to the transaction.
- #reset ⇒ Object
-
#sign ⇒ Hash | TransactionBuilder
Appends to the ‘signatures` array of the transaction, built from a serialized digest.
-
#transaction(options = {prepare: true, sign: true}) ⇒ Object
If all of the required values are set, this returns a fully formed transaction that is ready to broadcast.
- #transaction_hex ⇒ Object
-
#valid? ⇒ Boolean
True if the transaction has all of the required signatures.
Methods included from Utils
Methods included from Retriable
Constructor Details
#initialize(options = {}) ⇒ TransactionBuilder
Returns a new instance of TransactionBuilder.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/hive/transaction_builder.rb', line 37 def initialize( = {}) @app_base = !![:app_base] # default false @database_api = [:database_api] @block_api = [:block_api] if app_base? @database_api ||= Hive::DatabaseApi.new() @block_api ||= Hive::BlockApi.new() else @database_api ||= Hive::CondenserApi.new() @block_api ||= Hive::CondenserApi.new() end @wif = [[:wif]].flatten @signed = false @testnet = !![:testnet] @force_serialize = !![:force_serialize] if !!(trx = [:trx]) trx = case trx when String then JSON[trx] else; trx end @trx = Transaction.new(trx) end @trx ||= Transaction.new @chain = [:chain] || :hive @error_pipe = [:error_pipe] || STDERR @chain_id = [:chain_id] || ENV['HIVE_CHAIN_ID'] @network_chain_id ||= case @chain when :hive then @database_api.get_config{|config| config['HIVE_CHAIN_ID']} rescue NETWORKS_HIVE_CHAIN_ID when :test then @database_api.get_config{|config| config['HIVE_CHAIN_ID']} rescue NETWORKS_TEST_CHAIN_ID else; raise UnsupportedChainError, "Unsupported chain: #{@chain}" end @chain_id ||= @network_chain_id if testnet? && (@chain_id == NETWORKS_HIVE_CHAIN_ID || @chain_id == NETWORKS_HIVE_LEGACY_CHAIN_ID) raise UnsupportedChainError, "Unsupported testnet chain id: #{@chain_id}" end if @chain_id != @network_chain_id raise UnsupportedChainError, "Unsupported chain id (expected: #{@chain_id}, network was: #{@network_chain_id})" end end |
Instance Attribute Details
#app_base ⇒ Object Also known as: app_base?
Returns the value of attribute app_base.
29 30 31 |
# File 'lib/hive/transaction_builder.rb', line 29 def app_base @app_base end |
#block_api ⇒ Object
Returns the value of attribute block_api.
29 30 31 |
# File 'lib/hive/transaction_builder.rb', line 29 def block_api @block_api end |
#database_api ⇒ Object
Returns the value of attribute database_api.
29 30 31 |
# File 'lib/hive/transaction_builder.rb', line 29 def database_api @database_api end |
#expiration ⇒ Object
Returns the value of attribute expiration.
29 30 31 |
# File 'lib/hive/transaction_builder.rb', line 29 def expiration @expiration end |
#force_serialize ⇒ Object (readonly) Also known as: force_serialize?
Returns the value of attribute force_serialize.
31 32 33 |
# File 'lib/hive/transaction_builder.rb', line 31 def force_serialize @force_serialize end |
#operations ⇒ Object
Returns the value of attribute operations.
29 30 31 |
# File 'lib/hive/transaction_builder.rb', line 29 def operations @operations end |
#signed ⇒ Object (readonly)
Returns the value of attribute signed.
31 32 33 |
# File 'lib/hive/transaction_builder.rb', line 31 def signed @signed end |
#testnet ⇒ Object (readonly) Also known as: testnet?
Returns the value of attribute testnet.
31 32 33 |
# File 'lib/hive/transaction_builder.rb', line 31 def testnet @testnet end |
#wif=(value) ⇒ Object (writeonly)
Sets the attribute wif
30 31 32 |
# File 'lib/hive/transaction_builder.rb', line 30 def wif=(value) @wif = value end |
Instance Method Details
#inspect ⇒ Object
86 87 88 89 90 91 92 93 94 |
# File 'lib/hive/transaction_builder.rb', line 86 def inspect properties = %w(trx).map do |prop| if !!(v = instance_variable_get("@#{prop}")) "@#{prop}=#{v.inspect}" end end.compact.join(', ') "#<#{self.class.name} [#{properties}]>" end |
#potential_signatures ⇒ Array
Returns All public keys that could possibly sign for a given transaction.
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/hive/transaction_builder.rb', line 312 def potential_signatures potential_signatures_args = if app_base? {trx: transaction} else transaction end @database_api.get_potential_signatures(potential_signatures_args) do |result| if app_base? result[:keys] else result end end end |
#prepare ⇒ TransactionBuilder
If the transaction can be prepared, this method will do so and set the expiration. Once the expiration is set, it will not re-prepare. If you call #put, the expiration is set Nil so that it can be re-prepared.
Usually, this method is called automatically by #put and/or #transaction.
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/hive/transaction_builder.rb', line 110 def prepare if @trx.expired? catch :prepare_header do; begin @database_api.get_dynamic_global_properties do |properties| block_number = properties.last_irreversible_block_num block_header_args = if app_base? {block_num: block_number} else block_number end @block_api.get_block_header(block_header_args) do |result| header = if app_base? result.header else result end @trx.ref_block_num = (block_number - 1) & 0xFFFF @trx.ref_block_prefix = unhexlify(header.previous[8..-1]).unpack('V*')[0] @trx.expiration ||= (Time.parse(properties.time + 'Z') + EXPIRE_IN_SECS).utc end end rescue => e if can_retry? e @error_pipe.puts "#{e} ... retrying." throw :prepare_header else raise e end end; end end self end |
#put(type, op = nil) ⇒ TransactionBuilder
A quick and flexible way to append a new operation to the transaction. This method uses ducktyping to figure out how to form the operation.
There are three main ways you can call this method. These assume that ‘op_type` is a Symbol (or String) representing the type of operation and `op` is the operation Hash.
put(op_type, op)
… or …
put(op_type => op)
… or …
put([op_type, op])
You can also chain multiple operations:
builder = Hive::TransactionBuilder.new
builder.put(vote: vote1).put(vote: vote2)
175 176 177 178 179 180 |
# File 'lib/hive/transaction_builder.rb', line 175 def put(type, op = nil) @trx.expiration = nil @trx.operations << normalize_operation(type, op) prepare self end |
#required_signatures ⇒ Array
This API will take a partially signed transaction and a set of public keys that the owner has the ability to sign for and return the minimal subset of public keys that should add signatures to the transaction.
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 |
# File 'lib/hive/transaction_builder.rb', line 333 def required_signatures required_signatures_args = if app_base? {trx: transaction} else [transaction, []] end @database_api.get_required_signatures(*required_signatures_args) do |result| if app_base? result[:keys] else result end end end |
#reset ⇒ Object
96 97 98 99 100 101 |
# File 'lib/hive/transaction_builder.rb', line 96 def reset @trx = Transaction.new @signed = false self end |
#sign ⇒ Hash | TransactionBuilder
Appends to the ‘signatures` array of the transaction, built from a serialized digest.
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 |
# File 'lib/hive/transaction_builder.rb', line 217 def sign return self if @wif.empty? return self if @trx.expired? unless @signed catch :serialize do; begin transaction_hex.tap do |result| hex = if app_base? result.hex else result end unless force_serialize? derrived_trx = Transaction.new(hex: hex) derrived_ops = derrived_trx.operations derrived_trx.operations = derrived_ops.map do |op| op_name = if app_base? op[:type].to_sym else op[:type].to_s.sub(/_operation$/, '').to_sym end normalize_operation op_name, JSON[op[:value].to_json] end unless @trx == derrived_trx if defined? JsonCompare raise SerializationMismatchError, JSON.pretty_generate({trx: @trx, derrived_trx: derrived_trx}) else raise SerializationMismatchError end end end hex = hex[0..-4] # drop empty signature array @trx.id = Digest::SHA256.hexdigest(unhexlify(hex))[0..39] hex = @chain_id + hex digest = unhexlify(hex) digest_hex = Digest::SHA256.digest(digest) private_keys = @wif.map{ |wif| Bitcoin::Key.from_base58 wif } ec = Bitcoin::OpenSSL_EC count = 0 private_keys.each do |private_key| sig = nil loop do count += 1 @error_pipe.puts "#{count} attempts to find canonical signature" if count % 40 == 0 public_key_hex = private_key.pub sig = ec.sign_compact(digest_hex, private_key.priv, public_key_hex, false) next if public_key_hex != ec.recover_compact(digest_hex, sig) break if canonical? sig end @trx.signatures << hexlify(sig) end @signed = true end rescue => e if can_retry? e @error_pipe.puts "#{e} ... retrying." throw :serialize else raise e end end; end end @trx end |
#transaction(options = {prepare: true, sign: true}) ⇒ Object
If all of the required values are set, this returns a fully formed transaction that is ready to broadcast.
200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/hive/transaction_builder.rb', line 200 def transaction( = {prepare: true, sign: true}) [:prepare] = true unless .has_key? :prepare [:sign] = true unless .has_key? :sign prepare if !![:prepare] if !![:sign] sign else @trx end end |
#transaction_hex ⇒ Object
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 |
# File 'lib/hive/transaction_builder.rb', line 293 def transaction_hex trx = transaction(prepare: true, sign: false) transaction_hex_args = if app_base? {trx: trx} else trx end @database_api.get_transaction_hex(transaction_hex_args) do |result| if app_base? result[:hex] else result end end end |
#valid? ⇒ Boolean
Returns True if the transaction has all of the required signatures.
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 |
# File 'lib/hive/transaction_builder.rb', line 350 def valid? = if app_base? {trx: transaction} else transaction end @database_api.() do |result| if app_base? result.valid else result end end end |