Class: Steem::TransactionBuilder
- Inherits:
-
Object
- Object
- Steem::TransactionBuilder
- Includes:
- ChainConfig, Retriable, Utils
- Defined in:
- lib/steem/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 = Steem::TransactionBuilder.new(wif: wif)
builder.put(vote: {
voter: 'alice',
author: 'bob',
permlink: 'my-burgers',
weight: 10000
})
trx = builder.transaction
network_broadcast_api = Steem::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_STEEM_ADDRESS_PREFIX, ChainConfig::NETWORKS_STEEM_CHAIN_ID, ChainConfig::NETWORKS_STEEM_CORE_ASSET, ChainConfig::NETWORKS_STEEM_DEBT_ASSET, ChainConfig::NETWORKS_STEEM_DEFAULT_NODE, ChainConfig::NETWORKS_STEEM_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 |
# File 'lib/steem/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 ||= Steem::DatabaseApi.new() @block_api ||= Steem::BlockApi.new() else @database_api ||= Steem::CondenserApi.new() @block_api ||= Steem::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] || :steem @error_pipe = [:error_pipe] || STDERR @chain_id = [:chain_id] @chain_id ||= case @chain when :steem then NETWORKS_STEEM_CHAIN_ID when :test then NETWORKS_TEST_CHAIN_ID else; raise UnsupportedChainError, "Unsupported chain: #{@chain}" end if testnet? && @chain_id == NETWORKS_STEEM_CHAIN_ID raise UnsupportedChainError, "Unsupported testnet chain id: #{@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/steem/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/steem/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/steem/transaction_builder.rb', line 29 def database_api @database_api end |
#expiration ⇒ Object
Returns the value of attribute expiration.
29 30 31 |
# File 'lib/steem/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/steem/transaction_builder.rb', line 31 def force_serialize @force_serialize end |
#operations ⇒ Object
Returns the value of attribute operations.
29 30 31 |
# File 'lib/steem/transaction_builder.rb', line 29 def operations @operations end |
#signed ⇒ Object (readonly)
Returns the value of attribute signed.
31 32 33 |
# File 'lib/steem/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/steem/transaction_builder.rb', line 31 def testnet @testnet end |
#wif=(value) ⇒ Object (writeonly)
Sets the attribute wif
30 31 32 |
# File 'lib/steem/transaction_builder.rb', line 30 def wif=(value) @wif = value end |
Instance Method Details
#inspect ⇒ Object
79 80 81 82 83 84 85 86 87 |
# File 'lib/steem/transaction_builder.rb', line 79 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.
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
# File 'lib/steem/transaction_builder.rb', line 299 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.
103 104 105 106 107 108 109 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 |
# File 'lib/steem/transaction_builder.rb', line 103 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 = Steem::TransactionBuilder.new
builder.put(vote: vote1).put(vote: vote2)
168 169 170 171 172 173 |
# File 'lib/steem/transaction_builder.rb', line 168 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.
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 |
# File 'lib/steem/transaction_builder.rb', line 320 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
89 90 91 92 93 94 |
# File 'lib/steem/transaction_builder.rb', line 89 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.
210 211 212 213 214 215 216 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 |
# File 'lib/steem/transaction_builder.rb', line 210 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 raise SerializationMismatchError unless @trx == derrived_trx 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.
193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/steem/transaction_builder.rb', line 193 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
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/steem/transaction_builder.rb', line 280 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.
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/steem/transaction_builder.rb', line 337 def valid? = if app_base? {trx: transaction} else transaction end @database_api.() do |result| if app_base? result.valid else result end end end |