From c584f8c6823cbb5eb0b5f99f67c627be6b18d853 Mon Sep 17 00:00:00 2001 From: Anthony Martin <github@martin-studio.com> Date: Mon, 29 Oct 2018 20:25:59 -0700 Subject: [PATCH] initial deserializer #14 --- Gemfile.lock | 8 +- README.md | 2 +- lib/steem.rb | 48 +++ lib/steem/base_error.rb | 8 +- lib/steem/broadcast.rb | 19 +- lib/steem/marshal.rb | 224 +++++++++++ lib/steem/mixins/jsonable.rb | 33 ++ lib/steem/mixins/serializable.rb | 37 ++ lib/steem/operation.rb | 118 ++++++ lib/steem/operation/account_create.rb | 10 + .../account_create_with_delegation.rb | 12 + lib/steem/operation/account_update.rb | 8 + lib/steem/operation/account_witness_proxy.rb | 4 + lib/steem/operation/account_witness_vote.rb | 5 + .../operation/cancel_transfer_from_savings.rb | 4 + lib/steem/operation/challenge_authority.rb | 5 + .../operation/change_recovery_account.rb | 5 + lib/steem/operation/claim_account.rb | 5 + lib/steem/operation/claim_reward_balance.rb | 6 + lib/steem/operation/comment.rb | 9 + lib/steem/operation/comment_options.rb | 10 + lib/steem/operation/convert.rb | 5 + lib/steem/operation/create_claimed_account.rb | 10 + lib/steem/operation/custom.rb | 5 + lib/steem/operation/custom_binary.rb | 8 + lib/steem/operation/custom_json.rb | 6 + lib/steem/operation/decline_voting_rights.rb | 4 + .../operation/delegate_vesting_shares.rb | 5 + lib/steem/operation/delete_comment.rb | 4 + lib/steem/operation/escrow_approve.rb | 8 + lib/steem/operation/escrow_dispute.rb | 7 + lib/steem/operation/escrow_release.rb | 10 + lib/steem/operation/escrow_transfer.rb | 12 + lib/steem/operation/feed_publish.rb | 4 + lib/steem/operation/limit_order_cancel.rb | 4 + lib/steem/operation/limit_order_create.rb | 8 + lib/steem/operation/limit_order_create2.rb | 8 + lib/steem/operation/prove_authority.rb | 4 + lib/steem/operation/recover_account.rb | 6 + lib/steem/operation/report_over_production.rb | 5 + .../operation/request_account_recovery.rb | 6 + lib/steem/operation/reset_account.rb | 5 + lib/steem/operation/set_reset_account.rb | 5 + .../operation/set_withdraw_vesting_route.rb | 6 + lib/steem/operation/transfer.rb | 6 + lib/steem/operation/transfer_from_savings.rb | 7 + lib/steem/operation/transfer_to_savings.rb | 6 + lib/steem/operation/transfer_to_vesting.rb | 5 + lib/steem/operation/vote.rb | 6 + lib/steem/operation/withdraw_vesting.rb | 4 + lib/steem/operation/witness_set_properties.rb | 5 + lib/steem/operation/witness_update.rb | 7 + lib/steem/transaction.rb | 86 ++++ lib/steem/transaction_builder.rb | 135 ++++--- lib/steem/type/amount.rb | 2 + steem-ruby.gemspec | 2 + test/steem/api_test.rb | 6 +- test/steem/broadcast_test.rb | 8 +- test/steem/database_api_test.rb | 36 ++ test/steem/jsonrpc_test.rb | 221 +++++++++- test/steem/marshal_test.rb | 377 ++++++++++++++++++ test/steem/transaction_builder_test.rb | 12 +- test/steem/witness_api_test.rb | 52 --- test/test_helper.rb | 1 + 64 files changed, 1567 insertions(+), 142 deletions(-) create mode 100644 lib/steem/marshal.rb create mode 100644 lib/steem/mixins/jsonable.rb create mode 100644 lib/steem/mixins/serializable.rb create mode 100644 lib/steem/operation.rb create mode 100644 lib/steem/operation/account_create.rb create mode 100644 lib/steem/operation/account_create_with_delegation.rb create mode 100644 lib/steem/operation/account_update.rb create mode 100644 lib/steem/operation/account_witness_proxy.rb create mode 100644 lib/steem/operation/account_witness_vote.rb create mode 100644 lib/steem/operation/cancel_transfer_from_savings.rb create mode 100644 lib/steem/operation/challenge_authority.rb create mode 100644 lib/steem/operation/change_recovery_account.rb create mode 100644 lib/steem/operation/claim_account.rb create mode 100644 lib/steem/operation/claim_reward_balance.rb create mode 100644 lib/steem/operation/comment.rb create mode 100644 lib/steem/operation/comment_options.rb create mode 100644 lib/steem/operation/convert.rb create mode 100644 lib/steem/operation/create_claimed_account.rb create mode 100644 lib/steem/operation/custom.rb create mode 100644 lib/steem/operation/custom_binary.rb create mode 100644 lib/steem/operation/custom_json.rb create mode 100644 lib/steem/operation/decline_voting_rights.rb create mode 100644 lib/steem/operation/delegate_vesting_shares.rb create mode 100644 lib/steem/operation/delete_comment.rb create mode 100644 lib/steem/operation/escrow_approve.rb create mode 100644 lib/steem/operation/escrow_dispute.rb create mode 100644 lib/steem/operation/escrow_release.rb create mode 100644 lib/steem/operation/escrow_transfer.rb create mode 100644 lib/steem/operation/feed_publish.rb create mode 100644 lib/steem/operation/limit_order_cancel.rb create mode 100644 lib/steem/operation/limit_order_create.rb create mode 100644 lib/steem/operation/limit_order_create2.rb create mode 100644 lib/steem/operation/prove_authority.rb create mode 100644 lib/steem/operation/recover_account.rb create mode 100644 lib/steem/operation/report_over_production.rb create mode 100644 lib/steem/operation/request_account_recovery.rb create mode 100644 lib/steem/operation/reset_account.rb create mode 100644 lib/steem/operation/set_reset_account.rb create mode 100644 lib/steem/operation/set_withdraw_vesting_route.rb create mode 100644 lib/steem/operation/transfer.rb create mode 100644 lib/steem/operation/transfer_from_savings.rb create mode 100644 lib/steem/operation/transfer_to_savings.rb create mode 100644 lib/steem/operation/transfer_to_vesting.rb create mode 100644 lib/steem/operation/vote.rb create mode 100644 lib/steem/operation/withdraw_vesting.rb create mode 100644 lib/steem/operation/witness_set_properties.rb create mode 100644 lib/steem/operation/witness_update.rb create mode 100644 lib/steem/transaction.rb create mode 100644 test/steem/marshal_test.rb delete mode 100644 test/steem/witness_api_test.rb diff --git a/Gemfile.lock b/Gemfile.lock index f3c6a18..5dc8dac 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,6 +2,8 @@ PATH remote: . specs: steem-ruby (0.9.3) + base58 (~> 0.2, >= 0.2.3) + bindata (~> 2.4, >= 2.4.4) bitcoin-ruby (~> 0.0, >= 0.0.18) ffi (~> 1.9, >= 1.9.23) hashie (~> 3.5, >= 3.5.7) @@ -14,6 +16,8 @@ GEM addressable (2.5.2) public_suffix (>= 2.0.2, < 4.0) awesome_print (1.8.0) + base58 (0.2.3) + bindata (2.4.4) bitcoin-ruby (0.0.18) coderay (1.1.2) crack (0.4.3) @@ -37,7 +41,7 @@ GEM pry (0.11.3) coderay (~> 1.1.0) method_source (~> 0.9.0) - public_suffix (3.0.2) + public_suffix (3.0.3) rake (12.3.1) safe_yaml (1.0.4) simplecov (0.16.1) @@ -50,7 +54,7 @@ GEM addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff - yard (0.9.15) + yard (0.9.16) PLATFORMS ruby diff --git a/README.md b/README.md index 1b207f5..fee66ff 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ trx = open('trx.json').read builder = Steem::TransactionBuilder.new(wif: wif2, trx: trx) api = Steem::CondenserApi.new trx = builder.transaction -api.broadcast_transaction_synchronous(trx: trx) +api.broadcast_transaction_synchronous(trx) ``` ### Get Accounts diff --git a/lib/steem.rb b/lib/steem.rb index 868f82f..b67bb21 100644 --- a/lib/steem.rb +++ b/lib/steem.rb @@ -6,10 +6,58 @@ require 'hashie' require 'steem/version' require 'steem/utils' require 'steem/base_error' +require 'steem/mixins/serializable' +require 'steem/mixins/jsonable' require 'steem/mixins/retriable' require 'steem/chain_config' require 'steem/type/base_type' require 'steem/type/amount' +require 'steem/operation' +require 'steem/operation/account_create.rb' +require 'steem/operation/account_create_with_delegation.rb' +require 'steem/operation/account_update.rb' +require 'steem/operation/account_witness_proxy.rb' +require 'steem/operation/account_witness_vote.rb' +require 'steem/operation/cancel_transfer_from_savings.rb' +require 'steem/operation/challenge_authority.rb' +require 'steem/operation/change_recovery_account.rb' +require 'steem/operation/claim_account.rb' +require 'steem/operation/claim_reward_balance.rb' +require 'steem/operation/comment.rb' +require 'steem/operation/comment_options.rb' +require 'steem/operation/convert.rb' +require 'steem/operation/create_claimed_account.rb' +require 'steem/operation/custom.rb' +require 'steem/operation/custom_binary.rb' +require 'steem/operation/custom_json.rb' +require 'steem/operation/decline_voting_rights.rb' +require 'steem/operation/delegate_vesting_shares.rb' +require 'steem/operation/delete_comment.rb' +require 'steem/operation/escrow_approve.rb' +require 'steem/operation/escrow_dispute.rb' +require 'steem/operation/escrow_release.rb' +require 'steem/operation/escrow_transfer.rb' +require 'steem/operation/feed_publish.rb' +require 'steem/operation/limit_order_cancel.rb' +require 'steem/operation/limit_order_create.rb' +require 'steem/operation/limit_order_create2.rb' +require 'steem/operation/prove_authority.rb' +require 'steem/operation/recover_account.rb' +require 'steem/operation/report_over_production.rb' +require 'steem/operation/request_account_recovery.rb' +require 'steem/operation/reset_account.rb' +require 'steem/operation/set_reset_account.rb' +require 'steem/operation/set_withdraw_vesting_route.rb' +require 'steem/operation/transfer.rb' +require 'steem/operation/transfer_from_savings.rb' +require 'steem/operation/transfer_to_savings.rb' +require 'steem/operation/transfer_to_vesting.rb' +require 'steem/operation/vote.rb' +require 'steem/operation/withdraw_vesting.rb' +require 'steem/operation/witness_update.rb' +require 'steem/operation/witness_set_properties.rb' +require 'steem/marshal' +require 'steem/transaction' require 'steem/transaction_builder' require 'steem/rpc/base_client' require 'steem/rpc/http_client' diff --git a/lib/steem/base_error.rb b/lib/steem/base_error.rb index 8a7f7b3..400c847 100644 --- a/lib/steem/base_error.rb +++ b/lib/steem/base_error.rb @@ -10,7 +10,7 @@ module Steem detail[:error] = @error if !!@error detail[:cause] = @cause if !!@cause - JSON[detail] rescue detai.to_s + JSON[detail] rescue detail.to_s end def self.build_error(error, context) @@ -19,11 +19,11 @@ module Steem end if error.message.include? 'Internal Error' - raise Steem::RemoteNodeError.new, error.message, build_backtrace(error) + raise Steem::RemoteNodeError, error.message, build_backtrace(error) end if error.message.include? 'Server error' - raise Steem::RemoteNodeError.new, error.message, build_backtrace(error) + raise Steem::RemoteNodeError, error.message, build_backtrace(error) end if error.message.include? 'plugin not enabled' @@ -176,6 +176,8 @@ module Steem end end + class DeserializationError < BaseError; end + class SerializationMismatchError < BaseError; end class UnsupportedChainError < BaseError; end class ArgumentError < BaseError; end class TypeError < BaseError; end diff --git a/lib/steem/broadcast.rb b/lib/steem/broadcast.rb index 4eb1f4e..b53fc69 100644 --- a/lib/steem/broadcast.rb +++ b/lib/steem/broadcast.rb @@ -203,7 +203,7 @@ module Steem } if !!params[:beneficiaries] - comment_options[:extensions] << [0, {beneficiaries: params[:beneficiaries]}] + comment_options[:extensions] << [comment_options[:extensions].size, normalize_beneficiaries(options.merge(beneficiaries: params[:beneficiaries]))] end ops << [:comment_options, comment_options] @@ -714,13 +714,21 @@ module Steem end if !!(sbd_exchange_rate = params[:props][:sbd_exchange_rate] rescue nil) - params[:props][:sbd_exchange_rate] = normalize_amount(options.merge amount: sbd_exchange_rate, serialize: true) + params[:props][:sbd_exchange_rate][:base] = normalize_amount(options.merge amount: sbd_exchange_rate[:base], serialize: true) + params[:props][:sbd_exchange_rate][:quote] = normalize_amount(options.merge amount: sbd_exchange_rate[:quote], serialize: true) + params[:props][:sbd_exchange_rate] = params[:props][:sbd_exchange_rate].to_json + end + + %i(key new_signing_key).each do |key| + if !!params[key] && params[key].size == 53 + params[key] = params[key][3..-1] + end end %i(account_creation_fee sbd_exchange_rate url new_signing_key).each do |key| next unless !!params[:props][key] - val = params[:props][key] + val = params[:props][key].to_s params[:props][key] = hexlify val unless val =~ /^[0-9A-F]+$/i end @@ -1282,6 +1290,11 @@ module Steem end end + def self.normalize_beneficiaries(options) + # Type::Beneficiaries.new(options[:beneficiaries]) + {beneficiaries: options[:beneficiaries]} + end + # @private def self.database_api(options) options[:database_api] ||= if !!options[:app_base] diff --git a/lib/steem/marshal.rb b/lib/steem/marshal.rb new file mode 100644 index 0000000..7d45667 --- /dev/null +++ b/lib/steem/marshal.rb @@ -0,0 +1,224 @@ +require 'bindata' +require 'base58' + +module Steem + class Marshal + include Utils + include ChainConfig + + attr_reader :bytes, :cursor + + def initialize(options = {}) + @bytes = if !!(hex = options[:hex]) + unhexlify hex + else + options[:bytes] + end + + @chain = options[:chain] || :steem + @prefix ||= case @chain + when :steem then NETWORKS_STEEM_ADDRESS_PREFIX + when :test then NETWORKS_TEST_ADDRESS_PREFIX + else; raise UnsupportedChainError, "Unsupported chain: #{@chain}" + end + @cursor = 0 + end + + def hex + hexlify bytes + end + + def rewind! + @cursor = 0 + end + + def step(n = 0) + @cursor += n + end + + def scan(len) + bytes.slice(@cursor..(@cursor - 1) + len).tap { |_| @cursor += len } + end + + def operation_type + Operation::IDS[unsigned_char] + end + + def unsigned_char; BinData::Uint8le.read(scan(1)); end # 8-bit unsigned + def uint16; BinData::Uint16le.read(scan(2)); end # 16-bit unsigned, VAX (little-endian) byte order + def uint32; BinData::Uint32le.read(scan(4)); end # 32-bit unsigned, VAX (little-endian) byte order + def uint64; BinData::Uint64le.read(scan(8)); end # 64-bit unsigned, little-endian + + def signed_char; BinData::Int8le.read(scan(1)); end # 8-bit signed + def int16; BinData::Int16le.read(scan(2)); end # 16-bit signed, little-endian + def int32; BinData::Int32le.read(scan(4)); end # 32-bit signed, little-endian + def int64; BinData::Int64le.read(scan(8)); end # 64-bit signed, little-endian + + def boolean; BinData::Bit1.read(scan(1)) != 0; end + + def varint + shift = 0 + result = 0 + bytes = [] + + while (n = unsigned_char) >> 7 == 1 + bytes << n + end + + bytes << n + + bytes.each do |b| + result += ((b & 0x7f) << shift) + break unless (b & 0x80) + shift += 7 + end + + result + end + + def string(len = nil); scan(len || varint); end + + def raw_bytes(len = nil); scan(len || varint).force_encoding('BINARY'); end + + def point_in_time + if (time = uint32) == 2**32-1 + Time.at -1 + else + Time.at time + end + end + + def public_key(prefix = @prefix) + raw_public_key = raw_bytes(33) + checksum = OpenSSL::Digest::RIPEMD160.digest(raw_public_key) + key = Base58.binary_to_base58(raw_public_key + checksum.slice(0, 4), :bitcoin) + + prefix + key + end + + def amount + amount = uint64.to_f + precision = signed_char + asset = scan(7).strip + + amount = "%.#{precision}f #{asset}" % (amount / 10 ** precision) + + Steem::Type::Amount.new(amount) + end + + def price + {base: amount, quote: amount} + end + + def authority(options = {optional: false}) + return if !!options[:optional] && unsigned_char == 0 + + { + weight_threshold: uint32, + account_auths: varint.times.map { [string, uint16] }, + key_auths: varint.times.map { [public_key, uint16] } + } + end + + def optional_authority + authority(optional: true) + end + + def comment_options_extensions + beneficiaries + end + + def beneficiaries + varint.times.map { + {account: string, weight: uint16} + } + end + + def chain_properties + { + account_creation_fee: amount, + maximum_block_size: uint32, + sbd_interest_rate: uint16 + } + end + + def required_auths + varint.times.map { string } + end + + def witness_properties + properties = {} + + varint.times do + key = string.to_sym + properties[key] = case key + when :account_creation_fee then Steem::Type::Amount.new(string) + when :account_subsidy_budget then scan(3) + when :account_subsidy_decay, :maximum_block_size then uint32 + when :url then string + when :sbd_exchange_rate + JSON[string].tap do |rate| + rate["base"] = Steem::Type::Amount.new(rate["base"]) + rate["quote"] = Steem::Type::Amount.new(rate["quote"]) + end + when :sbd_interest_rate then uint16 + when :key, :new_signing_key then @prefix + scan(50) + else; raise "Unknown witness property: #{key}" + end + end + + properties + end + + def empty_array + unsigned_char == 0 and [] or raise "Found non-empty array." + end + + def transaction(options = {}) + trx = options[:trx] || Transaction.new + + trx.ref_block_num = uint16 + trx.ref_block_prefix = uint32 + trx.expiration = point_in_time + + trx.operations = operations + + trx + rescue => e + raise DeserializationError.new("Transaction failed\nOriginal serialized bytes:\n[#{hex[0..(@cursor * 2) - 1]}]#{hex[((@cursor) * 2)..-1]}", e) + end + + def operations + operations_len = signed_char + operations = [] + + while operations.size < operations_len do + begin + type = operation_type + break if type.nil? + + op_class_name = type.to_s.sub!(/_operation$/, '') + op_class_name = "Steem::Operation::" + op_class_name.split('_').map(&:capitalize).join + op_class = Object::const_get(op_class_name) + op = op_class.new + + op_class::serializable_types.each do |k, v| + begin + op.send("#{k}=", send(v)) + rescue => e + raise DeserializationError.new("#{type}.#{k} (#{v}) failed", e) + end + end + + operations << {type: type, value: op} + rescue => e + raise DeserializationError.new("#{type} failed", e) + end + end + + operations + rescue => e + raise DeserializationError.new("Operations failed", e) + end + end +end diff --git a/lib/steem/mixins/jsonable.rb b/lib/steem/mixins/jsonable.rb new file mode 100644 index 0000000..dc8630f --- /dev/null +++ b/lib/steem/mixins/jsonable.rb @@ -0,0 +1,33 @@ +module Steem + module JSONable + module ClassMethods + attr_accessor :attributes + + def attr_accessor *attrs + self.attributes = Array attrs + + super + end + end + + def self.included(base) + base.extend(ClassMethods) + end + + def as_json options = {} + serialized = Hash.new + + self.class.attributes.each do |attribute| + if !!(value = self.public_send attribute) + serialized[attribute] = value + end + end + + serialized + end + + def to_json *a + as_json.to_json *a + end + end +end diff --git a/lib/steem/mixins/serializable.rb b/lib/steem/mixins/serializable.rb new file mode 100644 index 0000000..93fc8ea --- /dev/null +++ b/lib/steem/mixins/serializable.rb @@ -0,0 +1,37 @@ +module Steem + module Serializable + KNOWN_TYPES = %i(unsigned_char uint16 uint32 uint64 signed_char int16 int32 + int64 boolean varint string raw_bytes point_in_time public_key amount + price authority optional_authority comment_options_extensions + beneficiaries chain_properties required_auths witness_properties + empty_array lambda) + + module ClassMethods + def def_attr key_pair + name = key_pair.keys.first + type = key_pair.values.first + + self.attributes ||= [] + self.attributes << name + + attr_accessor *attributes + add_type name, type + end + + def add_type name, type + raise "Unknown type: #{type}" unless KNOWN_TYPES.include? type + + @serializable_types ||= {} + @serializable_types[name] = type + end + + def serializable_types + @serializable_types + end + end + + def self.included(base) + base.extend(ClassMethods) + end + end +end diff --git a/lib/steem/operation.rb b/lib/steem/operation.rb new file mode 100644 index 0000000..1e71d07 --- /dev/null +++ b/lib/steem/operation.rb @@ -0,0 +1,118 @@ +module Steem + class Operation + include JSONable + include Serializable + include Utils + + # IDs derrived from: + # https://github.com/steemit/steem/blob/127a441fbac2f06804359968bda83b66e602c891/libraries/protocol/include/steem/protocol/operations.hpp + + IDS = [ + :vote_operation, + :comment_operation, + + :transfer_operation, + :transfer_to_vesting_operation, + :withdraw_vesting_operation, + + :limit_order_create_operation, + :limit_order_cancel_operation, + + :feed_publish_operation, + :convert_operation, + + :account_create_operation, + :account_update_operation, + + :witness_update_operation, + :account_witness_vote_operation, + :account_witness_proxy_operation, + + :pow_operation, + + :custom_operation, + + :report_over_production_operation, + + :delete_comment_operation, + :custom_json_operation, + :comment_options_operation, + :set_withdraw_vesting_route_operation, + :limit_order_create2_operation, + :claim_account_operation, + :create_claimed_account_operation, + :request_account_recovery_operation, + :recover_account_operation, + :change_recovery_account_operation, + :escrow_transfer_operation, + :escrow_dispute_operation, + :escrow_release_operation, + :pow2_operation, + :escrow_approve_operation, + :transfer_to_savings_operation, + :transfer_from_savings_operation, + :cancel_transfer_from_savings_operation, + :custom_binary_operation, + :decline_voting_rights_operation, + :reset_account_operation, + :set_reset_account_operation, + :claim_reward_balance_operation, + :delegate_vesting_shares_operation, + :account_create_with_delegation_operation, + :witness_set_properties_operation, + + # SMT operations + :claim_reward_balance2_operation, + + :smt_setup_operation, + :smt_cap_reveal_operation, + :smt_refund_operation, + :smt_setup_emissions_operation, + :smt_set_setup_parameters_operation, + :smt_set_runtime_parameters_operation, + :smt_create_operation, + + # virtual operations below this point + :fill_convert_request_operation, + :author_reward_operation, + :curation_reward_operation, + :comment_reward_operation, + :liquidity_reward_operation, + :interest_operation, + :fill_vesting_withdraw_operation, + :fill_order_operation, + :shutdown_witness_operation, + :fill_transfer_from_savings_operation, + :hardfork_operation, + :comment_payout_update_operation, + :return_vesting_delegation_operation, + :comment_benefactor_reward_operation, + :producer_reward_operation, + :clear_null_account_balance_operation + ] + + def self.op_id(op) + IDS.find_index op + end + + def inspect + properties = self.class.attributes.map do |prop| + if !!(v = instance_variable_get("@#{prop}")) + "@#{prop}=#{v}" + end + end.compact.join(', ') + + "#<#{self.class.name} [#{properties}]>" + end + + def [](key) + key = key.to_sym + send(key) if self.class.attributes.include?(key) + end + + def []=(key, value) + key = key.to_sym + send("#{key}=", value) if self.class.attributes.include?(key) + end + end +end diff --git a/lib/steem/operation/account_create.rb b/lib/steem/operation/account_create.rb new file mode 100644 index 0000000..d69a8eb --- /dev/null +++ b/lib/steem/operation/account_create.rb @@ -0,0 +1,10 @@ +class Steem::Operation::AccountCreate < Steem::Operation + def_attr fee: :amount + def_attr creator: :string + def_attr new_account_name: :string + def_attr owner: :authority + def_attr active: :authority + def_attr posting: :authority + def_attr memo_key: :public_key + def_attr json_metadata: :string +end diff --git a/lib/steem/operation/account_create_with_delegation.rb b/lib/steem/operation/account_create_with_delegation.rb new file mode 100644 index 0000000..995c54c --- /dev/null +++ b/lib/steem/operation/account_create_with_delegation.rb @@ -0,0 +1,12 @@ +class Steem::Operation::AccountCreateWithDelegation < Steem::Operation + def_attr fee: :amount + def_attr delegation: :amount + def_attr creator: :string + def_attr new_account_name: :string + def_attr owner: :authority + def_attr active: :authority + def_attr posting: :authority + def_attr memo_key: :public_key + def_attr json_metadata: :string + def_attr extensions: :empty_array +end diff --git a/lib/steem/operation/account_update.rb b/lib/steem/operation/account_update.rb new file mode 100644 index 0000000..fe6ab87 --- /dev/null +++ b/lib/steem/operation/account_update.rb @@ -0,0 +1,8 @@ +class Steem::Operation::AccountUpdate < Steem::Operation + def_attr account: :string + def_attr owner: :optional_authority + def_attr active: :optional_authority + def_attr posting: :optional_authority + def_attr memo_key: :public_key + def_attr json_metadata: :string +end diff --git a/lib/steem/operation/account_witness_proxy.rb b/lib/steem/operation/account_witness_proxy.rb new file mode 100644 index 0000000..488238e --- /dev/null +++ b/lib/steem/operation/account_witness_proxy.rb @@ -0,0 +1,4 @@ +class Steem::Operation::AccountWitnessProxy < Steem::Operation + def_attr account: :string + def_attr proxy: :string +end diff --git a/lib/steem/operation/account_witness_vote.rb b/lib/steem/operation/account_witness_vote.rb new file mode 100644 index 0000000..799ef58 --- /dev/null +++ b/lib/steem/operation/account_witness_vote.rb @@ -0,0 +1,5 @@ +class Steem::Operation::AccountWitnessVote < Steem::Operation + def_attr account: :string + def_attr witness: :string + def_attr approve: :boolean +end diff --git a/lib/steem/operation/cancel_transfer_from_savings.rb b/lib/steem/operation/cancel_transfer_from_savings.rb new file mode 100644 index 0000000..ce262de --- /dev/null +++ b/lib/steem/operation/cancel_transfer_from_savings.rb @@ -0,0 +1,4 @@ +class Steem::Operation::CancelTransferFromSavings < Steem::Operation + def_attr from: :string + def_attr request_id: :uint32 +end diff --git a/lib/steem/operation/challenge_authority.rb b/lib/steem/operation/challenge_authority.rb new file mode 100644 index 0000000..53f685e --- /dev/null +++ b/lib/steem/operation/challenge_authority.rb @@ -0,0 +1,5 @@ +class Steem::Operation::ChallengeAuthority < Steem::Operation + def_attr challenger: :string + def_attr challenged: :string + def_attr require_owner: :boolean +end diff --git a/lib/steem/operation/change_recovery_account.rb b/lib/steem/operation/change_recovery_account.rb new file mode 100644 index 0000000..1d9f924 --- /dev/null +++ b/lib/steem/operation/change_recovery_account.rb @@ -0,0 +1,5 @@ +class Steem::Operation::ChangeRecoveryAccount < Steem::Operation + def_attr account_to_recover: :string + def_attr new_recovery_account: :string + def_attr extensions: :empty_array +end diff --git a/lib/steem/operation/claim_account.rb b/lib/steem/operation/claim_account.rb new file mode 100644 index 0000000..f36032b --- /dev/null +++ b/lib/steem/operation/claim_account.rb @@ -0,0 +1,5 @@ +class Steem::Operation::ClaimAccount < Steem::Operation + def_attr creator: :string + def_attr fee: :amount + def_attr extensions: :empty_array +end diff --git a/lib/steem/operation/claim_reward_balance.rb b/lib/steem/operation/claim_reward_balance.rb new file mode 100644 index 0000000..55a1e88 --- /dev/null +++ b/lib/steem/operation/claim_reward_balance.rb @@ -0,0 +1,6 @@ +class Steem::Operation::ClaimRewardBalance < Steem::Operation + def_attr account: :string + def_attr reward_steem: :amount + def_attr reward_sbd: :amount + def_attr reward_vests: :amount +end diff --git a/lib/steem/operation/comment.rb b/lib/steem/operation/comment.rb new file mode 100644 index 0000000..fdb203f --- /dev/null +++ b/lib/steem/operation/comment.rb @@ -0,0 +1,9 @@ +class Steem::Operation::Comment < Steem::Operation + def_attr parent_author: :string + def_attr parent_permlink: :string + def_attr author: :string + def_attr permlink: :string + def_attr title: :string + def_attr body: :string + def_attr json_metadata: :string +end diff --git a/lib/steem/operation/comment_options.rb b/lib/steem/operation/comment_options.rb new file mode 100644 index 0000000..d7a9a87 --- /dev/null +++ b/lib/steem/operation/comment_options.rb @@ -0,0 +1,10 @@ +class Steem::Operation::CommentOptions < Steem::Operation + def_attr author: :string + def_attr permlink: :string + def_attr max_accepted_payout: :amount + def_attr percent_steem_dollars: :uint32 + def_attr allow_replies: :boolean + def_attr allow_votes: :boolean + def_attr allow_curation_rewards: :boolean + def_attr extensions: :comment_options_extensions +end diff --git a/lib/steem/operation/convert.rb b/lib/steem/operation/convert.rb new file mode 100644 index 0000000..80d971a --- /dev/null +++ b/lib/steem/operation/convert.rb @@ -0,0 +1,5 @@ +class Steem::Operation::Convert < Steem::Operation + def_attr owner: :string + def_attr requestid: :uint32 + def_attr amount: :amount +end diff --git a/lib/steem/operation/create_claimed_account.rb b/lib/steem/operation/create_claimed_account.rb new file mode 100644 index 0000000..5f5b956 --- /dev/null +++ b/lib/steem/operation/create_claimed_account.rb @@ -0,0 +1,10 @@ +class Steem::Operation::CreateClaimedAccount < Steem::Operation + def_attr creator: :string + def_attr new_account_name: :string + def_attr owner: :authority + def_attr active: :authority + def_attr posting: :authority + def_attr memo_key: :public_key + def_attr json_metadata: :string + def_attr extensions: :empty_array +end diff --git a/lib/steem/operation/custom.rb b/lib/steem/operation/custom.rb new file mode 100644 index 0000000..ed6ede4 --- /dev/null +++ b/lib/steem/operation/custom.rb @@ -0,0 +1,5 @@ +class Steem::Operation::Custom < Steem::Operation + def_attr required_auths: :required_auths + def_attr id: :uint32 + def_attr data: :raw_bytes +end diff --git a/lib/steem/operation/custom_binary.rb b/lib/steem/operation/custom_binary.rb new file mode 100644 index 0000000..1b19edd --- /dev/null +++ b/lib/steem/operation/custom_binary.rb @@ -0,0 +1,8 @@ +class Steem::Operation::CustomBinary < Steem::Operation + def_attr required_owner_auths: :required_auths + def_attr required_active_auths: :required_auths + def_attr required_posting_auths: :required_auths + def_attr required_auths: :required_auths + def_attr id: :string + def_attr data: :raw_bytes +end diff --git a/lib/steem/operation/custom_json.rb b/lib/steem/operation/custom_json.rb new file mode 100644 index 0000000..c564e7d --- /dev/null +++ b/lib/steem/operation/custom_json.rb @@ -0,0 +1,6 @@ +class Steem::Operation::CustomJson < Steem::Operation + def_attr required_auths: :required_auths + def_attr required_posting_auths: :required_auths + def_attr id: :string + def_attr json: :string +end diff --git a/lib/steem/operation/decline_voting_rights.rb b/lib/steem/operation/decline_voting_rights.rb new file mode 100644 index 0000000..7f5fe38 --- /dev/null +++ b/lib/steem/operation/decline_voting_rights.rb @@ -0,0 +1,4 @@ +class Steem::Operation::DeclineVotingRights < Steem::Operation + def_attr account: :string + def_attr decline: :boolean +end diff --git a/lib/steem/operation/delegate_vesting_shares.rb b/lib/steem/operation/delegate_vesting_shares.rb new file mode 100644 index 0000000..73b9740 --- /dev/null +++ b/lib/steem/operation/delegate_vesting_shares.rb @@ -0,0 +1,5 @@ +class Steem::Operation::DelegateVestingShares < Steem::Operation + def_attr delegator: :string + def_attr delegatee: :string + def_attr vesting_shares: :amount +end diff --git a/lib/steem/operation/delete_comment.rb b/lib/steem/operation/delete_comment.rb new file mode 100644 index 0000000..638bc58 --- /dev/null +++ b/lib/steem/operation/delete_comment.rb @@ -0,0 +1,4 @@ +class Steem::Operation::DeleteComment < Steem::Operation + def_attr author: :string + def_attr permlink: :string +end diff --git a/lib/steem/operation/escrow_approve.rb b/lib/steem/operation/escrow_approve.rb new file mode 100644 index 0000000..9922a9b --- /dev/null +++ b/lib/steem/operation/escrow_approve.rb @@ -0,0 +1,8 @@ +class Steem::Operation::EscrowApprove < Steem::Operation + def_attr from: :string + def_attr to: :string + def_attr agent: :string + def_attr who: :string + def_attr escrow_id: :uint32 + def_attr approve: :boolean +end diff --git a/lib/steem/operation/escrow_dispute.rb b/lib/steem/operation/escrow_dispute.rb new file mode 100644 index 0000000..e4b748f --- /dev/null +++ b/lib/steem/operation/escrow_dispute.rb @@ -0,0 +1,7 @@ +class Steem::Operation::EscrowDispute < Steem::Operation + def_attr from: :string + def_attr to: :string + def_attr agent: :string + def_attr who: :string + def_attr escrow_id: :uint32 +end diff --git a/lib/steem/operation/escrow_release.rb b/lib/steem/operation/escrow_release.rb new file mode 100644 index 0000000..4fb4769 --- /dev/null +++ b/lib/steem/operation/escrow_release.rb @@ -0,0 +1,10 @@ +class Steem::Operation::EscrowRelease < Steem::Operation + def_attr from: :string + def_attr to: :string + def_attr agent: :string + def_attr who: :string + def_attr receiver: :string + def_attr escrow_id: :uint32 + def_attr sbd_amount: :amount + def_attr steem_amount: :amount +end diff --git a/lib/steem/operation/escrow_transfer.rb b/lib/steem/operation/escrow_transfer.rb new file mode 100644 index 0000000..e715550 --- /dev/null +++ b/lib/steem/operation/escrow_transfer.rb @@ -0,0 +1,12 @@ +class Steem::Operation::EscrowTransfer < Steem::Operation + def_attr from: :string + def_attr to: :string + def_attr sbd_amount: :amount + def_attr steem_amount: :amount + def_attr escrow_id: :uint32 + def_attr agent: :string + def_attr fee: :amount + def_attr json_metadata: :string + def_attr ratification_deadline: :point_in_time + def_attr escrow_expiration: :point_in_time +end diff --git a/lib/steem/operation/feed_publish.rb b/lib/steem/operation/feed_publish.rb new file mode 100644 index 0000000..c6680e6 --- /dev/null +++ b/lib/steem/operation/feed_publish.rb @@ -0,0 +1,4 @@ +class Steem::Operation::FeedPublish < Steem::Operation + def_attr publisher: :string + def_attr exchange_rate: :price +end diff --git a/lib/steem/operation/limit_order_cancel.rb b/lib/steem/operation/limit_order_cancel.rb new file mode 100644 index 0000000..7d74eb6 --- /dev/null +++ b/lib/steem/operation/limit_order_cancel.rb @@ -0,0 +1,4 @@ +class Steem::Operation::LimitOrderCancel < Steem::Operation + def_attr owner: :string + def_attr orderid: :uint32 +end diff --git a/lib/steem/operation/limit_order_create.rb b/lib/steem/operation/limit_order_create.rb new file mode 100644 index 0000000..1041b53 --- /dev/null +++ b/lib/steem/operation/limit_order_create.rb @@ -0,0 +1,8 @@ +class Steem::Operation::LimitOrderCreate < Steem::Operation + def_attr owner: :string + def_attr orderid: :uint32 + def_attr amount_to_sell: :amount + def_attr min_to_receive: :amount + def_attr fill_or_kill: :boolean + def_attr expiration: :point_in_time +end diff --git a/lib/steem/operation/limit_order_create2.rb b/lib/steem/operation/limit_order_create2.rb new file mode 100644 index 0000000..7801f18 --- /dev/null +++ b/lib/steem/operation/limit_order_create2.rb @@ -0,0 +1,8 @@ +class Steem::Operation::LimitOrderCreate2 < Steem::Operation + def_attr owner: :string + def_attr orderid: :uint32 + def_attr amount_to_sell: :amount + def_attr fill_or_kill: :boolean + def_attr exchange_rate: :price + def_attr expiration: :point_in_time +end diff --git a/lib/steem/operation/prove_authority.rb b/lib/steem/operation/prove_authority.rb new file mode 100644 index 0000000..1b5e18e --- /dev/null +++ b/lib/steem/operation/prove_authority.rb @@ -0,0 +1,4 @@ +class Steem::Operation::ProveAuthority < Steem::Operation + def_attr challenged: :string + def_attr require_owner: :boolean +end diff --git a/lib/steem/operation/recover_account.rb b/lib/steem/operation/recover_account.rb new file mode 100644 index 0000000..6da4218 --- /dev/null +++ b/lib/steem/operation/recover_account.rb @@ -0,0 +1,6 @@ +class Steem::Operation::RecoverAccount < Steem::Operation + def_attr account_to_recover: :string + def_attr new_owner_authority: :authority + def_attr recent_owner_authority: :authority + def_attr extensions: :empty_array +end diff --git a/lib/steem/operation/report_over_production.rb b/lib/steem/operation/report_over_production.rb new file mode 100644 index 0000000..33e0f1d --- /dev/null +++ b/lib/steem/operation/report_over_production.rb @@ -0,0 +1,5 @@ +class Steem::Operation::ReportOverProduction < Steem::Operation + def_attr reporter: :string + def_attr first_block: :string # FIXME signed_block_header + def_attr second_block: :string # FIXME signed_block_header +end diff --git a/lib/steem/operation/request_account_recovery.rb b/lib/steem/operation/request_account_recovery.rb new file mode 100644 index 0000000..dc82849 --- /dev/null +++ b/lib/steem/operation/request_account_recovery.rb @@ -0,0 +1,6 @@ +class Steem::Operation::RequestAccountRecovery < Steem::Operation + def_attr recovery_account: :string + def_attr account_to_recover: :string + def_attr new_owner_authority: :authority + def_attr extensions: :empty_array +end diff --git a/lib/steem/operation/reset_account.rb b/lib/steem/operation/reset_account.rb new file mode 100644 index 0000000..8e288df --- /dev/null +++ b/lib/steem/operation/reset_account.rb @@ -0,0 +1,5 @@ +class Steem::Operation::ResetAccount < Steem::Operation + def_attr reset_account: :string + def_attr account_to_reset: :string + def_attr new_owner_authority: :authority +end diff --git a/lib/steem/operation/set_reset_account.rb b/lib/steem/operation/set_reset_account.rb new file mode 100644 index 0000000..bebb6e6 --- /dev/null +++ b/lib/steem/operation/set_reset_account.rb @@ -0,0 +1,5 @@ +class Steem::Operation::SetResetAccount < Steem::Operation + def_attr account: :string + def_attr current_reset_account: :string + def_attr reset_account: :string +end diff --git a/lib/steem/operation/set_withdraw_vesting_route.rb b/lib/steem/operation/set_withdraw_vesting_route.rb new file mode 100644 index 0000000..5338b39 --- /dev/null +++ b/lib/steem/operation/set_withdraw_vesting_route.rb @@ -0,0 +1,6 @@ +class Steem::Operation::SetWithdrawVestingRoute < Steem::Operation + def_attr from: :string + def_attr to: :string + def_attr percent: :uint16 + def_attr auto_vest: :boolean +end diff --git a/lib/steem/operation/transfer.rb b/lib/steem/operation/transfer.rb new file mode 100644 index 0000000..55cd58d --- /dev/null +++ b/lib/steem/operation/transfer.rb @@ -0,0 +1,6 @@ +class Steem::Operation::Transfer < Steem::Operation + def_attr from: :string + def_attr to: :string + def_attr amount: :amount + def_attr memo: :string +end diff --git a/lib/steem/operation/transfer_from_savings.rb b/lib/steem/operation/transfer_from_savings.rb new file mode 100644 index 0000000..d49ea5c --- /dev/null +++ b/lib/steem/operation/transfer_from_savings.rb @@ -0,0 +1,7 @@ +class Steem::Operation::TransferFromSavings < Steem::Operation + def_attr from: :string + def_attr request_id: :uint32 + def_attr to: :string + def_attr amount: :amount + def_attr memo: :string +end diff --git a/lib/steem/operation/transfer_to_savings.rb b/lib/steem/operation/transfer_to_savings.rb new file mode 100644 index 0000000..42bb20c --- /dev/null +++ b/lib/steem/operation/transfer_to_savings.rb @@ -0,0 +1,6 @@ +class Steem::Operation::TransferToSavings < Steem::Operation + def_attr from: :string + def_attr to: :string + def_attr amount: :amount + def_attr memo: :string +end diff --git a/lib/steem/operation/transfer_to_vesting.rb b/lib/steem/operation/transfer_to_vesting.rb new file mode 100644 index 0000000..0b84bdd --- /dev/null +++ b/lib/steem/operation/transfer_to_vesting.rb @@ -0,0 +1,5 @@ +class Steem::Operation::TransferToVesting < Steem::Operation + def_attr from: :string + def_attr to: :string + def_attr amount: :amount +end diff --git a/lib/steem/operation/vote.rb b/lib/steem/operation/vote.rb new file mode 100644 index 0000000..837d96a --- /dev/null +++ b/lib/steem/operation/vote.rb @@ -0,0 +1,6 @@ +class Steem::Operation::Vote < Steem::Operation + def_attr voter: :string + def_attr author: :string + def_attr permlink: :string + def_attr weight: :int16 +end diff --git a/lib/steem/operation/withdraw_vesting.rb b/lib/steem/operation/withdraw_vesting.rb new file mode 100644 index 0000000..7b1a0f5 --- /dev/null +++ b/lib/steem/operation/withdraw_vesting.rb @@ -0,0 +1,4 @@ +class Steem::Operation::WithdrawVesting < Steem::Operation + def_attr account: :string + def_attr vesting_shares: :amount +end diff --git a/lib/steem/operation/witness_set_properties.rb b/lib/steem/operation/witness_set_properties.rb new file mode 100644 index 0000000..e92a19b --- /dev/null +++ b/lib/steem/operation/witness_set_properties.rb @@ -0,0 +1,5 @@ +class Steem::Operation::WitnessSetProperties < Steem::Operation + def_attr owner: :string + def_attr props: :witness_properties + def_attr extensions: :empty_array +end diff --git a/lib/steem/operation/witness_update.rb b/lib/steem/operation/witness_update.rb new file mode 100644 index 0000000..f34e7ce --- /dev/null +++ b/lib/steem/operation/witness_update.rb @@ -0,0 +1,7 @@ +class Steem::Operation::WitnessUpdate < Steem::Operation + def_attr owner: :string + def_attr url: :string + def_attr block_signing_key: :public_key + def_attr props: :chain_properties + def_attr fee: :amount +end diff --git a/lib/steem/transaction.rb b/lib/steem/transaction.rb new file mode 100644 index 0000000..56cbd5e --- /dev/null +++ b/lib/steem/transaction.rb @@ -0,0 +1,86 @@ +module Steem + class Transaction + include JSONable + include Utils + + ATTRIBUTES = %i(id ref_block_num ref_block_prefix expiration operations + extensions signatures) + + attr_accessor *ATTRIBUTES + + def initialize(options = {}) + if !!(hex = options.delete(:hex)) + marshal = Marshal.new(hex: hex) + marshal.transaction(trx: self) + end + + options.each do |k, v| + raise Steem::ArgumentError, "Invalid option specified: #{k}" unless ATTRIBUTES.include?(k.to_sym) + + send("#{k}=", v) + end + + self.operations ||= [] + self.extensions ||= [] + self.signatures ||= [] + + self.expiration = case @expiration + when String then Time.parse(@expiration + 'Z') + else; @expiration + end + end + + def inspect + properties = ATTRIBUTES.map do |prop| + if !!(v = instance_variable_get("@#{prop}")) + "@#{prop}=#{v}" + end + end.compact.join(', ') + + "#<#{self.class.name} [#{properties}]>" + end + + def expiration + if @expiration.respond_to? :strftime + @expiration.strftime('%Y-%m-%dT%H:%M:%S') + else + @expiration + end + end + + def expired? + @expiration.nil? || @expiration < Time.now + end + + def [](key) + key = key.to_sym + send(key) if self.class.attributes.include?(key) + end + + def []=(key, value) + key = key.to_sym + send("#{key}=", value) if self.class.attributes.include?(key) + end + + def ==(other_trx) + begin + return false if self[:ref_block_num].to_i != other_trx[:ref_block_num].to_i + return false if self[:ref_block_prefix].to_i != other_trx[:ref_block_prefix].to_i + return false if self[:expiration].to_i != other_trx[:expiration].to_i + return false if self[:operations].size != other_trx[:operations].size + + vals = self[:operations].sort.map do |k, v| + v.values.join.gsub(/[^a-zA-Z0-9]/, '') + end.join + + ovals = other_trx[:operations].sort.map do |k, v| + v.values.join.gsub(/[^a-zA-Z0-9]/, '') + end.join + # binding.pry if vals != ovals + return vals == ovals + rescue + false + end + end + end +end diff --git a/lib/steem/transaction_builder.rb b/lib/steem/transaction_builder.rb index f83cb41..63f74d8 100644 --- a/lib/steem/transaction_builder.rb +++ b/lib/steem/transaction_builder.rb @@ -56,28 +56,10 @@ module Steem else; trx end - trx_options = { - ref_block_num: trx['ref_block_num'], - ref_block_prefix: trx['ref_block_prefix'], - extensions: (trx['extensions']), - operations: trx['operations'], - signatures: (trx['signatures']), - } - - trx_options[:expiration] = case trx['expiration'] - when String then Time.parse(trx['expiration'] + 'Z') - else; trx['expiration'] - end - - options = options.merge(trx_options) + @trx = Transaction.new(trx) end - @ref_block_num = options[:ref_block_num] - @ref_block_prefix = options[:ref_block_prefix] - @operations = options[:operations] || [] - @expiration = options[:expiration] - @extensions = options[:extensions] || [] - @signatures = options[:signatures] || [] + @trx ||= Transaction.new @chain = options[:chain] || :steem @error_pipe = options[:error_pipe] || STDERR @chain_id = options[:chain_id] @@ -93,12 +75,9 @@ module Steem end def inspect - properties = %w( - ref_block_num ref_block_prefix expiration operations extensions - signatures - ).map do |prop| + properties = %w(trx).map do |prop| if !!(v = instance_variable_get("@#{prop}")) - "@#{prop}=#{v}" + "@#{prop}=#{v.inspect}" end end.compact.join(', ') @@ -106,21 +85,12 @@ module Steem end def reset - @ref_block_num = nil - @ref_block_prefix = nil - @expiration = nil - @operations = [] - @extensions = [] - @signatures = [] + @trx = Transaction.new @signed = false self end - def expired? - @expiration.nil? || @expiration < Time.now - end - # 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. @@ -129,7 +99,7 @@ module Steem # # @return {TransactionBuilder} def prepare - if expired? + if @trx.expired? catch :prepare_header do; begin @database_api.get_dynamic_global_properties do |properties| block_number = properties.last_irreversible_block_num @@ -146,9 +116,9 @@ module Steem 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 + @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 @@ -166,9 +136,9 @@ module Steem # Sets operations all at once, then prepares. def operations=(operations) - @operations = operations.map{ |op| normalize_operation(op) } + @trx.operations = operations.map{ |op| normalize_operation(op) } prepare - @operations + @trx.operations end # A quick and flexible way to append a new operation to the transaction. @@ -194,8 +164,8 @@ module Steem # builder.put(vote: vote1).put(vote: vote2) # @return {TransactionBuilder} def put(type, op = nil) - @expiration = nil - @operations << normalize_operation(type, op) + @trx.expiration = nil + @trx.operations << normalize_operation(type, op) prepare self end @@ -218,9 +188,17 @@ module Steem # ]], # :signatures => ["1c45b65740b4b2c17c4bcf6bcc3f8d90ddab827d50532729fc3b8f163f2c465a532b0112ae4bf388ccc97b7c2e0bc570caadda78af48cf3c261037e65eefcd941e"] # } - def transaction - prepare - sign + def transaction(options = {prepare: true, sign: true}) + options[:prepare] = true unless options.has_key? :prepare + options[:sign] = true unless options.has_key? :sign + + prepare if !!options[:prepare] + + if !!options[:sign] + sign + else + @trx + end end # Appends to the `signatures` array of the transaction, built from a @@ -229,39 +207,43 @@ module Steem # @return {Hash | TransactionBuilder} The fully signed transaction if a `wif` is provided or the instance of the {TransactionBuilder} if a `wif` has not yet been provided. 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 - } + return self if @trx.expired? 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| + transaction_hex.tap do |result| hex = if app_base? result.hex else result end + + # TODO Now that we have the hex from steemd, we will make our own + # and compare them for better errors and debugging. + + 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 - hex = @chain_id + hex[0..-4] # Why do we have to chop the last two bytes? + 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 - sigs = [] private_keys.each do |private_key| sig = nil @@ -276,11 +258,10 @@ module Steem break if canonical? sig end - @signatures << hexlify(sig) + @trx.signatures << hexlify(sig) end @signed = true - trx[:signatures] = @signatures end rescue => e if can_retry? e @@ -292,7 +273,25 @@ module Steem end; end end - Hashie::Mash.new trx + @trx + end + + 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 # @return [Array] All public keys that could possibly sign for a given transaction. diff --git a/lib/steem/type/amount.rb b/lib/steem/type/amount.rb index 7aa9e99..69bbf4b 100644 --- a/lib/steem/type/amount.rb +++ b/lib/steem/type/amount.rb @@ -55,6 +55,8 @@ module Steem when 'STEEM' then 3 when 'VESTS' then 6 when 'SBD' then 3 + when 'TESTS' then 3 + when 'TBD' then 3 else; raise TypeError, "Asset #{@asset} unknown." end end diff --git a/steem-ruby.gemspec b/steem-ruby.gemspec index 641193b..85de301 100644 --- a/steem-ruby.gemspec +++ b/steem-ruby.gemspec @@ -34,4 +34,6 @@ Gem::Specification.new do |spec| spec.add_dependency 'hashie', '~> 3.5', '>= 3.5.7' spec.add_dependency 'bitcoin-ruby', '~> 0.0', '>= 0.0.18' spec.add_dependency 'ffi', '~> 1.9', '>= 1.9.23' + spec.add_dependency 'bindata', '~> 2.4', '>= 2.4.4' + spec.add_dependency 'base58', '~> 0.2', '>= 0.2.3' end diff --git a/test/steem/api_test.rb b/test/steem/api_test.rb index 3f1b1e2..7ac2982 100644 --- a/test/steem/api_test.rb +++ b/test/steem/api_test.rb @@ -17,7 +17,7 @@ module Steem get_tags_used_by_author get_transaction_hex get_witness_by_account verify_authority) - METHOD_NAMES_2_ARGS = %i(get_account_bandwidth get_account_reputations + METHOD_NAMES_2_ARGS = %i(get_account_reputations get_active_votes get_content get_content_replies get_escrow get_expiring_vesting_delegations get_ops_in_block get_reblogged_by get_required_signatures get_trending_tags @@ -76,13 +76,13 @@ module Steem end def test_inspect - assert_equal "#<CondenserApi [@chain=steem, @methods=<85 elements>]>", @api.inspect + assert_equal "#<CondenserApi [@chain=steem, @methods=<84 elements>]>", @api.inspect end def test_inspect_testnet vcr_cassette("#{@api.class.api_name}_testnet") do api = Api.new(chain: :test) - assert_equal "#<CondenserApi [@chain=test, @methods=<85 elements>]>", api.inspect + assert_equal "#<CondenserApi [@chain=test, @methods=<84 elements>]>", api.inspect end end diff --git a/test/steem/broadcast_test.rb b/test/steem/broadcast_test.rb index c93f11d..8e6a8e1 100644 --- a/test/steem/broadcast_test.rb +++ b/test/steem/broadcast_test.rb @@ -217,7 +217,7 @@ module Steem } } - vcr_cassette('broadcast_comment') do + vcr_cassette('broadcast_comment_with_metadata') do Broadcast.comment(@broadcast_options.merge(options)) do |result| if result.respond_to? :valid assert result.valid @@ -655,8 +655,8 @@ module Steem sbd_interest_rate: 1000, account_subsidy_budget: 50000, account_subsidy_decay: 330782, - sbd_exchange_rate: '1.000 STEEM', - url: "https://steemit.com", + sbd_exchange_rate: {base: '1.000 SBD', quote: '1.000 STEEM'}, + url: 'https://steemit.com', new_signing_key: 'STM8LoQjQqJHvotqBo7HjnqmUbFW9oJ2theyqonzUd9DdJ7YYHsvD' } } @@ -853,7 +853,7 @@ module Steem from: @account_name, to: 'alice', agent: 'bob', - escrow_id: '1234', + escrow_id: 1234, sbd_amount: '0.000 SBD', steem_amount: '0.000 STEEM', fee: '0.000 STEEM', diff --git a/test/steem/database_api_test.rb b/test/steem/database_api_test.rb index 7aace57..bceeafc 100644 --- a/test/steem/database_api_test.rb +++ b/test/steem/database_api_test.rb @@ -2,6 +2,8 @@ require 'test_helper' module Steem class DatabaseApiTest < Steem::Test + include Utils + def setup @api = Steem::DatabaseApi.new(url: TEST_NODE) @jsonrpc = Jsonrpc.new(url: TEST_NODE) @@ -633,5 +635,39 @@ module Steem end end end + + def test_computed_trx_id + trx = { + ref_block_num: 20, + ref_block_prefix: 2890012981, + expiration: '2018-10-15T19:52:09', + operations: [{type: :account_create_operation, value: { + fee: {amount: '0', precision: 3, nai: '@@000000021'}, + creator: 'porter', + new_account_name: 'a2i-06e13981', + owner: {weight_threshold: 1, account_auths: [['porter', 1]], key_auths: []}, + active: {weight_threshold: 1, account_auths: [['porter', 1]], key_auths: []}, + posting: {weight_threshold: 1, account_auths: [['porter', 1]], key_auths: []}, + memo_key: 'TST77yiRp7pgK52V7BPgq8mEYtyi9XLHKxCH6TDgKA86inFRYgWju', + json_metadata: '' + }}, {type: :transfer_to_vesting_operation, value: { + from: 'porter', + to: 'a2i-06e13981', + amount: {amount: '8204', precision: 3, nai: '@@000000021'} + }}], + extensions: [] + } + + api = Steem::DatabaseApi.new(url: 'https://testnet.steemitdev.com') + + api.get_transaction_hex(trx: trx) do |result| + expected_hex = '1400351942ace9efc45b02090000000000000000035445535453000006706f727465720c6132692d3036653133393831010000000106706f72746572010000010000000106706f72746572010000010000000106706f7274657201000003260545a135c05a8adec1ad4676d046cd1312f16f41b2fb1c01cb2276cf2536e8000306706f727465720c6132692d30366531333938310c2000000000000003544553545300000000' + assert_equal expected_hex, result.hex + + hex = result.hex[0..-4] # drop empty signature array + trx_id = Digest::SHA256.hexdigest(unhexlify(hex))[0..39] + assert_equal trx_id, 'c68ad4eb64b3deb1033e002546481a2c9dfd9e9e' + end + end end end diff --git a/test/steem/jsonrpc_test.rb b/test/steem/jsonrpc_test.rb index 78b3968..7a2bd0f 100644 --- a/test/steem/jsonrpc_test.rb +++ b/test/steem/jsonrpc_test.rb @@ -14,6 +14,225 @@ module Steem vcr_cassette('jsonrpc_get_methods', record: :once) do apis = @jsonrpc.get_api_methods assert_equal Hashie::Mash, apis.class + + expected_apis = { + account_by_key_api: [ + "get_key_references" + ], + block_api: [ + "get_block", + "get_block_header" + ], + condenser_api: [ + "broadcast_block", + "broadcast_transaction", + "broadcast_transaction_synchronous", + "get_account_count", + "get_account_history", + "get_account_references", + "get_account_reputations", + "get_account_votes", + "get_accounts", + "get_active_votes", + "get_active_witnesses", + "get_block", + "get_block_header", + "get_blog", + "get_blog_authors", + "get_blog_entries", + "get_chain_properties", + "get_comment_discussions_by_payout", + "get_config", + "get_content", + "get_content_replies", + "get_conversion_requests", + "get_current_median_history_price", + "get_discussions_by_active", + "get_discussions_by_author_before_date", + "get_discussions_by_blog", + "get_discussions_by_cashout", + "get_discussions_by_children", + "get_discussions_by_comments", + "get_discussions_by_created", + "get_discussions_by_feed", + "get_discussions_by_hot", + "get_discussions_by_promoted", + "get_discussions_by_trending", + "get_discussions_by_votes", + "get_dynamic_global_properties", + "get_escrow", + "get_expiring_vesting_delegations", + "get_feed", + "get_feed_entries", + "get_feed_history", + "get_follow_count", + "get_followers", + "get_following", + "get_hardfork_version", + "get_key_references", + "get_market_history", + "get_market_history_buckets", + "get_next_scheduled_hardfork", + "get_open_orders", + "get_ops_in_block", + "get_order_book", + "get_owner_history", + "get_post_discussions_by_payout", + "get_potential_signatures", + "get_reblogged_by", + "get_recent_trades", + "get_recovery_request", + "get_replies_by_last_update", + "get_required_signatures", + "get_reward_fund", + "get_savings_withdraw_from", + "get_savings_withdraw_to", + "get_state", + "get_tags_used_by_author", + "get_ticker", + "get_trade_history", + "get_transaction", + "get_transaction_hex", + "get_trending_tags", + "get_version", + "get_vesting_delegations", + "get_volume", + "get_withdraw_routes", + "get_witness_by_account", + "get_witness_count", + "get_witness_schedule", + "get_witnesses", + "get_witnesses_by_vote", + "lookup_account_names", + "lookup_accounts", + "lookup_witness_accounts", + "verify_account_authority", + "verify_authority" + ], + database_api: [ + "find_account_recovery_requests", + "find_accounts", + "find_change_recovery_account_requests", + "find_comments", + "find_decline_voting_rights_requests", + "find_escrows", + "find_limit_orders", + "find_owner_histories", + "find_savings_withdrawals", + "find_sbd_conversion_requests", + "find_vesting_delegation_expirations", + "find_vesting_delegations", + "find_votes", + "find_withdraw_vesting_routes", + "find_witnesses", + "get_active_witnesses", + "get_config", + "get_current_price_feed", + "get_dynamic_global_properties", + "get_feed_history", + "get_hardfork_properties", + "get_order_book", + "get_potential_signatures", + "get_required_signatures", + "get_reward_funds", + "get_transaction_hex", + "get_version", + "get_witness_schedule", + "list_account_recovery_requests", + "list_accounts", + "list_change_recovery_account_requests", + "list_comments", + "list_decline_voting_rights_requests", + "list_escrows", + "list_limit_orders", + "list_owner_histories", + "list_savings_withdrawals", + "list_sbd_conversion_requests", + "list_vesting_delegation_expirations", + "list_vesting_delegations", + "list_votes", + "list_withdraw_vesting_routes", + "list_witness_votes", + "list_witnesses", + "verify_account_authority", + "verify_authority", + "verify_signatures" + ], + follow_api: [ + "get_account_reputations", + "get_blog", + "get_blog_authors", + "get_blog_entries", + "get_feed", + "get_feed_entries", + "get_follow_count", + "get_followers", + "get_following", + "get_reblogged_by" + ], + jsonrpc: [ + "get_methods", + "get_signature" + ], + market_history_api: [ + "get_market_history", + "get_market_history_buckets", + "get_order_book", + "get_recent_trades", + "get_ticker", + "get_trade_history", + "get_volume" + ], + network_broadcast_api: [ + "broadcast_block", + "broadcast_transaction" + ], + rc_api: [ + "find_rc_accounts", + "get_resource_params", + "get_resource_pool" + ], + tags_api: [ + "get_active_votes", + "get_comment_discussions_by_payout", + "get_content_replies", + "get_discussion", + "get_discussions_by_active", + "get_discussions_by_author_before_date", + "get_discussions_by_blog", + "get_discussions_by_cashout", + "get_discussions_by_children", + "get_discussions_by_comments", + "get_discussions_by_created", + "get_discussions_by_feed", + "get_discussions_by_hot", + "get_discussions_by_promoted", + "get_discussions_by_trending", + "get_discussions_by_votes", + "get_post_discussions_by_payout", + "get_replies_by_last_update", + "get_tags_used_by_author", + "get_trending_tags" + ] + } + + api_names = expected_apis.keys.map(&:to_s) + unexpected_apis = (api_names + apis.keys).uniq - api_names + missing_apis = (api_names + apis.keys).uniq - apis.keys + assert_equal [], unexpected_apis, "found unexpected apis" + assert_equal [], missing_apis, "missing expected apis" + + assert_equal expected_apis.size, apis.size, "expected #{expected_apis.size} apis, found: #{apis.size}" + + expected_apis.each do |api, methods| + method_names = apis[api].map(&:to_s) + unexpected_methods = (methods + method_names).uniq - methods + missing_methods = (methods + method_names).uniq - method_names + + assert_equal [], unexpected_methods, "found unexpected methods for api: #{api}" + assert_equal [], missing_methods, "missing expected methods for api: #{api}" + assert_equal expected_apis[api].size, apis[api].size, "expected #{expected_apis[api].size} methods for #{api}, found: #{apis[api].size}" + end end end @@ -42,7 +261,7 @@ module Steem if api == :condenser_api if %i( - get_account_bandwidth get_block get_block_header get_escrow + get_block get_block_header get_escrow get_witness_by_account get_recovery_request ).include? method assert_nil signature.ret, "expect #{api}.#{method} to have nil ret" diff --git a/test/steem/marshal_test.rb b/test/steem/marshal_test.rb new file mode 100644 index 0000000..effeb66 --- /dev/null +++ b/test/steem/marshal_test.rb @@ -0,0 +1,377 @@ +require 'test_helper' + +module Steem + class MarshalTest < Steem::Test + include Utils + def setup + @database_api = Steem::DatabaseApi.new + end + + def test_trx_example_1 + # block: 2997469, trx_id: 677040fdb081c1e67928ccc1320b51e57df1b86a + + trx = { + "ref_block_num": 48262, + "ref_block_prefix": 4209344763, + "expiration": "2016-07-07T19:18:15", + "operations": [ + { + "type": "limit_order_create_operation", + "value": { + "owner": "gavvet", + "orderid": 1467919074, + "amount_to_sell": {"amount": "19477", "precision": 3, "nai": "@@000000013"}, + "min_to_receive": {"amount": "67164", "precision": 3, "nai": "@@000000021"}, + "fill_or_kill": false, + "expiration": "1969-12-31T23:59:59" + } + } + ] + } + + hex = @database_api.get_transaction_hex(trx: trx) do |result| + result.hex + end + + marshal = Marshal.new(hex: hex) + + assert_equal 48262, marshal.uint16, 'expect ref_block_num: 48262' + assert_equal 4209344763, marshal.uint32, 'expect ref_block_prefix: 4209344763' + assert_equal Time.parse('2016-07-07T19:18:15Z'), marshal.point_in_time, 'expect expiration: 2016-07-07T19:18:15Z' + + assert_equal 1, marshal.signed_char, 'expect operations: 1' + + assert_equal :limit_order_create_operation, marshal.operation_type, 'expect operation type: limit_order_create_operation' + assert_equal 'gavvet', marshal.string, 'expect owner: gavvet' + assert_equal 1467919074, marshal.uint32, 'expect order_id: 1467919074' + assert_equal Type::Amount.new('19.477 SBD').to_s, marshal.amount.to_s, 'expect amount_to_sell: 19.477 SBD' + assert_equal Type::Amount.new('67.164 STEEM').to_s, marshal.amount.to_s, 'expect min_to_receive: 67.164 STEEM' + assert_equal false, marshal.boolean, 'expect fill_or_kill: false' + assert_equal Time.parse('1969-12-31T23:59:59Z'), marshal.point_in_time, 'expect expiration: 1969-12-31T23:59:59Z' + end + + def test_trx_example_2 + # block: 20000000, trx_id: 8ae2c3e1561462b2c7ed4c9128058e53ba9ca54f + + trx = { + "ref_block_num": 11501, + "ref_block_prefix": 655107659, + "expiration": "2018-02-19T07:26:45", + "operations": [ + { + "type": "claim_reward_balance_operation", + "value": { + "account": "teacherpearline", + "reward_steem": {"amount": "0", "precision": 3, "nai": "@@000000021"}, + "reward_sbd": {"amount": "845", "precision": 3, "nai": "@@000000013"}, + "reward_vests": {"amount": "404731593", "precision": 6, "nai": "@@000000037"} + } + } + ] + } + + hex = @database_api.get_transaction_hex(trx: trx) do |result| + result.hex + end + + marshal = Marshal.new(hex: hex) + + assert_equal 11501, marshal.uint16, 'expect ref_block_num: 11501' + assert_equal 655107659, marshal.uint32, 'expect ref_block_prefix: 655107659' + assert_equal Time.parse('2018-02-19T07:26:45Z'), marshal.point_in_time, 'expect expiration: 2018-02-19T07:26:45Z' + + assert_equal 1, marshal.signed_char, 'expect operations: 1' + + assert_equal :claim_reward_balance_operation, marshal.operation_type, 'expect operation type: claim_reward_balance_operation' + assert_equal 'teacherpearline', marshal.string, 'expect account: teacherpearline' + assert_equal Type::Amount.new('0.000 STEEM').to_s, marshal.amount.to_s, 'expect amount: 0.000 STEEM' + assert_equal Type::Amount.new('0.845 SBD').to_s, marshal.amount.to_s, 'expect amount: 0.845 SBD' + assert_equal Type::Amount.new('404.731593 VESTS').to_s, marshal.amount.to_s, 'expect amount: 0.000 VESTS' + end + + def test_trx_ad_hoc_1 + builder = Steem::TransactionBuilder.new + + builder.put(account_update_operation: { + account: 'social', + memo_key: 'STM8ZSyzjPm48GmUuMSRufkVYkwYbZzbxeMysAVp7KFQwbTf98TcG', + json_metadata: '{}' + }) + + marshal = Marshal.new(hex: builder.transaction_hex) + + assert marshal.uint16, 'expect ref_block_num' + assert marshal.uint32, 'expect ref_block_prefix' + assert marshal.point_in_time, 'expect expiration' + + assert_equal 1, marshal.signed_char, 'expect operations: 1' + assert_equal :account_update_operation, marshal.operation_type, 'expect operation type: account_update_operation' + assert_equal 'social', marshal.string, 'expect account: social' + assert 0, marshal.authority(optional: true) + assert 0, marshal.authority(optional: true) + assert 0, marshal.authority(optional: true) + assert_equal 'STM8ZSyzjPm48GmUuMSRufkVYkwYbZzbxeMysAVp7KFQwbTf98TcG', marshal.public_key, 'expect memo_key: STM8ZSyzjPm48GmUuMSRufkVYkwYbZzbxeMysAVp7KFQwbTf98TcG' + assert_equal '{}', marshal.string, 'expect json_metadata: {}' + end + + def test_trx_ad_hoc_2 + builder = Steem::TransactionBuilder.new + + builder.put(comment_operation: { + "parent_author": "", + "parent_permlink": "dlive", + "author": "zaku", + "permlink": "98bb0d05-9e15-11e8-b733-0242ac110003", + "title": "MODERN COMBAT 5 [BLACK OUT] : DAILY GAMEPLAY #11-August-2018", + "body": "[](https:\/\/dlive.io\/video\/zaku\/98bb0d05-9e15-11e8-b733-0242ac110003)\n\n\n# MC5: MODERN COMBAT 5 Videos Upload:\n\n\n\n* [MODERN COMBAT 5 (BLACKOUT) : Multiplayer Fight + COMBAT PACK Unlock + Level up to 5](https:\/\/steemit.com\/games\/@zaku\/modern-combat-5-blackout-multiplayer-fight-combat-pack-unlock-level-up-to-5)\n\n* [MODERN COMBAT 5 (BLACK OUT) : Multiplayer Fight ( Free-For-All + VIP ) + Reward Redeem + GOLDEN BOSK Purchase](https:\/\/steemit.com\/dlive\/@zaku\/768946c6-98a6-11e8-8af8-0242ac110003)\n\n* [MODERN COMBAT GAMEPLAY : Multiplayer fights](https:\/\/dlive.io\/video\/zaku\/c2fb655a-9a5c-11e8-9e1e-0242ac110003)\n\n* [MODERN COMBAT 5 (BLACK OUT) : Multiplayer Fight ( Free-For-All + VIP ) + Reward Redeem + GOLDEN BOSK Purchase](https:\/\/steemit.com\/dlive\/@zaku\/768946c6-98a6-11e8-8af8-0242ac110003)\n\n* [MODERN COMBAT GAMEPLAY : Multiplayer fights](https:\/\/steemit.com\/dlive\/@zaku\/c2fb655a-9a5c-11e8-9e1e-0242ac110003)\n\n* [MODERN COMBAT 5 [BLACK OUT] : GAMEPLAY #08-Aug-2018](https:\/\/steemit.com\/dlive\/@zaku\/8fb4c940-9bb9-11e8-9a98-0242ac110003)\n\n# MODERN COMBAT VERSUS Videos Upload:\n\n\n\n* [MODERN COMBAT VERSUS : Kult Unlock (POISON BOSS)](https:\/\/steemit.com\/dlive\/@zaku\/acefa790-870a-11e8-adb2-bf4283a63cb9)\n\n* [MODERN COMBAT VERSUS : Ultimate Kult Agent + Multiplayer Fight & Agent Upgrade [480p]](https:\/\/steemit.com\/dlive\/@zaku\/e7f2eaa0-8809-11e8-adb2-bf4283a63cb9)\n\n* [MODERN COMBAT VERSUS : LEAGUE PROMOTION TO GOLD LEAGUE](https:\/\/steemit.com\/dlive\/@zaku\/c76a52e0-882d-11e8-adb2-bf4283a63cb9)\n\n* [MODERN COMBAT VERSUS : League Promotion + Reward Collect + Multiplayer Fight](https:\/\/steemit.com\/dlive\/@zaku\/0da0ccf0-8a57-11e8-b2de-f7be8f055a16)\n\n* [Modern Combat Versus : 20$ Worth Pack buy using xbox gift cards + Kult Ultimate Upgrade + Beast mode](https:\/\/steemit.com\/dlive\/@zaku\/2306b3c0-8e80-11e8-b2de-f7be8f055a16)\n\n* [Modern Combat Versus: Turrent vs Enemy ](https:\/\/steemit.com\/dlive\/@zaku\/321eb460-917e-11e8-b2de-f7be8f055a16)\n\n* [MODERN COMBAT VERSUS : Multiplayer Game play + New Agent + Reward Redeem](https:\/\/steemit.com\/dlive\/@zaku\/1a46d3ae-989c-11e8-9e1e-0242ac110003)\n\n*[MODERN COMBAT VERSUS : Multiplayer Fight's + Loot Open + Reward Redeem](https:\/\/steemit.com\/dlive\/@zaku\/199e4a06-996d-11e8-9e1e-0242ac110003)\n\n* [MODERN COMBAT VERSUS : Daily Gameplay #07-Aug-2018](https:\/\/steemit.com\/dlive\/@zaku\/e5b61976-9a70-11e8-a04f-0242ac110003)\n\n* [MODERN COMBAT VERSUS : GAMEPLAY #8-Aug-2018](https:\/\/steemit.com\/dlive\/@zaku\/b0da8459-9bd1-11e8-a04f-0242ac110003)\n\nhttps:\/\/steemitimages.com\/0x0\/https:\/\/cdn.steemitimages.com\/DQmaKdWkztaw7QsWpgFLiWYma491XfZPBCitb1oo9gYMn7V\/DLive_br.gif\n\n# Some Important Post that might help you:\n\n\n\n* [Introducing Instant Voting Bot - @bdvoter with guaranteed profit for Buyer and Delegator](https:\/\/steemit.com\/bdvoter\/@bdvoter\/introducing-instant-voting-bot-bdvoter-with-granteed-profit-for-buyer-and-delegator)\n\n\n* [STEEMIT \u098f \u09b8\u09a0\u09bf\u0995 \u09ad\u09be\u09ac\u09c7 \u0989\u09aa\u09be\u09b0\u09cd\u099c\u09a8 \u0995\u09b0\u09be\u09b0 \u09a8\u09bf\u09df\u09ae \u0993 \u09ac\u09bf\u09a1 \u09ac\u09cb\u099f\u09c7\u09b0 \u09ac\u09bf\u09b8\u09cd\u09a4\u09be\u09b0\u09bf\u09a4 \u09b8\u09ae\u09cd\u09aa\u09b0\u09cd\u0995\u09c7 \u0986\u09b2\u09cb\u099a\u09a8\u09be](https:\/\/steemit.com\/bidbot\/@zaku\/steemit)\n\n* [Minnowbooster \u098f\u09b0 \u09b8\u0995\u09b2 \u09b8\u09be\u09b0\u09cd\u09ad\u09bf\u09b8 \u09a8\u09bf\u09df\u09c7 \u09ac\u09be\u0982\u09b2\u09be \u0986\u09b2\u09cb\u099a\u09a8\u09be \u0964 ( Part - 1 )](https:\/\/steemit.com\/minnowbooster\/@zaku\/minnowbooster)\n\n* [MinnowBooster \u098f\u09b0 \u09b8\u0995\u09b2 \u09b8\u09be\u09b0\u09cd\u09ad\u09bf\u09b8 \u09a8\u09bf\u09df\u09c7 \u09ac\u09be\u0982\u09b2\u09be \u0986\u09b2\u09cb\u099a\u09a8\u09be \u0964 ( Part - 2 )](https:\/\/steemit.com\/minnowbooster\/@zaku\/minnowbooster-part-2)\n\n* [\u09ae\u09bf\u09a8\u09cb\u09ac\u09c1\u09b8\u09cd\u099f\u09be\u09b0 \u098f\u09b0 \u09b8\u09a0\u09bf\u0995 \u09ac\u09cd\u09af\u09ac\u09b9\u09be\u09b0 \u0993 \u09ac\u09cd\u09b2\u0995\u09b2\u09bf\u09b8\u09cd\u099f \u09a5\u09c7\u0995\u09c7 \u09aa\u09b0\u09bf\u09a4\u09cd\u09b0\u09be\u09a8 \u098f\u09b0 \u099c\u09a8\u09cd\u09af \u0995\u09b0\u09a8\u09c0\u09df \u09aa\u09a6\u0995\u09cd\u09b7\u09c7\u09aa](https:\/\/steemit.com\/minnowbooster\/@zaku\/569dxc)\n\n\n* [\u09af\u09c7\u09ad\u09be\u09ac\u09c7 @steemitbd \u09ac\u09be\u0982\u09b2\u09be\u09a6\u09c7\u09b6\u09c0 \u0987\u0989\u099c\u09be\u09b0\u09a6\u09c7\u09b0 \u09b8\u09b9\u09af\u09cb\u0997\u09bf\u09a4\u09be\u09df \u098f\u0997\u09bf\u09df\u09c7 \u0986\u09b8\u099b\u09c7\u0964](https:\/\/steemit.com\/steemitbd\/@zaku\/steemitbd) \n\n* [Why @steemitbd is the best communication for Bangladeshi Newbie Users. (Find Out Here)](https:\/\/steemit.com\/community\/@zaku\/why-steemitbd-is-the-best-communication-for-bangladeshi-newbie-users-find-out-here)\n\n* [ANNOUNCED: STEEMITBD PROMOTION CONTEST](https:\/\/steemit.com\/promo-steemitbd\/@zaku\/announced-steemitbd-promotion-contest)\n\nhttps:\/\/steemitimages.com\/0x0\/https:\/\/cdn.steemitimages.com\/DQmaKdWkztaw7QsWpgFLiWYma491XfZPBCitb1oo9gYMn7V\/DLive_br.gif\n\n\n\n# Gaming Youtube Channels:\n\n\n\n* [MC5 Official Youtube Channel](https:\/\/www.youtube.com\/channel\/UCF1C_Ptm9Pdtpbo1n0C42LQ)\n\n* [Jwae - Modern Combat 5](https:\/\/www.youtube.com\/user\/fskjwae)\n\n* [Hybrid Gamer](https:\/\/www.youtube.com\/channel\/UCl8cWr8TFvNwiQKVoLwwUyw)\n\n* [Techzamazing](https:\/\/www.youtube.com\/user\/Techzamazing)\n\n* [GameRiot](https:\/\/www.youtube.com\/user\/GameRiotArmy)\n\n<sub>***INFORMATION TAKEN FROM MC5 WEBSITE***<\/sub>\n\n[](https:\/\/discord.gg\/Z3P6bbt)<\/center>\n\n\nMy video is at [DLive](https:\/\/dlive.io\/video\/zaku\/98bb0d05-9e15-11e8-b733-0242ac110003)", + "json_metadata": "{\"tags\":[\"dlive\",\"dlive-video\",\"Gaming\",\"steemgamingcommunity\",\"games\",\"dailygames\",\"mc5\",\"moderncombat5blackout\"],\"app\":\"dlive\/0.1\",\"format\":\"markdown\",\"language\":\"English\",\"thumbnail\":\"https:\/\/images.dlive.io\/4a85e837-9e18-11e8-9a43-0242ac110002\"}" + }) + + marshal = Marshal.new(hex: builder.transaction_hex) + + assert marshal.uint16, 'expect ref_block_num' + assert marshal.uint32, 'expect ref_block_prefix' + assert marshal.point_in_time, 'expect expiration' + + assert_equal 1, marshal.signed_char, 'expect operations: 1' + assert_equal :comment_operation, marshal.operation_type, 'expect operation type: comment_operation' + assert_equal '', marshal.string, 'expect parent_author: <empty>' + assert_equal 'dlive', marshal.string, 'expect parent_permlink: dlive' + assert_equal 'zaku', marshal.string, 'expect author: zaku' + assert_equal '98bb0d05-9e15-11e8-b733-0242ac110003', marshal.string, 'expect permlink: 98bb0d05-9e15-11e8-b733-0242ac110003' + assert_equal 'MODERN COMBAT 5 [BLACK OUT] : DAILY GAMEPLAY #11-August-2018', marshal.string, 'expect title: MODERN COMBAT 5 [BLACK OUT] : DAILY GAMEPLAY #11-August-2018' + assert_equal 5588, marshal.string.size, 'expect body length: 5588' + assert_equal "{\"tags\":[\"dlive\",\"dlive-video\",\"Gaming\",\"steemgamingcommunity\",\"games\",\"dailygames\",\"mc5\",\"moderncombat5blackout\"],\"app\":\"dlive\/0.1\",\"format\":\"markdown\",\"language\":\"English\",\"thumbnail\":\"https:\/\/images.dlive.io\/4a85e837-9e18-11e8-9a43-0242ac110002\"}", marshal.string, 'expect json_metadata: {...}' + end + + def test_trx_ad_hoc_3 + builder = Steem::TransactionBuilder.new + + builder.put(account_update_operation: { + account: 'social', + owner: { + weight_threshold: 1, + account_auths: [], + key_auths: [['STM8ZSyzjPm48GmUuMSRufkVYkwYbZzbxeMysAVp7KFQwbTf98TcG', 1]], + }, + active: { + weight_threshold: 1, + account_auths: [], + key_auths: [['STM8ZSyzjPm48GmUuMSRufkVYkwYbZzbxeMysAVp7KFQwbTf98TcG', 1]], + }, + posting: { + weight_threshold: 1, + account_auths: [], + key_auths: [['STM8ZSyzjPm48GmUuMSRufkVYkwYbZzbxeMysAVp7KFQwbTf98TcG', 1]], + }, + memo_key: 'STM8ZSyzjPm48GmUuMSRufkVYkwYbZzbxeMysAVp7KFQwbTf98TcG', + json_metadata: '{}' + }) + + marshal = Marshal.new(hex: builder.transaction_hex) + + assert marshal.uint16, 'expect ref_block_num' + assert marshal.uint32, 'expect ref_block_prefix' + assert marshal.point_in_time, 'expect expiration' + + assert_equal 1, marshal.signed_char, 'expect operations: 1' + assert_equal :account_update_operation, marshal.operation_type, 'expect operation type: account_update_operation' + assert_equal 'social', marshal.string, 'expect account: social' + + marshal.authority(optional: true).tap do |owner| + assert_equal 1, owner[:weight_threshold], 'expect owner authority weight_threshold: 1' + assert_equal [], owner[:account_auths], 'expect owner authority account_auths: []' + assert_equal [["STM8ZSyzjPm48GmUuMSRufkVYkwYbZzbxeMysAVp7KFQwbTf98TcG", 1]], owner[:key_auths], 'expect owner authority key_auths: [["STM8ZSyzjPm48GmUuMSRufkVYkwYbZzbxeMysAVp7KFQwbTf98TcG", 1]]' + end + + marshal.authority(optional: true).tap do |active| + assert_equal 1, active[:weight_threshold], 'expect active authority weight_threshold: 1' + assert_equal [], active[:account_auths], 'expect active authority account_auths: []' + assert_equal [["STM8ZSyzjPm48GmUuMSRufkVYkwYbZzbxeMysAVp7KFQwbTf98TcG", 1]], active[:key_auths], 'expect active authority key_auths: [["STM8ZSyzjPm48GmUuMSRufkVYkwYbZzbxeMysAVp7KFQwbTf98TcG", 1]]' + end + + marshal.authority(optional: true).tap do |posting| + assert_equal 1, posting[:weight_threshold], 'expect posting authority weight_threshold: 1' + assert_equal [], posting[:account_auths], 'expect posting authority account_auths: []' + assert_equal [["STM8ZSyzjPm48GmUuMSRufkVYkwYbZzbxeMysAVp7KFQwbTf98TcG", 1]], posting[:key_auths], 'expect posting authority key_auths: [["STM8ZSyzjPm48GmUuMSRufkVYkwYbZzbxeMysAVp7KFQwbTf98TcG", 1]]' + end + + assert_equal 'STM8ZSyzjPm48GmUuMSRufkVYkwYbZzbxeMysAVp7KFQwbTf98TcG', marshal.public_key, 'expect memo_key: STM8ZSyzjPm48GmUuMSRufkVYkwYbZzbxeMysAVp7KFQwbTf98TcG' + assert_equal '{}', marshal.string, 'expect json_metadata: {}' + end + + def test_trx_ad_hoc_4 + builder = Steem::TransactionBuilder.new + + builder.put(escrow_transfer: { # FIXME Why do we have to use escrow_transfer and not :escrow_transfer_operation here? + from: 'social', + to: 'alice', + agent: 'bob', + escrow_id: 1234, + sbd_amount: '0.000 SBD', + steem_amount: '0.000 STEEM', + fee: '0.000 STEEM', + ratification_deadline: '2018-10-15T19:52:09', + escrow_expiration: '2018-10-15T19:52:09', + json_meta: '{}' + }) + + marshal = Marshal.new(hex: builder.transaction_hex) + + assert marshal.uint16, 'expect ref_block_num' + assert marshal.uint32, 'expect ref_block_prefix' + assert marshal.point_in_time, 'expect expiration' + + assert_equal 1, marshal.signed_char, 'expect operations: 1' + assert_equal :escrow_transfer_operation, marshal.operation_type, 'expect operation type: escrow_transfer_operation' + assert_equal 'social', marshal.string, 'expect from: social' + assert_equal 'alice', marshal.string, 'expect to: alice' + assert_equal '0.000 SBD', marshal.amount.to_s, 'expect sbd_amount: 0.000 SBD' + assert_equal '0.000 STEEM', marshal.amount.to_s, 'expect steem_amount: 0.000 STEEM' + assert_equal 1234, marshal.uint32, 'expect escrow_id: 1234' + assert_equal 'bob', marshal.string, 'expect agent: bob' + assert_equal '0.000 STEEM', marshal.amount.to_s, 'expect fee: 0.000 STEEM' + assert_equal '{}', marshal.string, 'expect json_meta: {}' + assert_equal Time.parse('2018-10-15 12:52:09 -0700'), marshal.point_in_time, 'expect escrow_expiration: 2018-10-15 12:52:09 -0700' + assert_equal Time.parse('2018-10-15 12:52:09 -0700'), marshal.point_in_time, 'expect escrow_expiration: 2018-10-15 12:52:09 -0700' + end + + def test_trx_ad_hoc_5 + builder = Steem::TransactionBuilder.new + + builder.put(change_recovery_account_operation: { + account_to_recover: 'alice', + new_recovery_account: 'bob', + extensions: [] + }) + + marshal = Marshal.new(hex: builder.transaction_hex) + + assert marshal.uint16, 'expect ref_block_num' + assert marshal.uint32, 'expect ref_block_prefix' + assert marshal.point_in_time, 'expect expiration' + + assert_equal 1, marshal.signed_char, 'expect operations: 1' + assert_equal :change_recovery_account_operation, marshal.operation_type, 'expect operation type: change_recovery_account_operation' + assert_equal 'alice', marshal.string, 'expect account_to_recover: alice' + assert_equal 'bob', marshal.string, 'expect new_recovery_account: bob' + end + + def test_trx_ad_hoc_6 + builder = Steem::TransactionBuilder.new + + builder.put(comment_operation: { + author: 'alice', + permlink: 'permlink', + parent_permlink: 'parent_permlink', + title: 'title', + body: 'body' + }) + + builder.put(comment_options: { # FIXME Why do we have to use comment_options and not :comment_options_operation here? + author: 'alice', + permlink: 'permlink', + max_accepted_payout: '1000000.000 SBD', + percent_steem_dollars: 10000, + allow_votes: true, + allow_curation_rewards: true, + extensions: [] + }) + + builder.put(vote_operation: { + voter: 'alice', + author: 'alice', + permlink: 'permlink', + weight: 10000 + }) + + marshal = Marshal.new(hex: builder.transaction_hex) + + assert marshal.uint16, 'expect ref_block_num' + assert marshal.uint32, 'expect ref_block_prefix' + assert marshal.point_in_time, 'expect expiration' + + assert_equal 3, marshal.signed_char, 'expect operations: 3' + + assert_equal :comment_operation, marshal.operation_type, 'expect operation type: comment_operation' + assert_equal '', marshal.string, 'expect parent_author: <empty>' + assert_equal 'parent_permlink', marshal.string, 'expect parent_permlink: parent_permlink' + assert_equal 'alice', marshal.string, 'expect author: alice' + assert_equal 'permlink', marshal.string, 'expect permlink: permlink' + assert_equal 'title', marshal.string, 'expect title: title' + assert_equal 'body', marshal.string, 'expect body: body' + assert_equal '', marshal.string, 'expect json_metadata: <empty>' + + assert_equal :comment_options_operation, marshal.operation_type, 'expect operation type: comment_options_operation' + assert_equal 'alice', marshal.string, 'expect author: alice' + assert_equal 'permlink', marshal.string, 'expect permlink: permlink' + assert_equal '1000000.000 SBD', marshal.amount.to_s, 'expect max_accepted_payout: 1000000.000 SBD' + assert_equal 10000, marshal.uint16, 'expect percent_steem_dollars: 10000' + assert_equal false, marshal.boolean, 'expect allow_replies: false' + assert_equal false, marshal.boolean, 'expect allow_votes: false' + assert_equal false, marshal.boolean, 'expect allow_curation_rewards: false' + + assert_equal :vote_operation, marshal.operation_type, 'expect operation type: vote_operation' + assert_equal 'alice', marshal.string, 'expect voter: alice' + assert_equal 'alice', marshal.string, 'expect author: alice' + assert_equal 'permlink', marshal.string, 'expect permlink: permlink' + assert_equal 10000, marshal.int16, 'expect weight: 10000' + end + + def test_trx_ad_hoc_9 + # Example transaction: + # + # ref_block_num: 20, + # ref_block_prefix: 2890012981, + # expiration: '2018-10-15T19:52:09', + # operations: [{type: :account_create_operation, value: { + # fee: Type::Amount.new('0.000 TESTS'), + # creator: 'porter', + # new_account_name: 'a2i-06e13981', + # owner: {weight_threshold: 1, account_auths: [['porter', 1]], key_auths: []}, + # active: {weight_threshold: 1, account_auths: [['porter', 1]], key_auths: []}, + # posting: {weight_threshold: 1, account_auths: [['porter', 1]], key_auths: []}, + # memo_key: 'TST77yiRp7pgK52V7BPgq8mEYtyi9XLHKxCH6TDgKA86inFRYgWju', + # json_metadata: '' + # }}, {type: :transfer_to_vesting_operation, value: { + # from: 'porter', + # to: 'a2i-06e13981', + # amount: Type::Amount.new('8.204 TESTS') + # }}] + + marshal = Marshal.new(chain: :test, hex: '1400351942ace9efc45b02090000000000000000035445535453000006706f727465720c6132692d3036653133393831010000000106706f72746572010000010000000106706f72746572010000010000000106706f7274657201000003260545a135c05a8adec1ad4676d046cd1312f16f41b2fb1c01cb2276cf2536e8000306706f727465720c6132692d30366531333938310c2000000000000003544553545300000000') + + assert_equal 20, marshal.uint16, 'expect ref_block_num: 20' + assert_equal 2890012981, marshal.uint32, 'expect ref_block_prefix: 2890012981' + assert_equal Time.parse('2018-10-15 12:52:09 -0700'), marshal.point_in_time, 'expect expiration: 2018-10-15 12:52:09 -0700' + + assert_equal 2, marshal.signed_char, 'expect operations: 2' + + assert_equal :account_create_operation, marshal.operation_type, 'expect operation type: account_create_operation' + assert_equal Type::Amount.new('0.000 TESTS').to_s, marshal.amount.to_s, 'expect amount: 0.000 TESTS' + assert_equal 'porter', marshal.string, 'expect creator: porter' + assert_equal 'a2i-06e13981', marshal.string, 'expect new_account_name: a2i-06e13981' + + marshal.authority.tap do |owner| + assert_equal 1, owner[:weight_threshold], 'expect owner authority weight_threshold: 1' + assert_equal [["porter", 1]], owner[:account_auths], 'expect owner authority account_auths: [["porter", 1]]' + assert_equal [], owner[:key_auths], 'expect owner authority key_auths: []' + end + + marshal.authority.tap do |active| + assert_equal 1, active[:weight_threshold], 'expect active authority weight_threshold: 1' + assert_equal [["porter", 1]], active[:account_auths], 'expect active authority account_auths: [["porter", 1]]' + assert_equal [], active[:key_auths], 'expect active authority key_auths: []' + end + + marshal.authority.tap do |posting| + assert_equal 1, posting[:weight_threshold], 'expect posting authority weight_threshold: 1' + assert_equal [["porter", 1]], posting[:account_auths], 'expect posting authority account_auths: [["porter", 1]]' + assert_equal [], posting[:key_auths], 'expect posting authority key_auths: []' + end + + assert_equal 'TST77yiRp7pgK52V7BPgq8mEYtyi9XLHKxCH6TDgKA86inFRYgWju', marshal.public_key, 'expect memo_key: TST77yiRp7pgK52V7BPgq8mEYtyi9XLHKxCH6TDgKA86inFRYgWju' + assert_equal '', marshal.string, 'expect empty json_metata' + end + end +end diff --git a/test/steem/transaction_builder_test.rb b/test/steem/transaction_builder_test.rb index daa1472..4bc743c 100644 --- a/test/steem/transaction_builder_test.rb +++ b/test/steem/transaction_builder_test.rb @@ -104,7 +104,9 @@ module Steem weight: 10000 }) - assert builder.sign + trx = builder.sign + assert trx + assert trx[:id] end end @@ -169,7 +171,7 @@ module Steem }) end - assert 1, builder.operations.size + assert 1, builder.transaction(sign: false).operations.size end def test_put_array @@ -184,7 +186,7 @@ module Steem }]) end - assert 1, builder.operations.size + assert 1, builder.transaction(sign: false).operations.size end def test_put_symbol @@ -199,7 +201,7 @@ module Steem }) end - assert 1, builder.operations.size + assert 1, builder.transaction(sign: false).operations.size end def test_put_string @@ -214,7 +216,7 @@ module Steem }) end - assert 1, builder.operations.size + assert 1, builder.transaction(sign: false).operations.size end def test_potential_signatures diff --git a/test/steem/witness_api_test.rb b/test/steem/witness_api_test.rb deleted file mode 100644 index d6eecec..0000000 --- a/test/steem/witness_api_test.rb +++ /dev/null @@ -1,52 +0,0 @@ -require 'test_helper' - -module Steem - class WitnessApiTest < Steem::Test - def setup - @api = Steem::WitnessApi.new(url: TEST_NODE) - @jsonrpc = Jsonrpc.new(url: TEST_NODE) - @methods = @jsonrpc.get_api_methods[@api.class.api_name] - end - - def test_api_class_name - assert_equal 'WitnessApi', Steem::WitnessApi::api_class_name - end - - def test_inspect - assert_equal "#<WitnessApi [@chain=steem, @methods=<2 elements>]>", @api.inspect - end - - def test_method_missing - assert_raises NoMethodError do - @api.bogus - end - end - - def test_all_respond_to - @methods.each do |key| - assert @api.respond_to?(key), "expect rpc respond to #{key}" - end - end - - def test_get_account_bandwidth - vcr_cassette('witness_api_get_account_bandwidth', record: :once) do - options = { - account: 'steemit', - type: 'forum' - } - - @api.get_account_bandwidth(options) do |result| - assert_equal Hashie::Mash, result.class - end - end - end - - def test_get_reserve_ratio - vcr_cassette('witness_api_get_reserve_ratio', record: :once) do - @api.get_reserve_ratio do |result| - assert_equal Hashie::Mash, result.class - end - end - end - end -end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index bd963b6..b7e2241 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -7,6 +7,7 @@ SimpleCov.merge_timeout 3600 require 'steem' require 'minitest/autorun' +require 'minitest/line/describe_track' require 'webmock/minitest' require 'vcr' require 'yaml' -- GitLab