Class: DPay::TransactionBuilder
- Inherits:
-
Object
- Object
- DPay::TransactionBuilder
- Includes:
- ChainConfig, Retriable, Utils
- Defined in:
- lib/dpay/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 = DPay::TransactionBuilder.new(wif: wif)
builder.put(vote: {
voter: 'alice',
author: 'bob',
permlink: 'my-burgers',
weight: 10000
})
trx = builder.transaction
network_broadcast_api = DPay::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_DPAY_ADDRESS_PREFIX, ChainConfig::NETWORKS_DPAY_CHAIN_ID, ChainConfig::NETWORKS_DPAY_CORE_ASSET, ChainConfig::NETWORKS_DPAY_DEBT_ASSET, ChainConfig::NETWORKS_DPAY_DEFAULT_NODE, ChainConfig::NETWORKS_DPAY_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.
-
#operations ⇒ Object
Returns the value of attribute operations.
-
#signed ⇒ Object
readonly
Returns the value of attribute signed.
-
#wif ⇒ Object
writeonly
Sets the attribute wif.
Instance Method Summary collapse
- #expired? ⇒ Boolean
-
#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 ⇒ Object
If all of the required values are set, this returns a fully formed transaction that is ready to broadcast.
-
#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.
35 36 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 85 86 |
# File 'lib/dpay/transaction_builder.rb', line 35 def initialize( = {}) @app_base = !![:app_base] # default false @database_api = [:database_api] @block_api = [:block_api] if app_base? @database_api ||= DPay::DatabaseApi.new() @block_api ||= DPay::BlockApi.new() else @database_api ||= DPay::CondenserApi.new() @block_api ||= DPay::CondenserApi.new() end @wif = [[:wif]].flatten @signed = false if !!(trx = [:trx]) trx = case trx when String then JSON[trx] else; trx end = { ref_block_num: trx['ref_block_num'], ref_block_prefix: trx['ref_block_prefix'], extensions: (trx['extensions']), operations: trx['operations'], signatures: (trx['signatures']), } [:expiration] = case trx['expiration'] when String then Time.parse(trx['expiration'] + 'Z') else; trx['expiration'] end = .merge() end @ref_block_num = [:ref_block_num] @ref_block_prefix = [:ref_block_prefix] @operations = [:operations] || [] @expiration = [:expiration] @extensions = [:extensions] || [] @signatures = [:signatures] || [] @chain = [:chain] || :dpay @error_pipe = [:error_pipe] || STDERR @chain_id = case @chain when :dpay then NETWORKS_DPAY_CHAIN_ID when :test then NETWORKS_TEST_CHAIN_ID else; raise UnsupportedChainError, "Unsupported chain: #{@chain}" 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/dpay/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/dpay/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/dpay/transaction_builder.rb', line 29 def database_api @database_api end |
#expiration ⇒ Object
Returns the value of attribute expiration.
29 30 31 |
# File 'lib/dpay/transaction_builder.rb', line 29 def expiration @expiration end |
#operations ⇒ Object
Returns the value of attribute operations.
29 30 31 |
# File 'lib/dpay/transaction_builder.rb', line 29 def operations @operations end |
#signed ⇒ Object (readonly)
Returns the value of attribute signed.
31 32 33 |
# File 'lib/dpay/transaction_builder.rb', line 31 def signed @signed end |
#wif=(value) ⇒ Object (writeonly)
Sets the attribute wif
30 31 32 |
# File 'lib/dpay/transaction_builder.rb', line 30 def wif=(value) @wif = value end |
Instance Method Details
#expired? ⇒ Boolean
113 114 115 |
# File 'lib/dpay/transaction_builder.rb', line 113 def expired? @expiration.nil? || @expiration < Time.now end |
#inspect ⇒ Object
88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/dpay/transaction_builder.rb', line 88 def inspect properties = %w( ref_block_num ref_block_prefix expiration operations extensions signatures ).map do |prop| if !!(v = instance_variable_get("@#{prop}")) "@#{prop}=#{v}" end end.compact.join(', ') "#<#{self.class.name} [#{properties}]>" end |
#potential_signatures ⇒ Array
Returns All public keys that could possibly sign for a given transaction.
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/dpay/transaction_builder.rb', line 292 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.
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/dpay/transaction_builder.rb', line 124 def prepare if 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 @ref_block_num = (block_number - 1) & 0xFFFF @ref_block_prefix = unhexlify(header.previous[8..-1]).unpack('V*')[0] @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 = DPay::TransactionBuilder.new
builder.put(vote: vote1).put(vote: vote2)
189 190 191 192 193 194 |
# File 'lib/dpay/transaction_builder.rb', line 189 def put(type, op = nil) @expiration = nil @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.
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/dpay/transaction_builder.rb', line 313 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
101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/dpay/transaction_builder.rb', line 101 def reset @ref_block_num = nil @ref_block_prefix = nil @expiration = nil @operations = [] @extensions = [] @signatures = [] @signed = false self end |
#sign ⇒ Hash | TransactionBuilder
Appends to the ‘signatures` array of the transaction, built from a serialized digest.
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 |
# File 'lib/dpay/transaction_builder.rb', line 223 def sign return self if @wif.empty? return self if expired? trx = { ref_block_num: @ref_block_num, ref_block_prefix: @ref_block_prefix, expiration: @expiration.strftime('%Y-%m-%dT%H:%M:%S'), operations: @operations, extensions: @extensions, signatures: @signatures } unless @signed catch :serialize do; begin transaction_hex_args = if app_base? {trx: trx} else trx end @database_api.get_transaction_hex(transaction_hex_args) do |result| hex = if app_base? result.hex else result end hex = @chain_id + hex[0..-4] # Why do we have to chop the last two bytes? 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 sigs = [] 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 @signatures << hexlify(sig) end @signed = true trx[:signatures] = @signatures end rescue => e if can_retry? e @error_pipe.puts "#{e} ... retrying." throw :serialize else raise e end end; end end Hashie::Mash.new trx end |
#transaction ⇒ Object
If all of the required values are set, this returns a fully formed transaction that is ready to broadcast.
214 215 216 217 |
# File 'lib/dpay/transaction_builder.rb', line 214 def transaction prepare sign end |
#valid? ⇒ Boolean
Returns True if the transaction has all of the required signatures.
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 |
# File 'lib/dpay/transaction_builder.rb', line 330 def valid? = if app_base? {trx: transaction} else transaction end @database_api.() do |result| if app_base? result.valid else result end end end |