steem-ruby 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/.gitignore +54 -0
 - data/CONTRIBUTING.md +79 -0
 - data/Gemfile +3 -0
 - data/Gemfile.lock +73 -0
 - data/LICENSE +22 -0
 - data/README.md +77 -0
 - data/Rakefile +115 -0
 - data/gource.sh +6 -0
 - data/images/Anthony Martin.png +0 -0
 - data/lib/steem/api.rb +190 -0
 - data/lib/steem/base_error.rb +151 -0
 - data/lib/steem/block_api.rb +45 -0
 - data/lib/steem/broadcast.rb +1056 -0
 - data/lib/steem/chain_config.rb +36 -0
 - data/lib/steem/formatter.rb +14 -0
 - data/lib/steem/jsonrpc.rb +98 -0
 - data/lib/steem/mixins/retriable.rb +56 -0
 - data/lib/steem/rpc/base_client.rb +154 -0
 - data/lib/steem/rpc/thread_safe_client.rb +23 -0
 - data/lib/steem/transaction_builder.rb +266 -0
 - data/lib/steem/type/amount.rb +61 -0
 - data/lib/steem/type/base_type.rb +10 -0
 - data/lib/steem/utils.rb +17 -0
 - data/lib/steem/version.rb +4 -0
 - data/lib/steem.rb +35 -0
 - data/steem-ruby.gemspec +37 -0
 - metadata +390 -0
 
| 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Steem
         
     | 
| 
      
 2 
     | 
    
         
            +
              module ChainConfig
         
     | 
| 
      
 3 
     | 
    
         
            +
                EXPIRE_IN_SECS = 600
         
     | 
| 
      
 4 
     | 
    
         
            +
                EXPIRE_IN_SECS_PROPOSAL = 24 * 60 * 60
         
     | 
| 
      
 5 
     | 
    
         
            +
                
         
     | 
| 
      
 6 
     | 
    
         
            +
                NETWORKS_STEEM_CHAIN_ID = '0000000000000000000000000000000000000000000000000000000000000000'
         
     | 
| 
      
 7 
     | 
    
         
            +
                NETWORKS_STEEM_ADDRESS_PREFIX = 'STM'
         
     | 
| 
      
 8 
     | 
    
         
            +
                NETWORKS_STEEM_CORE_ASSET = ["0", 3, "@@000000021"] # STEEM
         
     | 
| 
      
 9 
     | 
    
         
            +
                NETWORKS_STEEM_DEBT_ASSET = ["0", 3, "@@000000013"] # SBD
         
     | 
| 
      
 10 
     | 
    
         
            +
                NETWORKS_STEEM_VEST_ASSET = ["0", 6, "@@000000037"] # VESTS
         
     | 
| 
      
 11 
     | 
    
         
            +
                NETWORKS_STEEM_DEFAULT_NODE = 'https://api.steemit.com' # √
         
     | 
| 
      
 12 
     | 
    
         
            +
                # NETWORKS_STEEM_DEFAULT_NODE = 'https://api.steemitstage.com' # √
         
     | 
| 
      
 13 
     | 
    
         
            +
                # NETWORKS_STEEM_DEFAULT_NODE = 'https://api.steemitdev.com' # √
         
     | 
| 
      
 14 
     | 
    
         
            +
                # NETWORKS_STEEM_DEFAULT_NODE = 'https://appbasetest.timcliff.com'
         
     | 
| 
      
 15 
     | 
    
         
            +
                # NETWORKS_STEEM_DEFAULT_NODE = 'https://gtg.steem.house:8090'
         
     | 
| 
      
 16 
     | 
    
         
            +
                # NETWORKS_STEEM_DEFAULT_NODE = 'https://api.steem.house' # √?
         
     | 
| 
      
 17 
     | 
    
         
            +
                # NETWORKS_STEEM_DEFAULT_NODE = 'https://seed.bitcoiner.me'
         
     | 
| 
      
 18 
     | 
    
         
            +
                # NETWORKS_STEEM_DEFAULT_NODE = 'https://steemd.minnowsupportproject.org'
         
     | 
| 
      
 19 
     | 
    
         
            +
                # NETWORKS_STEEM_DEFAULT_NODE = 'https://steemd.privex.io'
         
     | 
| 
      
 20 
     | 
    
         
            +
                # NETWORKS_STEEM_DEFAULT_NODE = 'https://rpc.steemliberator.com'
         
     | 
| 
      
 21 
     | 
    
         
            +
                # NETWORKS_STEEM_DEFAULT_NODE = 'https://rpc.curiesteem.com'
         
     | 
| 
      
 22 
     | 
    
         
            +
                # NETWORKS_STEEM_DEFAULT_NODE = 'https://rpc.buildteam.io'
         
     | 
| 
      
 23 
     | 
    
         
            +
                # NETWORKS_STEEM_DEFAULT_NODE = 'https://steemd.pevo.science'
         
     | 
| 
      
 24 
     | 
    
         
            +
                # NETWORKS_STEEM_DEFAULT_NODE = 'https://rpc.steemviz.com'
         
     | 
| 
      
 25 
     | 
    
         
            +
                # NETWORKS_STEEM_DEFAULT_NODE = 'https://steemd.steemgigs.org'
         
     | 
| 
      
 26 
     | 
    
         
            +
                
         
     | 
| 
      
 27 
     | 
    
         
            +
                NETWORKS_TEST_CHAIN_ID = '46d82ab7d8db682eb1959aed0ada039a6d49afa1602491f93dde9cac3e8e6c32'
         
     | 
| 
      
 28 
     | 
    
         
            +
                NETWORKS_TEST_ADDRESS_PREFIX = 'TST'
         
     | 
| 
      
 29 
     | 
    
         
            +
                NETWORKS_TEST_CORE_ASSET = ["0", 3, "@@000000021"] # TESTS
         
     | 
| 
      
 30 
     | 
    
         
            +
                NETWORKS_TEST_DEBT_ASSET = ["0", 3, "@@000000013"] # TBD
         
     | 
| 
      
 31 
     | 
    
         
            +
                NETWORKS_TEST_VEST_ASSET = ["0", 6, "@@000000037"] # VESTS
         
     | 
| 
      
 32 
     | 
    
         
            +
                NETWORKS_TEST_DEFAULT_NODE = 'https://testnet.steemitdev.com'
         
     | 
| 
      
 33 
     | 
    
         
            +
                
         
     | 
| 
      
 34 
     | 
    
         
            +
                NETWORK_CHAIN_IDS = [NETWORKS_STEEM_CHAIN_ID, NETWORKS_TEST_CHAIN_ID]
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,98 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Steem
         
     | 
| 
      
 2 
     | 
    
         
            +
              # {Jsonrpc} allows you to inspect the available methods offered by a node.
         
     | 
| 
      
 3 
     | 
    
         
            +
              # If a node runs a plugin you want, then all of the API methods it can exposes
         
     | 
| 
      
 4 
     | 
    
         
            +
              # will automatically be available.  This API is used internally to determine
         
     | 
| 
      
 5 
     | 
    
         
            +
              # which APIs and methods are available on the node you specify.
         
     | 
| 
      
 6 
     | 
    
         
            +
              #
         
     | 
| 
      
 7 
     | 
    
         
            +
              # In theory, if a new plugin is created and enabled by the node, it will be
         
     | 
| 
      
 8 
     | 
    
         
            +
              # available by this library without needing an update to its code.
         
     | 
| 
      
 9 
     | 
    
         
            +
              class Jsonrpc < Api
         
     | 
| 
      
 10 
     | 
    
         
            +
                API_METHODS = %i(get_signature get_methods)
         
     | 
| 
      
 11 
     | 
    
         
            +
                
         
     | 
| 
      
 12 
     | 
    
         
            +
                def self.api_methods
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @api_methods ||= {}
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
                
         
     | 
| 
      
 16 
     | 
    
         
            +
                # Might help diagnose a cluster that has asymmetric plugin definitions.
         
     | 
| 
      
 17 
     | 
    
         
            +
                def self.reset_api_methods
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @api_methods = nil
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
                
         
     | 
| 
      
 21 
     | 
    
         
            +
                def initialize(options = {})
         
     | 
| 
      
 22 
     | 
    
         
            +
                  self.class.api_name = :jsonrpc
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @methods = API_METHODS
         
     | 
| 
      
 24 
     | 
    
         
            +
                  super
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
                
         
     | 
| 
      
 27 
     | 
    
         
            +
                def get_api_methods(&block)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  api_methods = self.class.api_methods[@rpc_client.uri.to_s]
         
     | 
| 
      
 29 
     | 
    
         
            +
                  
         
     | 
| 
      
 30 
     | 
    
         
            +
                  if api_methods.nil?
         
     | 
| 
      
 31 
     | 
    
         
            +
                    get_methods do |result, error, rpc_id|
         
     | 
| 
      
 32 
     | 
    
         
            +
                      methods = result.map do |method|
         
     | 
| 
      
 33 
     | 
    
         
            +
                        method.split('.').map(&:to_sym)
         
     | 
| 
      
 34 
     | 
    
         
            +
                      end
         
     | 
| 
      
 35 
     | 
    
         
            +
                      
         
     | 
| 
      
 36 
     | 
    
         
            +
                      api_methods = Hashie::Mash.new
         
     | 
| 
      
 37 
     | 
    
         
            +
                      
         
     | 
| 
      
 38 
     | 
    
         
            +
                      methods.each do |api, method|
         
     | 
| 
      
 39 
     | 
    
         
            +
                        api_methods[api] ||= []
         
     | 
| 
      
 40 
     | 
    
         
            +
                        api_methods[api] << method
         
     | 
| 
      
 41 
     | 
    
         
            +
                      end
         
     | 
| 
      
 42 
     | 
    
         
            +
                      
         
     | 
| 
      
 43 
     | 
    
         
            +
                      self.class.api_methods[@rpc_client.uri.to_s] = api_methods
         
     | 
| 
      
 44 
     | 
    
         
            +
                    end
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
                  
         
     | 
| 
      
 47 
     | 
    
         
            +
                  if !!block
         
     | 
| 
      
 48 
     | 
    
         
            +
                    api_methods.each do |api, methods|
         
     | 
| 
      
 49 
     | 
    
         
            +
                      yield api, methods
         
     | 
| 
      
 50 
     | 
    
         
            +
                    end
         
     | 
| 
      
 51 
     | 
    
         
            +
                  else
         
     | 
| 
      
 52 
     | 
    
         
            +
                    return api_methods
         
     | 
| 
      
 53 
     | 
    
         
            +
                  end
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
                
         
     | 
| 
      
 56 
     | 
    
         
            +
                def get_all_signatures(&block)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  request_body = []
         
     | 
| 
      
 58 
     | 
    
         
            +
                  method_names = []
         
     | 
| 
      
 59 
     | 
    
         
            +
                  method_map = {}
         
     | 
| 
      
 60 
     | 
    
         
            +
                  signatures = {}
         
     | 
| 
      
 61 
     | 
    
         
            +
                  offset = 0
         
     | 
| 
      
 62 
     | 
    
         
            +
                  
         
     | 
| 
      
 63 
     | 
    
         
            +
                  get_api_methods do |api, methods|
         
     | 
| 
      
 64 
     | 
    
         
            +
                    request_body += methods.map do |method|
         
     | 
| 
      
 65 
     | 
    
         
            +
                      method_name = "#{api}.#{method}"
         
     | 
| 
      
 66 
     | 
    
         
            +
                      method_names << method_name
         
     | 
| 
      
 67 
     | 
    
         
            +
                      current_rpc_id = @rpc_client.rpc_id
         
     | 
| 
      
 68 
     | 
    
         
            +
                      offset += 1
         
     | 
| 
      
 69 
     | 
    
         
            +
                      method_map[current_rpc_id] = [api, method]
         
     | 
| 
      
 70 
     | 
    
         
            +
                      
         
     | 
| 
      
 71 
     | 
    
         
            +
                      {
         
     | 
| 
      
 72 
     | 
    
         
            +
                        jsonrpc: '2.0',
         
     | 
| 
      
 73 
     | 
    
         
            +
                        id: current_rpc_id,
         
     | 
| 
      
 74 
     | 
    
         
            +
                        method: 'jsonrpc.get_signature',
         
     | 
| 
      
 75 
     | 
    
         
            +
                        params: {method: method_name}
         
     | 
| 
      
 76 
     | 
    
         
            +
                      }
         
     | 
| 
      
 77 
     | 
    
         
            +
                    end
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
                  
         
     | 
| 
      
 80 
     | 
    
         
            +
                  @rpc_client.rpc_post(nil, nil, {request_body: request_body}) do |result, error, id|
         
     | 
| 
      
 81 
     | 
    
         
            +
                    api, method = method_map[id]
         
     | 
| 
      
 82 
     | 
    
         
            +
                    api = api.to_sym
         
     | 
| 
      
 83 
     | 
    
         
            +
                    method = method.to_sym
         
     | 
| 
      
 84 
     | 
    
         
            +
                    
         
     | 
| 
      
 85 
     | 
    
         
            +
                    signatures[api] ||= {}
         
     | 
| 
      
 86 
     | 
    
         
            +
                    signatures[api][method] = result
         
     | 
| 
      
 87 
     | 
    
         
            +
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
                  
         
     | 
| 
      
 89 
     | 
    
         
            +
                  if !!block
         
     | 
| 
      
 90 
     | 
    
         
            +
                    signatures.each do |api, methods|
         
     | 
| 
      
 91 
     | 
    
         
            +
                      yield api, methods
         
     | 
| 
      
 92 
     | 
    
         
            +
                    end
         
     | 
| 
      
 93 
     | 
    
         
            +
                  else
         
     | 
| 
      
 94 
     | 
    
         
            +
                    return signatures
         
     | 
| 
      
 95 
     | 
    
         
            +
                  end
         
     | 
| 
      
 96 
     | 
    
         
            +
                end
         
     | 
| 
      
 97 
     | 
    
         
            +
              end
         
     | 
| 
      
 98 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,56 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Steem
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Retriable
         
     | 
| 
      
 3 
     | 
    
         
            +
                # @private
         
     | 
| 
      
 4 
     | 
    
         
            +
                MAX_RETRY_COUNT = 100
         
     | 
| 
      
 5 
     | 
    
         
            +
                
         
     | 
| 
      
 6 
     | 
    
         
            +
                # @private
         
     | 
| 
      
 7 
     | 
    
         
            +
                MAX_BACKOFF = 30
         
     | 
| 
      
 8 
     | 
    
         
            +
                
         
     | 
| 
      
 9 
     | 
    
         
            +
                MAX_RETRY_ELAPSE = 300
         
     | 
| 
      
 10 
     | 
    
         
            +
                
         
     | 
| 
      
 11 
     | 
    
         
            +
                RETRYABLE_EXCEPTIONS = [
         
     | 
| 
      
 12 
     | 
    
         
            +
                  NonCanonicalSignatureError, IncorrectRequestIdError,
         
     | 
| 
      
 13 
     | 
    
         
            +
                  IncorrectResponseIdError, Errno::EBADF, Errno::ECONNREFUSED,
         
     | 
| 
      
 14 
     | 
    
         
            +
                  JSON::ParserError, IOError, Net::OpenTimeout
         
     | 
| 
      
 15 
     | 
    
         
            +
                ]
         
     | 
| 
      
 16 
     | 
    
         
            +
                
         
     | 
| 
      
 17 
     | 
    
         
            +
                # Expontential backoff.
         
     | 
| 
      
 18 
     | 
    
         
            +
                #
         
     | 
| 
      
 19 
     | 
    
         
            +
                # @private
         
     | 
| 
      
 20 
     | 
    
         
            +
                def backoff
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @backoff ||= 0.1
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @backoff *= 2
         
     | 
| 
      
 23 
     | 
    
         
            +
                  if @backoff > MAX_BACKOFF
         
     | 
| 
      
 24 
     | 
    
         
            +
                    @backoff = 0.1
         
     | 
| 
      
 25 
     | 
    
         
            +
                    
         
     | 
| 
      
 26 
     | 
    
         
            +
                    if Time.now.utc - @first_retry_at > MAX_RETRY_ELAPSE
         
     | 
| 
      
 27 
     | 
    
         
            +
                      @retry_count = nil
         
     | 
| 
      
 28 
     | 
    
         
            +
                      @first_retry_at = nil
         
     | 
| 
      
 29 
     | 
    
         
            +
                    end
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
                  
         
     | 
| 
      
 32 
     | 
    
         
            +
                  sleep @backoff
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
                
         
     | 
| 
      
 35 
     | 
    
         
            +
                def can_retry?(e = nil)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  @retry_count ||= 0
         
     | 
| 
      
 37 
     | 
    
         
            +
                  @first_retry_at ||= Time.now.utc
         
     | 
| 
      
 38 
     | 
    
         
            +
                  
         
     | 
| 
      
 39 
     | 
    
         
            +
                  return false if @retry_count >= MAX_RETRY_COUNT
         
     | 
| 
      
 40 
     | 
    
         
            +
                  
         
     | 
| 
      
 41 
     | 
    
         
            +
                  @retry_count += 1
         
     | 
| 
      
 42 
     | 
    
         
            +
                  
         
     | 
| 
      
 43 
     | 
    
         
            +
                  can_retry = case e
         
     | 
| 
      
 44 
     | 
    
         
            +
                  when *RETRYABLE_EXCEPTIONS
         
     | 
| 
      
 45 
     | 
    
         
            +
                    true
         
     | 
| 
      
 46 
     | 
    
         
            +
                  when RemoteNodeError
         
     | 
| 
      
 47 
     | 
    
         
            +
                    e.inspect.include?('Unable to acquire database lock')
         
     | 
| 
      
 48 
     | 
    
         
            +
                  else; false
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
                  
         
     | 
| 
      
 51 
     | 
    
         
            +
                  backoff if can_retry
         
     | 
| 
      
 52 
     | 
    
         
            +
                  
         
     | 
| 
      
 53 
     | 
    
         
            +
                  can_retry
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
              end
         
     | 
| 
      
 56 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,154 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Steem
         
     | 
| 
      
 2 
     | 
    
         
            +
              module RPC
         
     | 
| 
      
 3 
     | 
    
         
            +
                class BaseClient
         
     | 
| 
      
 4 
     | 
    
         
            +
                  include ChainConfig
         
     | 
| 
      
 5 
     | 
    
         
            +
                  
         
     | 
| 
      
 6 
     | 
    
         
            +
                  attr_accessor :chain, :error_pipe
         
     | 
| 
      
 7 
     | 
    
         
            +
                  
         
     | 
| 
      
 8 
     | 
    
         
            +
                  # @private
         
     | 
| 
      
 9 
     | 
    
         
            +
                  POST_HEADERS = {
         
     | 
| 
      
 10 
     | 
    
         
            +
                    'Content-Type' => 'application/json; charset=utf-8',
         
     | 
| 
      
 11 
     | 
    
         
            +
                    'User-Agent' => Steem::AGENT_ID
         
     | 
| 
      
 12 
     | 
    
         
            +
                  }
         
     | 
| 
      
 13 
     | 
    
         
            +
                  
         
     | 
| 
      
 14 
     | 
    
         
            +
                  def initialize(options = {})
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @chain = options[:chain] || :steem
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @error_pipe = options[:error_pipe] || STDERR
         
     | 
| 
      
 17 
     | 
    
         
            +
                    @api_name = options[:api_name]
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @url = case @chain
         
     | 
| 
      
 19 
     | 
    
         
            +
                    when :steem then options[:url] || NETWORKS_STEEM_DEFAULT_NODE
         
     | 
| 
      
 20 
     | 
    
         
            +
                    when :test then options[:url] || NETWORKS_TEST_DEFAULT_NODE
         
     | 
| 
      
 21 
     | 
    
         
            +
                    else; raise UnsupportedChainError, "Unsupported chain: #{@chain}"
         
     | 
| 
      
 22 
     | 
    
         
            +
                    end
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
                  
         
     | 
| 
      
 25 
     | 
    
         
            +
                  def uri
         
     | 
| 
      
 26 
     | 
    
         
            +
                    @uri ||= URI.parse(@url)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
                  
         
     | 
| 
      
 29 
     | 
    
         
            +
                  def http
         
     | 
| 
      
 30 
     | 
    
         
            +
                    @http ||= Net::HTTP.new(uri.host, uri.port).tap do |http|
         
     | 
| 
      
 31 
     | 
    
         
            +
                      http.use_ssl = true
         
     | 
| 
      
 32 
     | 
    
         
            +
                      http.keep_alive_timeout = 2 # seconds
         
     | 
| 
      
 33 
     | 
    
         
            +
                      
         
     | 
| 
      
 34 
     | 
    
         
            +
                      # WARNING This method opens a serious security hole. Never use this
         
     | 
| 
      
 35 
     | 
    
         
            +
                      # method in production code.
         
     | 
| 
      
 36 
     | 
    
         
            +
                      # http.set_debug_output(STDOUT) if !!ENV['DEBUG']
         
     | 
| 
      
 37 
     | 
    
         
            +
                    end
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
                  
         
     | 
| 
      
 40 
     | 
    
         
            +
                  def http_post
         
     | 
| 
      
 41 
     | 
    
         
            +
                    @http_post ||= Net::HTTP::Post.new(uri.request_uri, POST_HEADERS)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
                  
         
     | 
| 
      
 44 
     | 
    
         
            +
                  def put(api_name = @api_name, api_method = nil, options = {})
         
     | 
| 
      
 45 
     | 
    
         
            +
                    current_rpc_id = rpc_id
         
     | 
| 
      
 46 
     | 
    
         
            +
                    rpc_method_name = "#{api_name}.#{api_method}"
         
     | 
| 
      
 47 
     | 
    
         
            +
                    options ||= {}
         
     | 
| 
      
 48 
     | 
    
         
            +
                    request_body = defined?(options.delete) ? options.delete(:request_body) : []
         
     | 
| 
      
 49 
     | 
    
         
            +
                    request_body ||= []
         
     | 
| 
      
 50 
     | 
    
         
            +
                    
         
     | 
| 
      
 51 
     | 
    
         
            +
                    request_body << {
         
     | 
| 
      
 52 
     | 
    
         
            +
                      jsonrpc: '2.0',
         
     | 
| 
      
 53 
     | 
    
         
            +
                      id: current_rpc_id,
         
     | 
| 
      
 54 
     | 
    
         
            +
                      method: rpc_method_name,
         
     | 
| 
      
 55 
     | 
    
         
            +
                      params: options
         
     | 
| 
      
 56 
     | 
    
         
            +
                    }
         
     | 
| 
      
 57 
     | 
    
         
            +
                    
         
     | 
| 
      
 58 
     | 
    
         
            +
                    request_body
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  def evaluate_id(options = {})
         
     | 
| 
      
 62 
     | 
    
         
            +
                    debug = options[:debug] || ENV['DEBUG'] == 'true'
         
     | 
| 
      
 63 
     | 
    
         
            +
                    request = options[:request]
         
     | 
| 
      
 64 
     | 
    
         
            +
                    response = options[:response]
         
     | 
| 
      
 65 
     | 
    
         
            +
                    api_method = options[:api_method]
         
     | 
| 
      
 66 
     | 
    
         
            +
                    req_id = request[:id].to_i
         
     | 
| 
      
 67 
     | 
    
         
            +
                    res_id = !!response['id'] ? response['id'].to_i : nil
         
     | 
| 
      
 68 
     | 
    
         
            +
                    method = [@api_name, api_method].join('.')
         
     | 
| 
      
 69 
     | 
    
         
            +
                    
         
     | 
| 
      
 70 
     | 
    
         
            +
                    if debug
         
     | 
| 
      
 71 
     | 
    
         
            +
                      req = JSON.pretty_generate(request)
         
     | 
| 
      
 72 
     | 
    
         
            +
                      res = JSON.parse(response) rescue response
         
     | 
| 
      
 73 
     | 
    
         
            +
                      res = JSON.pretty_generate(response) rescue response
         
     | 
| 
      
 74 
     | 
    
         
            +
                      
         
     | 
| 
      
 75 
     | 
    
         
            +
                      error_pipe.puts '=' * 80
         
     | 
| 
      
 76 
     | 
    
         
            +
                      error_pipe.puts "Request:"
         
     | 
| 
      
 77 
     | 
    
         
            +
                      error_pipe.puts req
         
     | 
| 
      
 78 
     | 
    
         
            +
                      error_pipe.puts '=' * 80
         
     | 
| 
      
 79 
     | 
    
         
            +
                      error_pipe.puts "Response:"
         
     | 
| 
      
 80 
     | 
    
         
            +
                      error_pipe.puts res
         
     | 
| 
      
 81 
     | 
    
         
            +
                      error_pipe.puts '=' * 80
         
     | 
| 
      
 82 
     | 
    
         
            +
                      error_pipe.puts Thread.current.backtrace.join("\n")
         
     | 
| 
      
 83 
     | 
    
         
            +
                    end
         
     | 
| 
      
 84 
     | 
    
         
            +
                    
         
     | 
| 
      
 85 
     | 
    
         
            +
                    error = response['error'].to_json if !!response['error']
         
     | 
| 
      
 86 
     | 
    
         
            +
                          
         
     | 
| 
      
 87 
     | 
    
         
            +
                    if req_id != res_id
         
     | 
| 
      
 88 
     | 
    
         
            +
                      raise IncorrectResponseIdError, "#{method}: The json-rpc id did not match.  Request was: #{req_id}, got: #{res_id.inspect}", error.nil? ? nil : error.to_json
         
     | 
| 
      
 89 
     | 
    
         
            +
                    end
         
     | 
| 
      
 90 
     | 
    
         
            +
                  end
         
     | 
| 
      
 91 
     | 
    
         
            +
                  
         
     | 
| 
      
 92 
     | 
    
         
            +
                  def http_request(request)
         
     | 
| 
      
 93 
     | 
    
         
            +
                    http.request(request)
         
     | 
| 
      
 94 
     | 
    
         
            +
                  end
         
     | 
| 
      
 95 
     | 
    
         
            +
                  
         
     | 
| 
      
 96 
     | 
    
         
            +
                  def rpc_post(api_name = @api_name, api_method = nil, options = {}, &block)
         
     | 
| 
      
 97 
     | 
    
         
            +
                    request = http_post
         
     | 
| 
      
 98 
     | 
    
         
            +
                    
         
     | 
| 
      
 99 
     | 
    
         
            +
                    request_body = if !!api_name && !!api_method
         
     | 
| 
      
 100 
     | 
    
         
            +
                      put(api_name, api_method, options)
         
     | 
| 
      
 101 
     | 
    
         
            +
                    elsif !!options && defined?(options.delete)
         
     | 
| 
      
 102 
     | 
    
         
            +
                      options.delete(:request_body)
         
     | 
| 
      
 103 
     | 
    
         
            +
                    end
         
     | 
| 
      
 104 
     | 
    
         
            +
                    
         
     | 
| 
      
 105 
     | 
    
         
            +
                    request.body = if request_body.size == 1
         
     | 
| 
      
 106 
     | 
    
         
            +
                      request_body.first.to_json
         
     | 
| 
      
 107 
     | 
    
         
            +
                    else
         
     | 
| 
      
 108 
     | 
    
         
            +
                      request_body.to_json
         
     | 
| 
      
 109 
     | 
    
         
            +
                    end
         
     | 
| 
      
 110 
     | 
    
         
            +
                    
         
     | 
| 
      
 111 
     | 
    
         
            +
                    response = http_request(request)
         
     | 
| 
      
 112 
     | 
    
         
            +
                    
         
     | 
| 
      
 113 
     | 
    
         
            +
                    case response.code
         
     | 
| 
      
 114 
     | 
    
         
            +
                    when '200'
         
     | 
| 
      
 115 
     | 
    
         
            +
                      response = JSON[response.body]
         
     | 
| 
      
 116 
     | 
    
         
            +
                      response = case response
         
     | 
| 
      
 117 
     | 
    
         
            +
                      when Hash
         
     | 
| 
      
 118 
     | 
    
         
            +
                        Hashie::Mash.new(response).tap do |r|
         
     | 
| 
      
 119 
     | 
    
         
            +
                          evaluate_id(request: request_body.first, response: r, api_method: api_method)
         
     | 
| 
      
 120 
     | 
    
         
            +
                        end
         
     | 
| 
      
 121 
     | 
    
         
            +
                      when Array
         
     | 
| 
      
 122 
     | 
    
         
            +
                        Hashie::Array.new(response).tap do |r|
         
     | 
| 
      
 123 
     | 
    
         
            +
                          request_body.each_with_index do |req, index|
         
     | 
| 
      
 124 
     | 
    
         
            +
                            evaluate_id(request: req, response: r[index], api_method: api_method)
         
     | 
| 
      
 125 
     | 
    
         
            +
                          end
         
     | 
| 
      
 126 
     | 
    
         
            +
                        end
         
     | 
| 
      
 127 
     | 
    
         
            +
                      else; response
         
     | 
| 
      
 128 
     | 
    
         
            +
                      end
         
     | 
| 
      
 129 
     | 
    
         
            +
                      
         
     | 
| 
      
 130 
     | 
    
         
            +
                      if !!block
         
     | 
| 
      
 131 
     | 
    
         
            +
                        case response
         
     | 
| 
      
 132 
     | 
    
         
            +
                        when Hashie::Mash then yield response.result, response.error, response.id
         
     | 
| 
      
 133 
     | 
    
         
            +
                        when Hashie::Array
         
     | 
| 
      
 134 
     | 
    
         
            +
                          response.each do |r|
         
     | 
| 
      
 135 
     | 
    
         
            +
                            r = Hashie::Mash.new(r)
         
     | 
| 
      
 136 
     | 
    
         
            +
                            yield r.result, r.error, r.id
         
     | 
| 
      
 137 
     | 
    
         
            +
                          end
         
     | 
| 
      
 138 
     | 
    
         
            +
                        else; yield response
         
     | 
| 
      
 139 
     | 
    
         
            +
                        end
         
     | 
| 
      
 140 
     | 
    
         
            +
                      else
         
     | 
| 
      
 141 
     | 
    
         
            +
                        return response
         
     | 
| 
      
 142 
     | 
    
         
            +
                      end
         
     | 
| 
      
 143 
     | 
    
         
            +
                    else
         
     | 
| 
      
 144 
     | 
    
         
            +
                      raise UnknownError, "#{api_name}.#{api_method}: #{response.body}"
         
     | 
| 
      
 145 
     | 
    
         
            +
                    end
         
     | 
| 
      
 146 
     | 
    
         
            +
                  end
         
     | 
| 
      
 147 
     | 
    
         
            +
                  
         
     | 
| 
      
 148 
     | 
    
         
            +
                  def rpc_id
         
     | 
| 
      
 149 
     | 
    
         
            +
                    @rpc_id ||= 0
         
     | 
| 
      
 150 
     | 
    
         
            +
                    @rpc_id += 1
         
     | 
| 
      
 151 
     | 
    
         
            +
                  end
         
     | 
| 
      
 152 
     | 
    
         
            +
                end
         
     | 
| 
      
 153 
     | 
    
         
            +
              end
         
     | 
| 
      
 154 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,23 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Steem
         
     | 
| 
      
 2 
     | 
    
         
            +
              module RPC
         
     | 
| 
      
 3 
     | 
    
         
            +
                # {ThreadSafeClient} is the default RPC Client used by `steem-ruby.`  It's
         
     | 
| 
      
 4 
     | 
    
         
            +
                # perfect for simple requests.  But for higher performance, it's better to
         
     | 
| 
      
 5 
     | 
    
         
            +
                # override {BaseClient} and implement something other than {Net::HTTP}.
         
     | 
| 
      
 6 
     | 
    
         
            +
                class ThreadSafeClient < BaseClient
         
     | 
| 
      
 7 
     | 
    
         
            +
                  SEMAPHORE = Mutex.new.freeze
         
     | 
| 
      
 8 
     | 
    
         
            +
                  
         
     | 
| 
      
 9 
     | 
    
         
            +
                  def http_request(request)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    response = SEMAPHORE.synchronize do
         
     | 
| 
      
 11 
     | 
    
         
            +
                      http.request(request)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    end
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
                  
         
     | 
| 
      
 15 
     | 
    
         
            +
                  def rpc_id
         
     | 
| 
      
 16 
     | 
    
         
            +
                    SEMAPHORE.synchronize do
         
     | 
| 
      
 17 
     | 
    
         
            +
                      @rpc_id ||= 0
         
     | 
| 
      
 18 
     | 
    
         
            +
                      @rpc_id += 1
         
     | 
| 
      
 19 
     | 
    
         
            +
                    end
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,266 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Steem
         
     | 
| 
      
 2 
     | 
    
         
            +
              # {TransactionBuilder} can be used to create a transaction that the
         
     | 
| 
      
 3 
     | 
    
         
            +
              # {NetworkBroadcastApi} can broadcast to the rest of the platform.  The main
         
     | 
| 
      
 4 
     | 
    
         
            +
              # feature of this class is the ability to cryptographically sign the
         
     | 
| 
      
 5 
     | 
    
         
            +
              # transaction so that it conforms to the consensus rules that are required by
         
     | 
| 
      
 6 
     | 
    
         
            +
              # the blockchain.
         
     | 
| 
      
 7 
     | 
    
         
            +
              #
         
     | 
| 
      
 8 
     | 
    
         
            +
              #     wif = '5JrvPrQeBBvCRdjv29iDvkwn3EQYZ9jqfAHzrCyUvfbEbRkrYFC'
         
     | 
| 
      
 9 
     | 
    
         
            +
              #     builder = Steem::TransactionBuilder.new(wif: wif)
         
     | 
| 
      
 10 
     | 
    
         
            +
              #     builder.put(vote: {
         
     | 
| 
      
 11 
     | 
    
         
            +
              #       voter: 'alice',
         
     | 
| 
      
 12 
     | 
    
         
            +
              #       author: 'bob',
         
     | 
| 
      
 13 
     | 
    
         
            +
              #       permlink: 'my-burgers',
         
     | 
| 
      
 14 
     | 
    
         
            +
              #       weight: 10000
         
     | 
| 
      
 15 
     | 
    
         
            +
              #     })
         
     | 
| 
      
 16 
     | 
    
         
            +
              #     
         
     | 
| 
      
 17 
     | 
    
         
            +
              #     trx = builder.transaction
         
     | 
| 
      
 18 
     | 
    
         
            +
              #     network_broadcast_api = Steem::NetworkBroadcastApi.new
         
     | 
| 
      
 19 
     | 
    
         
            +
              #     network_broadcast_api.broadcast_transaction_synchronous(trx: trx)
         
     | 
| 
      
 20 
     | 
    
         
            +
              #
         
     | 
| 
      
 21 
     | 
    
         
            +
              class TransactionBuilder
         
     | 
| 
      
 22 
     | 
    
         
            +
                include Retriable
         
     | 
| 
      
 23 
     | 
    
         
            +
                include ChainConfig
         
     | 
| 
      
 24 
     | 
    
         
            +
                include Utils
         
     | 
| 
      
 25 
     | 
    
         
            +
                
         
     | 
| 
      
 26 
     | 
    
         
            +
                attr_accessor :database_api, :block_api, :wif, :expiration, :operations
         
     | 
| 
      
 27 
     | 
    
         
            +
                
         
     | 
| 
      
 28 
     | 
    
         
            +
                def initialize(options = {})
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @database_api = options[:database_api] || Steem::DatabaseApi.new(options)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @block_api = options[:block_api] || Steem::BlockApi.new(options)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  @wif = options[:wif]
         
     | 
| 
      
 32 
     | 
    
         
            +
                  @ref_block_num = options[:ref_block_num]
         
     | 
| 
      
 33 
     | 
    
         
            +
                  @ref_block_prefix = options[:ref_block_prefix]
         
     | 
| 
      
 34 
     | 
    
         
            +
                  @expiration = nil
         
     | 
| 
      
 35 
     | 
    
         
            +
                  @operations = options[:operations] || []
         
     | 
| 
      
 36 
     | 
    
         
            +
                  @extensions = []
         
     | 
| 
      
 37 
     | 
    
         
            +
                  @signatures = []
         
     | 
| 
      
 38 
     | 
    
         
            +
                  @chain = options[:chain] || :steem
         
     | 
| 
      
 39 
     | 
    
         
            +
                  @error_pipe = options[:error_pipe] || STDERR
         
     | 
| 
      
 40 
     | 
    
         
            +
                  @chain_id = case @chain
         
     | 
| 
      
 41 
     | 
    
         
            +
                  when :steem then NETWORKS_STEEM_CHAIN_ID
         
     | 
| 
      
 42 
     | 
    
         
            +
                  when :test then NETWORKS_TEST_CHAIN_ID
         
     | 
| 
      
 43 
     | 
    
         
            +
                  else; raise UnsupportedChainError, "Unsupported chain: #{@chain}"
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
                
         
     | 
| 
      
 47 
     | 
    
         
            +
                def inspect
         
     | 
| 
      
 48 
     | 
    
         
            +
                  properties = %w(
         
     | 
| 
      
 49 
     | 
    
         
            +
                    ref_block_num ref_block_prefix expiration operations
         
     | 
| 
      
 50 
     | 
    
         
            +
                    extensions signatures
         
     | 
| 
      
 51 
     | 
    
         
            +
                  ).map do |prop|
         
     | 
| 
      
 52 
     | 
    
         
            +
                    if !!(v = instance_variable_get("@#{prop}"))
         
     | 
| 
      
 53 
     | 
    
         
            +
                      "@#{prop}=#{v}" 
         
     | 
| 
      
 54 
     | 
    
         
            +
                    end
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end.compact.join(', ')
         
     | 
| 
      
 56 
     | 
    
         
            +
                  
         
     | 
| 
      
 57 
     | 
    
         
            +
                  "#<#{self.class.name} [#{properties}]>"
         
     | 
| 
      
 58 
     | 
    
         
            +
                end
         
     | 
| 
      
 59 
     | 
    
         
            +
                
         
     | 
| 
      
 60 
     | 
    
         
            +
                def reset
         
     | 
| 
      
 61 
     | 
    
         
            +
                  @ref_block_num = nil
         
     | 
| 
      
 62 
     | 
    
         
            +
                  @ref_block_prefix = nil
         
     | 
| 
      
 63 
     | 
    
         
            +
                  @expiration = nil
         
     | 
| 
      
 64 
     | 
    
         
            +
                  @operations = []
         
     | 
| 
      
 65 
     | 
    
         
            +
                  @extensions = []
         
     | 
| 
      
 66 
     | 
    
         
            +
                  @signatures = []
         
     | 
| 
      
 67 
     | 
    
         
            +
                  
         
     | 
| 
      
 68 
     | 
    
         
            +
                  self
         
     | 
| 
      
 69 
     | 
    
         
            +
                end
         
     | 
| 
      
 70 
     | 
    
         
            +
                
         
     | 
| 
      
 71 
     | 
    
         
            +
                def expired?
         
     | 
| 
      
 72 
     | 
    
         
            +
                  @expiration.nil? || @expiration < Time.now
         
     | 
| 
      
 73 
     | 
    
         
            +
                end
         
     | 
| 
      
 74 
     | 
    
         
            +
                
         
     | 
| 
      
 75 
     | 
    
         
            +
                # If the transaction can be prepared, this method will do so and set the
         
     | 
| 
      
 76 
     | 
    
         
            +
                # expiration.  Once the expiration is set, it will not re-prepare.  If you
         
     | 
| 
      
 77 
     | 
    
         
            +
                # call {#put}, the expiration is set {::Nil} so that it can be re-prepared.
         
     | 
| 
      
 78 
     | 
    
         
            +
                #
         
     | 
| 
      
 79 
     | 
    
         
            +
                # Usually, this method is called automatically by {#put} and/or {#transaction}.
         
     | 
| 
      
 80 
     | 
    
         
            +
                #
         
     | 
| 
      
 81 
     | 
    
         
            +
                # @return {TransactionBuilder}
         
     | 
| 
      
 82 
     | 
    
         
            +
                def prepare
         
     | 
| 
      
 83 
     | 
    
         
            +
                  if expired?
         
     | 
| 
      
 84 
     | 
    
         
            +
                    catch :prepare_header do; begin
         
     | 
| 
      
 85 
     | 
    
         
            +
                      @database_api.get_dynamic_global_properties do |properties|
         
     | 
| 
      
 86 
     | 
    
         
            +
                        block_number = properties.last_irreversible_block_num
         
     | 
| 
      
 87 
     | 
    
         
            +
                      
         
     | 
| 
      
 88 
     | 
    
         
            +
                        @block_api.get_block_header(block_num: block_number) do |result|
         
     | 
| 
      
 89 
     | 
    
         
            +
                          header = result.header
         
     | 
| 
      
 90 
     | 
    
         
            +
                          
         
     | 
| 
      
 91 
     | 
    
         
            +
                          @ref_block_num = (block_number - 1) & 0xFFFF
         
     | 
| 
      
 92 
     | 
    
         
            +
                          @ref_block_prefix = unhexlify(header.previous[8..-1]).unpack('V*')[0]
         
     | 
| 
      
 93 
     | 
    
         
            +
                          @expiration = (Time.parse(properties.time + 'Z') + EXPIRE_IN_SECS).utc
         
     | 
| 
      
 94 
     | 
    
         
            +
                        end
         
     | 
| 
      
 95 
     | 
    
         
            +
                      end
         
     | 
| 
      
 96 
     | 
    
         
            +
                    rescue => e
         
     | 
| 
      
 97 
     | 
    
         
            +
                      if can_retry? e
         
     | 
| 
      
 98 
     | 
    
         
            +
                        @error_pipe.puts "#{e} ... retrying."
         
     | 
| 
      
 99 
     | 
    
         
            +
                        throw :prepare_header
         
     | 
| 
      
 100 
     | 
    
         
            +
                      else
         
     | 
| 
      
 101 
     | 
    
         
            +
                        raise e
         
     | 
| 
      
 102 
     | 
    
         
            +
                      end
         
     | 
| 
      
 103 
     | 
    
         
            +
                    end; end
         
     | 
| 
      
 104 
     | 
    
         
            +
                  end
         
     | 
| 
      
 105 
     | 
    
         
            +
                  
         
     | 
| 
      
 106 
     | 
    
         
            +
                  self
         
     | 
| 
      
 107 
     | 
    
         
            +
                end
         
     | 
| 
      
 108 
     | 
    
         
            +
                
         
     | 
| 
      
 109 
     | 
    
         
            +
                # Sets operations all at once, then prepares.
         
     | 
| 
      
 110 
     | 
    
         
            +
                def operations=(operations)
         
     | 
| 
      
 111 
     | 
    
         
            +
                  @operations = operations
         
     | 
| 
      
 112 
     | 
    
         
            +
                  prepare
         
     | 
| 
      
 113 
     | 
    
         
            +
                  @operations
         
     | 
| 
      
 114 
     | 
    
         
            +
                end
         
     | 
| 
      
 115 
     | 
    
         
            +
                
         
     | 
| 
      
 116 
     | 
    
         
            +
                # A quick and flexible way to append a new operation to the transaction.
         
     | 
| 
      
 117 
     | 
    
         
            +
                # This method uses ducktyping to figure out how to form the operation.
         
     | 
| 
      
 118 
     | 
    
         
            +
                #
         
     | 
| 
      
 119 
     | 
    
         
            +
                # There are three main ways you can call this method.  These assume that
         
     | 
| 
      
 120 
     | 
    
         
            +
                # `op_type` is a {::Symbol} (or {::String}) representing the type of operation and `op` is the
         
     | 
| 
      
 121 
     | 
    
         
            +
                # operation {::Hash}.
         
     | 
| 
      
 122 
     | 
    
         
            +
                #
         
     | 
| 
      
 123 
     | 
    
         
            +
                #     put(op_type, op)
         
     | 
| 
      
 124 
     | 
    
         
            +
                #
         
     | 
| 
      
 125 
     | 
    
         
            +
                # ... or ...
         
     | 
| 
      
 126 
     | 
    
         
            +
                #
         
     | 
| 
      
 127 
     | 
    
         
            +
                #     put(op_type => op)
         
     | 
| 
      
 128 
     | 
    
         
            +
                #
         
     | 
| 
      
 129 
     | 
    
         
            +
                # ... or ...
         
     | 
| 
      
 130 
     | 
    
         
            +
                #
         
     | 
| 
      
 131 
     | 
    
         
            +
                #     put([op_type, op])
         
     | 
| 
      
 132 
     | 
    
         
            +
                #
         
     | 
| 
      
 133 
     | 
    
         
            +
                # You can also chain multiple operations:
         
     | 
| 
      
 134 
     | 
    
         
            +
                #
         
     | 
| 
      
 135 
     | 
    
         
            +
                #     builder = Steem::TransactionBuilder.new
         
     | 
| 
      
 136 
     | 
    
         
            +
                #     builder.put(vote: vote1).put(vote: vote2)
         
     | 
| 
      
 137 
     | 
    
         
            +
                # @return {TransactionBuilder}
         
     | 
| 
      
 138 
     | 
    
         
            +
                def put(type, op = nil)
         
     | 
| 
      
 139 
     | 
    
         
            +
                  @expiration = nil
         
     | 
| 
      
 140 
     | 
    
         
            +
                  
         
     | 
| 
      
 141 
     | 
    
         
            +
                  case type
         
     | 
| 
      
 142 
     | 
    
         
            +
                  when Symbol then @operations << [type, op]
         
     | 
| 
      
 143 
     | 
    
         
            +
                  when String then @operations << [type.to_sym, op]
         
     | 
| 
      
 144 
     | 
    
         
            +
                  when Hash then @operations << [type.keys.first.to_sym, type.values.first]
         
     | 
| 
      
 145 
     | 
    
         
            +
                  when Array then @operations << type
         
     | 
| 
      
 146 
     | 
    
         
            +
                  else
         
     | 
| 
      
 147 
     | 
    
         
            +
                    # don't know what to do with it, skipped
         
     | 
| 
      
 148 
     | 
    
         
            +
                  end
         
     | 
| 
      
 149 
     | 
    
         
            +
                  
         
     | 
| 
      
 150 
     | 
    
         
            +
                  prepare
         
     | 
| 
      
 151 
     | 
    
         
            +
                  
         
     | 
| 
      
 152 
     | 
    
         
            +
                  self
         
     | 
| 
      
 153 
     | 
    
         
            +
                end
         
     | 
| 
      
 154 
     | 
    
         
            +
                
         
     | 
| 
      
 155 
     | 
    
         
            +
                # If all of the required values are set, this returns a fully formed
         
     | 
| 
      
 156 
     | 
    
         
            +
                # transaction that is ready to broadcast.
         
     | 
| 
      
 157 
     | 
    
         
            +
                # 
         
     | 
| 
      
 158 
     | 
    
         
            +
                # @return
         
     | 
| 
      
 159 
     | 
    
         
            +
                #     {
         
     | 
| 
      
 160 
     | 
    
         
            +
                #            :ref_block_num => 18912,
         
     | 
| 
      
 161 
     | 
    
         
            +
                #         :ref_block_prefix => 575781536,
         
     | 
| 
      
 162 
     | 
    
         
            +
                #               :expiration => "2018-04-26T15:26:12",
         
     | 
| 
      
 163 
     | 
    
         
            +
                #               :extensions => [],
         
     | 
| 
      
 164 
     | 
    
         
            +
                #               :operations => [[:vote, {
         
     | 
| 
      
 165 
     | 
    
         
            +
                #                    :voter => "alice",
         
     | 
| 
      
 166 
     | 
    
         
            +
                #                   :author => "bob",
         
     | 
| 
      
 167 
     | 
    
         
            +
                #                 :permlink => "my-burgers",
         
     | 
| 
      
 168 
     | 
    
         
            +
                #                   :weight => 10000
         
     | 
| 
      
 169 
     | 
    
         
            +
                #                 }
         
     | 
| 
      
 170 
     | 
    
         
            +
                #             ]],
         
     | 
| 
      
 171 
     | 
    
         
            +
                #               :signatures => ["1c45b65740b4b2c17c4bcf6bcc3f8d90ddab827d50532729fc3b8f163f2c465a532b0112ae4bf388ccc97b7c2e0bc570caadda78af48cf3c261037e65eefcd941e"]
         
     | 
| 
      
 172 
     | 
    
         
            +
                #     }
         
     | 
| 
      
 173 
     | 
    
         
            +
                def transaction
         
     | 
| 
      
 174 
     | 
    
         
            +
                  prepare
         
     | 
| 
      
 175 
     | 
    
         
            +
                  sign
         
     | 
| 
      
 176 
     | 
    
         
            +
                end
         
     | 
| 
      
 177 
     | 
    
         
            +
                
         
     | 
| 
      
 178 
     | 
    
         
            +
                # Appends to the `signatures` array of the transaction, built from a
         
     | 
| 
      
 179 
     | 
    
         
            +
                # serialized digest.
         
     | 
| 
      
 180 
     | 
    
         
            +
                #
         
     | 
| 
      
 181 
     | 
    
         
            +
                # @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.
         
     | 
| 
      
 182 
     | 
    
         
            +
                def sign
         
     | 
| 
      
 183 
     | 
    
         
            +
                  return self unless !!@wif
         
     | 
| 
      
 184 
     | 
    
         
            +
                  return self if expired?
         
     | 
| 
      
 185 
     | 
    
         
            +
                  
         
     | 
| 
      
 186 
     | 
    
         
            +
                  trx = {
         
     | 
| 
      
 187 
     | 
    
         
            +
                    ref_block_num: @ref_block_num,
         
     | 
| 
      
 188 
     | 
    
         
            +
                    ref_block_prefix: @ref_block_prefix, 
         
     | 
| 
      
 189 
     | 
    
         
            +
                    expiration: @expiration.strftime('%Y-%m-%dT%H:%M:%S'),
         
     | 
| 
      
 190 
     | 
    
         
            +
                    operations: @operations,
         
     | 
| 
      
 191 
     | 
    
         
            +
                    extensions: @extensions,
         
     | 
| 
      
 192 
     | 
    
         
            +
                    signatures: @signatures
         
     | 
| 
      
 193 
     | 
    
         
            +
                  }
         
     | 
| 
      
 194 
     | 
    
         
            +
                  
         
     | 
| 
      
 195 
     | 
    
         
            +
                  catch :serialize do; begin
         
     | 
| 
      
 196 
     | 
    
         
            +
                    @database_api.get_transaction_hex(trx: trx) do |result|
         
     | 
| 
      
 197 
     | 
    
         
            +
                      hex = @chain_id + result.hex[0..-4] # Why do we have to chop the last two bytes?
         
     | 
| 
      
 198 
     | 
    
         
            +
                      digest = unhexlify(hex)
         
     | 
| 
      
 199 
     | 
    
         
            +
                      digest_hex = Digest::SHA256.digest(digest)
         
     | 
| 
      
 200 
     | 
    
         
            +
                      private_key = Bitcoin::Key.from_base58 @wif
         
     | 
| 
      
 201 
     | 
    
         
            +
                      public_key_hex = private_key.pub
         
     | 
| 
      
 202 
     | 
    
         
            +
                      ec = Bitcoin::OpenSSL_EC
         
     | 
| 
      
 203 
     | 
    
         
            +
                      count = 0
         
     | 
| 
      
 204 
     | 
    
         
            +
                      sig = nil
         
     | 
| 
      
 205 
     | 
    
         
            +
                      
         
     | 
| 
      
 206 
     | 
    
         
            +
                      loop do
         
     | 
| 
      
 207 
     | 
    
         
            +
                        count += 1
         
     | 
| 
      
 208 
     | 
    
         
            +
                        @error_pipe.puts "#{count} attempts to find canonical signature" if count % 40 == 0
         
     | 
| 
      
 209 
     | 
    
         
            +
                        sig = ec.sign_compact(digest_hex, private_key.priv, public_key_hex, false)
         
     | 
| 
      
 210 
     | 
    
         
            +
                        
         
     | 
| 
      
 211 
     | 
    
         
            +
                        next if public_key_hex != ec.recover_compact(digest_hex, sig)
         
     | 
| 
      
 212 
     | 
    
         
            +
                        break if canonical? sig
         
     | 
| 
      
 213 
     | 
    
         
            +
                      end
         
     | 
| 
      
 214 
     | 
    
         
            +
                      
         
     | 
| 
      
 215 
     | 
    
         
            +
                      trx[:signatures] = @signatures = [hexlify(sig)]
         
     | 
| 
      
 216 
     | 
    
         
            +
                    end
         
     | 
| 
      
 217 
     | 
    
         
            +
                  rescue => e
         
     | 
| 
      
 218 
     | 
    
         
            +
                    if can_retry? e
         
     | 
| 
      
 219 
     | 
    
         
            +
                      @error_pipe.puts "#{e} ... retrying."
         
     | 
| 
      
 220 
     | 
    
         
            +
                      throw :serialize
         
     | 
| 
      
 221 
     | 
    
         
            +
                    else
         
     | 
| 
      
 222 
     | 
    
         
            +
                      raise e
         
     | 
| 
      
 223 
     | 
    
         
            +
                    end
         
     | 
| 
      
 224 
     | 
    
         
            +
                  end; end
         
     | 
| 
      
 225 
     | 
    
         
            +
                  
         
     | 
| 
      
 226 
     | 
    
         
            +
                  trx
         
     | 
| 
      
 227 
     | 
    
         
            +
                end
         
     | 
| 
      
 228 
     | 
    
         
            +
                
         
     | 
| 
      
 229 
     | 
    
         
            +
                # @return [Array] All public keys that could possibly sign for a given transaction.
         
     | 
| 
      
 230 
     | 
    
         
            +
                def potential_signatures
         
     | 
| 
      
 231 
     | 
    
         
            +
                  @database_api.get_potential_signatures(trx: transaction) do |result|
         
     | 
| 
      
 232 
     | 
    
         
            +
                    result[:keys]
         
     | 
| 
      
 233 
     | 
    
         
            +
                  end
         
     | 
| 
      
 234 
     | 
    
         
            +
                end
         
     | 
| 
      
 235 
     | 
    
         
            +
                
         
     | 
| 
      
 236 
     | 
    
         
            +
                # This API will take a partially signed transaction and a set of public keys
         
     | 
| 
      
 237 
     | 
    
         
            +
                # that the owner has the ability to sign for and return the minimal subset
         
     | 
| 
      
 238 
     | 
    
         
            +
                # of public keys that should add signatures to the transaction.
         
     | 
| 
      
 239 
     | 
    
         
            +
                #
         
     | 
| 
      
 240 
     | 
    
         
            +
                # @return [Array] The minimal subset of public keys that should add signatures to the transaction.
         
     | 
| 
      
 241 
     | 
    
         
            +
                def required_signatures
         
     | 
| 
      
 242 
     | 
    
         
            +
                  @database_api.get_required_signatures(trx: transaction) do |result|
         
     | 
| 
      
 243 
     | 
    
         
            +
                    result[:keys]
         
     | 
| 
      
 244 
     | 
    
         
            +
                  end
         
     | 
| 
      
 245 
     | 
    
         
            +
                end
         
     | 
| 
      
 246 
     | 
    
         
            +
                
         
     | 
| 
      
 247 
     | 
    
         
            +
                # @return [Boolean] True if the transaction has all of the required signatures.
         
     | 
| 
      
 248 
     | 
    
         
            +
                def valid?
         
     | 
| 
      
 249 
     | 
    
         
            +
                  @database_api.verify_authority(trx: transaction) do |result|
         
     | 
| 
      
 250 
     | 
    
         
            +
                    result.valid
         
     | 
| 
      
 251 
     | 
    
         
            +
                  end
         
     | 
| 
      
 252 
     | 
    
         
            +
                end
         
     | 
| 
      
 253 
     | 
    
         
            +
              private
         
     | 
| 
      
 254 
     | 
    
         
            +
                # See: https://github.com/steemit/steem/issues/1944
         
     | 
| 
      
 255 
     | 
    
         
            +
                def canonical?(sig)
         
     | 
| 
      
 256 
     | 
    
         
            +
                  sig = sig.unpack('C*')
         
     | 
| 
      
 257 
     | 
    
         
            +
                  
         
     | 
| 
      
 258 
     | 
    
         
            +
                  !(
         
     | 
| 
      
 259 
     | 
    
         
            +
                    ((sig[0] & 0x80 ) != 0) || ( sig[0] == 0 ) ||
         
     | 
| 
      
 260 
     | 
    
         
            +
                    ((sig[1] & 0x80 ) != 0) ||
         
     | 
| 
      
 261 
     | 
    
         
            +
                    ((sig[32] & 0x80 ) != 0) || ( sig[32] == 0 ) ||
         
     | 
| 
      
 262 
     | 
    
         
            +
                    ((sig[33] & 0x80 ) != 0)
         
     | 
| 
      
 263 
     | 
    
         
            +
                  )
         
     | 
| 
      
 264 
     | 
    
         
            +
                end
         
     | 
| 
      
 265 
     | 
    
         
            +
              end
         
     | 
| 
      
 266 
     | 
    
         
            +
            end
         
     |