cita-sdk-ruby 0.20.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.
data/lib/cita/http.rb ADDED
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ require "json"
3
+ require "faraday"
4
+
5
+ module CITA
6
+ class Http
7
+ attr_accessor :url
8
+
9
+ DEFAULT_JSONRPC = "2.0"
10
+ DEFAULT_PARAMS = [].freeze
11
+ DEFAULT_ID = 83
12
+
13
+ def initialize(url)
14
+ @url = url
15
+ end
16
+
17
+ # wrapper for call rpc method
18
+ #
19
+ # @param method [String] method you want to call
20
+ # @param jsonrpc [String] jsonrpc version
21
+ # @param params [Array] rpc params
22
+ # @param id [Integer] jsonrpc id
23
+ #
24
+ # @return [Faraday::Response]
25
+ def call_rpc(method, jsonrpc: DEFAULT_JSONRPC, params: DEFAULT_PARAMS, id: DEFAULT_ID)
26
+ conn.post("/", rpc_params(method, jsonrpc: jsonrpc, params: params, id: id))
27
+ end
28
+
29
+ # wrapper for rpc params
30
+ #
31
+ # @param method [String] method you want to call
32
+ # @param jsonrpc [String] jsonrpc version
33
+ # @param params [Array] rpc params
34
+ # @param id [Integer] jsonrpc id
35
+ #
36
+ # @return [String] json string
37
+ def rpc_params(method, jsonrpc: DEFAULT_JSONRPC, params: DEFAULT_PARAMS, id: DEFAULT_ID)
38
+ {
39
+ jsonrpc: jsonrpc,
40
+ id: id,
41
+ method: method,
42
+ params: params
43
+ }.to_json
44
+ end
45
+
46
+ # wrapper faraday object with CITA URL and Content-Type
47
+ #
48
+ # @return [Faraday]
49
+ def conn
50
+ Faraday.new(url: url) do |faraday|
51
+ faraday.headers["Content-Type"] = "application/json"
52
+ faraday.request :url_encoded # form-encode POST params
53
+ faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,112 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: blockchain.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ Google::Protobuf::DescriptorPool.generated_pool.build do
7
+ add_message "Proof" do
8
+ optional :content, :bytes, 1
9
+ optional :type, :enum, 2, "ProofType"
10
+ end
11
+ add_message "BlockHeader" do
12
+ optional :prevhash, :bytes, 1
13
+ optional :timestamp, :uint64, 2
14
+ optional :height, :uint64, 3
15
+ optional :state_root, :bytes, 4
16
+ optional :transactions_root, :bytes, 5
17
+ optional :receipts_root, :bytes, 6
18
+ optional :quota_used, :uint64, 7
19
+ optional :quota_limit, :uint64, 8
20
+ optional :proof, :message, 9, "Proof"
21
+ optional :proposer, :bytes, 10
22
+ end
23
+ add_message "Status" do
24
+ optional :hash, :bytes, 1
25
+ optional :height, :uint64, 2
26
+ end
27
+ add_message "AccountGasLimit" do
28
+ optional :common_quota_limit, :uint64, 1
29
+ map :specific_quota_limit, :string, :uint64, 2
30
+ end
31
+ add_message "RichStatus" do
32
+ optional :hash, :bytes, 1
33
+ optional :height, :uint64, 2
34
+ repeated :nodes, :bytes, 3
35
+ optional :interval, :uint64, 4
36
+ optional :version, :uint32, 5
37
+ repeated :validators, :bytes, 6
38
+ end
39
+ add_message "Transaction" do
40
+ optional :to, :string, 1
41
+ optional :nonce, :string, 2
42
+ optional :quota, :uint64, 3
43
+ optional :valid_until_block, :uint64, 4
44
+ optional :data, :bytes, 5
45
+ optional :value, :bytes, 6
46
+ optional :chain_id, :uint32, 7
47
+ optional :version, :uint32, 8
48
+ optional :to_v1, :bytes, 9
49
+ optional :chain_id_v1, :bytes, 10
50
+ end
51
+ add_message "UnverifiedTransaction" do
52
+ optional :transaction, :message, 1, "Transaction"
53
+ optional :signature, :bytes, 2
54
+ optional :crypto, :enum, 3, "Crypto"
55
+ end
56
+ add_message "SignedTransaction" do
57
+ optional :transaction_with_sig, :message, 1, "UnverifiedTransaction"
58
+ optional :tx_hash, :bytes, 2
59
+ optional :signer, :bytes, 3
60
+ end
61
+ add_message "BlockBody" do
62
+ repeated :transactions, :message, 1, "SignedTransaction"
63
+ end
64
+ add_message "Block" do
65
+ optional :version, :uint32, 1
66
+ optional :header, :message, 2, "BlockHeader"
67
+ optional :body, :message, 3, "BlockBody"
68
+ end
69
+ add_message "BlockWithProof" do
70
+ optional :blk, :message, 1, "Block"
71
+ optional :proof, :message, 2, "Proof"
72
+ end
73
+ add_message "BlockTxs" do
74
+ optional :height, :uint64, 1
75
+ optional :body, :message, 3, "BlockBody"
76
+ end
77
+ add_message "BlackList" do
78
+ repeated :black_list, :bytes, 1
79
+ repeated :clear_list, :bytes, 2
80
+ end
81
+ add_message "StateSignal" do
82
+ optional :height, :uint64, 1
83
+ end
84
+ add_enum "ProofType" do
85
+ value :AuthorityRound, 0
86
+ value :Raft, 1
87
+ value :Bft, 2
88
+ end
89
+ add_enum "Crypto" do
90
+ value :SECP, 0
91
+ value :SM2, 1
92
+ end
93
+ end
94
+
95
+ module CITA::Protos
96
+ Proof = Google::Protobuf::DescriptorPool.generated_pool.lookup("Proof").msgclass
97
+ BlockHeader = Google::Protobuf::DescriptorPool.generated_pool.lookup("BlockHeader").msgclass
98
+ Status = Google::Protobuf::DescriptorPool.generated_pool.lookup("Status").msgclass
99
+ AccountGasLimit = Google::Protobuf::DescriptorPool.generated_pool.lookup("AccountGasLimit").msgclass
100
+ RichStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("RichStatus").msgclass
101
+ Transaction = Google::Protobuf::DescriptorPool.generated_pool.lookup("Transaction").msgclass
102
+ UnverifiedTransaction = Google::Protobuf::DescriptorPool.generated_pool.lookup("UnverifiedTransaction").msgclass
103
+ SignedTransaction = Google::Protobuf::DescriptorPool.generated_pool.lookup("SignedTransaction").msgclass
104
+ BlockBody = Google::Protobuf::DescriptorPool.generated_pool.lookup("BlockBody").msgclass
105
+ Block = Google::Protobuf::DescriptorPool.generated_pool.lookup("Block").msgclass
106
+ BlockWithProof = Google::Protobuf::DescriptorPool.generated_pool.lookup("BlockWithProof").msgclass
107
+ BlockTxs = Google::Protobuf::DescriptorPool.generated_pool.lookup("BlockTxs").msgclass
108
+ BlackList = Google::Protobuf::DescriptorPool.generated_pool.lookup("BlackList").msgclass
109
+ StateSignal = Google::Protobuf::DescriptorPool.generated_pool.lookup("StateSignal").msgclass
110
+ ProofType = Google::Protobuf::DescriptorPool.generated_pool.lookup("ProofType").enummodule
111
+ Crypto = Google::Protobuf::DescriptorPool.generated_pool.lookup("Crypto").enummodule
112
+ end
data/lib/cita/rpc.rb ADDED
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+ require "active_support/inflector"
3
+
4
+ module CITA
5
+ class RPC
6
+ attr_reader :url, :http
7
+
8
+ # CITA v0.18 RPC list
9
+ METHOD_NAMES = %w(
10
+ peerCount
11
+ blockNumber
12
+ sendRawTransaction
13
+ getBlockByHash
14
+ getBlockByNumber
15
+ getTransaction
16
+ getTransactionReceipt
17
+ getLogs
18
+ call
19
+ getTransactionCount
20
+ getCode
21
+ getAbi
22
+ getBalance
23
+ newFilter
24
+ newBlockFilter
25
+ uninstallFilter
26
+ getFilterChanges
27
+ getFilterLogs
28
+ getTransactionProof
29
+ getMetaData
30
+ getBlockHeader
31
+ getStateProof
32
+ ).freeze
33
+
34
+ def initialize(url)
35
+ @url = url
36
+ @http = Http.new(@url)
37
+ end
38
+
39
+ # generate rpc methods
40
+ METHOD_NAMES.each do |name|
41
+ define_method name do |*params|
42
+ call_rpc(name, params: params)
43
+ end
44
+
45
+ define_method name.underscore do |*params|
46
+ send(name, *params)
47
+ end
48
+ end
49
+
50
+ # @return [Hash] response body
51
+ def call_rpc(method, jsonrpc: Http::DEFAULT_JSONRPC, params: Http::DEFAULT_PARAMS, id: Http::DEFAULT_ID)
52
+ resp = http.call_rpc(method, params: params, jsonrpc: jsonrpc, id: id)
53
+ JSON.parse(resp.body)
54
+ end
55
+
56
+ # @param transaction [CITA::Transaction]
57
+ # @return [Hash]
58
+ def send_transaction(transaction, private_key)
59
+ content = TransactionSigner.encode(transaction, private_key)
60
+ send_raw_transaction(content)
61
+ end
62
+
63
+ # easy to transfer tokens
64
+ #
65
+ # @param to [String] to address
66
+ # @param private_key [String]
67
+ # @param value [String | Integer] hex string or decimal integer
68
+ # @param quota [Integer] default to 30_000
69
+ #
70
+ # @return [Hash]
71
+ def transfer(to:, private_key:, value:, quota: 30_000)
72
+ valid_until_block = block_number["result"].hex + 88
73
+ meta_data = get_meta_data("latest")["result"]
74
+ version = meta_data["version"]
75
+ chain_id = if version.zero?
76
+ meta_data["chainId"]
77
+ elsif version == 1
78
+ meta_data["chainIdV1"]
79
+ end
80
+ transaction = Transaction.new(nonce: Utils.nonce, valid_until_block: valid_until_block, chain_id: chain_id, to: to, value: value, quota: quota, version: version)
81
+ send_transaction(transaction, private_key)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ require "active_support/core_ext/string"
3
+
4
+ module CITA
5
+ class Transaction
6
+ class VersionError < StandardError
7
+ end
8
+
9
+ attr_accessor :to, :nonce, :quota, :valid_until_block, :data, :value, :chain_id, :version
10
+
11
+ # @param nonce [String] default is SecureRandom.hex; if you provide with nil or empty string, it will be assigned a random string.
12
+ # @param valid_until_block [Integer]
13
+ # @param chain_id [Integer | String] hex string if version == 1
14
+ # @param version [Integer]
15
+ # @param to [String]
16
+ # @param data [String]
17
+ # @param value: [String | Integer] hex string or decimal integer
18
+ # @param quota [Integer]
19
+ #
20
+ # @return [void]
21
+ def initialize(valid_until_block:, chain_id:, nonce: nil, version: 1, to: nil, data: nil, value: "0", quota: 1_000_000) # rubocop:disable Metrics/ParameterLists
22
+ raise VersionError, "transaction version error, expected 0 or 1, got #{version}" unless [0, 1].include?(version)
23
+
24
+ @to = to
25
+ @nonce = nonce.blank? ? SecureRandom.hex : nonce
26
+ @quota = quota
27
+ @valid_until_block = valid_until_block
28
+ @data = data
29
+ @chain_id = if chain_id.is_a?(String)
30
+ chain_id.delete("-")
31
+ else
32
+ chain_id
33
+ end
34
+ @version = version
35
+ @value = if value.is_a?(Integer)
36
+ Utils.to_hex(value)
37
+ else
38
+ value
39
+ end
40
+ end
41
+
42
+ def self.from_hash(hash)
43
+ h = hash.map { |k, v| { k.to_sym => v } }.reduce({}, :merge)
44
+
45
+ new(
46
+ nonce: h[:nonce],
47
+ valid_until_block: h[:valid_until_block],
48
+ chain_id: h[:chain_id],
49
+ to: h[:to],
50
+ data: h[:data],
51
+ version: h[:version] || 1,
52
+ value: h[:value] || "0",
53
+ quota: h[:quota] || 1_000_000
54
+ )
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ciri/utils"
4
+ require "ciri/crypto"
5
+
6
+ module CITA
7
+ class TransactionSigner
8
+ class << self
9
+ # sign transaction
10
+ #
11
+ # @param transaction [CITA::Transaction]
12
+ # @param private_key [String]
13
+ def encode(transaction, private_key)
14
+ tx = CITA::Protos::Transaction.new
15
+
16
+ to = CITA::Utils.remove_hex_prefix(transaction.to)&.downcase
17
+
18
+ tx.nonce = transaction.nonce
19
+ tx.quota = transaction.quota
20
+ tx.data = CITA::Utils.to_bytes(transaction.data)
21
+ tx.value = hex_to_bytes(transaction.value)
22
+ tx.version = transaction.version
23
+ tx.valid_until_block = transaction.valid_until_block
24
+
25
+ if transaction.version.zero?
26
+ tx.to = to unless to.nil?
27
+ tx.chain_id = transaction.chain_id
28
+ elsif transaction.version == 1
29
+ tx.to_v1 = Utils.to_bytes(to) unless to.nil?
30
+ tx.chain_id_v1 = hex_to_bytes(transaction.chain_id)
31
+ end
32
+
33
+ encoded_tx = Protos::Transaction.encode(tx)
34
+
35
+ private_key_bytes = CITA::Utils.to_bytes(private_key)
36
+
37
+ protobuf_hash = Utils.keccak256(encoded_tx)
38
+
39
+ signature = Ciri::Crypto.ecdsa_signature(private_key_bytes, protobuf_hash).signature
40
+
41
+ unverified_tx = Protos::UnverifiedTransaction.new(transaction: tx, signature: signature)
42
+
43
+ encoded_unverified_tx = Protos::UnverifiedTransaction.encode(unverified_tx)
44
+
45
+ CITA::Utils.from_bytes(encoded_unverified_tx)
46
+ end
47
+
48
+ # unsign transaction
49
+ #
50
+ # @param tx_content [String] hex string
51
+ def simple_decode(tx_content)
52
+ content_bytes = CITA::Utils.to_bytes(tx_content)
53
+ unverified_transaction = Protos::UnverifiedTransaction.decode(content_bytes)
54
+
55
+ signature = unverified_transaction["signature"]
56
+
57
+ transaction = unverified_transaction["transaction"]
58
+ msg = Protos::Transaction.encode(transaction)
59
+ msg_hash = CITA::Utils.keccak256(msg)
60
+ pubkey = Ciri::Crypto.ecdsa_recover(msg_hash, signature)
61
+ pubkey_hex = Utils.from_bytes(pubkey[1..-1])
62
+
63
+ from_address = Utils.keccak256(pubkey[1..-1])[-20..-1]
64
+ from_address_hex = Utils.from_bytes(from_address)
65
+
66
+ sender = {
67
+ address: from_address_hex,
68
+ public_key: pubkey_hex
69
+ }
70
+
71
+ {
72
+ unverified_transaction: unverified_transaction.to_h,
73
+ sender: sender
74
+ }
75
+ end
76
+
77
+ # decode and support forks
78
+ #
79
+ # @param tx_content [String] hex string
80
+ # @return [Hash]
81
+ def original_decode(tx_content)
82
+ data = simple_decode(tx_content)
83
+ utx = data[:unverified_transaction]
84
+ tx = utx[:transaction]
85
+ version = tx[:version]
86
+
87
+ if version == 0 # rubocop:disable Style/NumericPredicate
88
+ tx.delete(:to_v1)
89
+ tx.delete(:chain_id_v1)
90
+ elsif version == 1
91
+ tx[:to] = tx.delete(:to_v1)
92
+ tx[:chain_id] = tx.delete(:chain_id_v1)
93
+ else
94
+ raise Transaction::VersionError, "transaction version error, expected 0 or 1, got #{version}"
95
+ end
96
+
97
+ data
98
+ end
99
+
100
+ # decode and parse bytes to hex string
101
+ #
102
+ # @param tx_content [String] hex string
103
+ # @return [Hash]
104
+ def decode(tx_content)
105
+ data = original_decode(tx_content)
106
+ utx = data[:unverified_transaction]
107
+ tx = utx[:transaction]
108
+ version = tx[:version]
109
+
110
+ tx[:value] = Utils.from_bytes(tx[:value])
111
+ tx[:data] = Utils.from_bytes(tx[:data])
112
+ tx[:nonce] = Utils.add_prefix_for_not_blank(tx[:nonce])
113
+ utx[:signature] = Utils.from_bytes(utx[:signature])
114
+
115
+ if version == 0 # rubocop:disable Style/NumericPredicate
116
+ tx.delete(:to_v1)
117
+ tx.delete(:chain_id_v1)
118
+ tx[:to] = Utils.add_prefix_for_not_blank(tx[:to])
119
+ elsif version == 1
120
+ tx[:to] = Utils.from_bytes(tx[:to])
121
+ tx[:chain_id] = Utils.from_bytes(tx[:chain_id])
122
+ end
123
+
124
+ data
125
+ end
126
+
127
+ private
128
+
129
+ # @param value [String] hex string with or without `0x` prefix
130
+ # @param length [Integer] length, default is 64
131
+ # @return [String] byte code string
132
+ def hex_to_bytes(value, length = 64)
133
+ CITA::Utils.to_bytes(CITA::Utils.remove_hex_prefix(value).rjust(length, "0"))
134
+ end
135
+ end
136
+ end
137
+ end
data/lib/cita/utils.rb ADDED
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CITA
4
+ class Utils
5
+ HEX_PREFIX = "0x"
6
+
7
+ class << self
8
+ # add `0x` prefix to hex string
9
+ #
10
+ # @param hex [String]
11
+ def add_hex_prefix(hex)
12
+ return if hex.nil?
13
+ return hex if hex.start_with?(HEX_PREFIX)
14
+
15
+ HEX_PREFIX + hex
16
+ end
17
+
18
+ # add `0x` prefix to not blank hex string
19
+ #
20
+ # @param hex [String]
21
+ def add_prefix_for_not_blank(hex)
22
+ return add_hex_prefix(hex) unless hex.blank?
23
+
24
+ hex
25
+ end
26
+
27
+ # remove `0x` prefix from hex string
28
+ #
29
+ # @param hex [String]
30
+ def remove_hex_prefix(hex)
31
+ return if hex.nil?
32
+ return hex.gsub(HEX_PREFIX, "") if hex.start_with?(HEX_PREFIX)
33
+
34
+ hex
35
+ end
36
+
37
+ # convert decimal value to hex string without `0x` prefix
38
+ #
39
+ # @param decimal [Integer]
40
+ # @return [String]
41
+ def to_hex(decimal)
42
+ add_hex_prefix decimal.to_s(16)
43
+ end
44
+
45
+ # convert hex string to decimal value
46
+ #
47
+ # @param hex [String]
48
+ # @return [Integer]
49
+ def to_decimal(hex)
50
+ hex.hex
51
+ end
52
+
53
+ # to byte code value
54
+ # remove `0x` prefix first
55
+ #
56
+ # @param str [String] normal string
57
+ # @return [String] byte code string
58
+ def to_bytes(str)
59
+ [CITA::Utils.remove_hex_prefix(str)].pack("H*")
60
+ end
61
+
62
+ # byte code to string value, with `0x` prefix
63
+ #
64
+ # @param bytes_str [String] byte code string
65
+ # @return [String] normal string
66
+ def from_bytes(bytes_str)
67
+ hex = bytes_str.unpack1("H*")
68
+ return CITA::Utils.add_hex_prefix(hex) unless hex.blank?
69
+
70
+ hex
71
+ end
72
+
73
+ # keccak 256 hash
74
+ #
75
+ def keccak256(*data)
76
+ Ciri::Utils.keccak(*data)
77
+ end
78
+
79
+ # get nonce
80
+ #
81
+ # @return [String]
82
+ def nonce
83
+ SecureRandom.hex
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CITA
4
+ VERSION = "0.20.0"
5
+ end
data/lib/cita.rb ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cita/version"
4
+
5
+ module CITA
6
+ # Your code goes here...
7
+ require "cita/protos/blockchain_pb"
8
+
9
+ require "web3_eth/contract"
10
+
11
+ require "cita/address"
12
+ require "cita/transaction"
13
+ require "cita/transaction_signer"
14
+ require "cita/utils"
15
+ require "cita/http"
16
+ require "cita/rpc"
17
+ require "cita/client"
18
+ require "cita/contract"
19
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "web3/eth"
4
+
5
+ module Web3
6
+ module Eth
7
+ class Contract
8
+ class ContractInstance
9
+ def function_data(method_name, *args)
10
+ @contract.function_data(method_name.to_s, args)
11
+ end
12
+ end
13
+
14
+ class ContractMethod
15
+ def function_data(args)
16
+ ["0x" + signature_hash + encode_hex(encode_abi(input_types, args)), output_types]
17
+ end
18
+ end
19
+
20
+ def function_data(method_name, args)
21
+ function = functions[method_name]
22
+ raise "No method found in ABI: #{method_name}" unless function
23
+
24
+ function.function_data args
25
+ end
26
+ end
27
+ end
28
+ end