cita-sdk-ruby 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
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