Module: Bdb

Defined in:
lib/bigchaindb.rb

Constant Summary collapse

ADDRESS_REGEXP =
/\A[1-9A-Za-z]{43,45}\z/

Class Method Summary collapse

Class Method Details

.balance_asset(ipdb, public_key, asset_id) ⇒ Object



132
133
134
135
136
137
138
139
140
141
# File 'lib/bigchaindb.rb', line 132

def self.balance_asset(ipdb, public_key, asset_id)
  unspent = Bdb.unspent_outputs(ipdb, public_key)
  balance = 0
  unspent.each do |u|
    txn = JSON.parse(Bdb.get_transaction_by_id(ipdb, u["transaction_id"]).body)
    next unless ((txn["operation"] == "CREATE") && (txn["id"] == asset_id)) || (txn["asset"]["id"] == asset_id)
    balance += txn["outputs"][u["output_index"]]["amount"].to_i
  end
  return balance
end

.create_asset(ipdb, public_key, private_key, asset_data, amount = 1, metadata = {"x"=> "y"}) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/bigchaindb.rb', line 143

def self.create_asset(ipdb, public_key, private_key, asset_data, amount = 1,  = {"x"=> "y"})
  output = Bdb.generate_output(public_key, amount)
  create = Bdb.create_txn(public_key, output, asset_data, )
  signed_create = Bdb.sign(create, private_key)
  resp = post_transaction(ipdb, signed_create)
  if resp.code == 202
    txn = JSON.parse(resp.body)
    puts "Transaction #{txn["id"]} posted. Now checking status..."
    sleep(5)
    status_resp = get_transaction_status(ipdb, txn["id"])
    if (status_resp.code == 200) && JSON.parse(status_resp.body)["status"] == "valid"
      return {"create" => create, "txn" => txn}
    else
      puts "Trying again: #{status_resp.code} #{status_resp.body}"
      sleep(5)
      status_resp = get_transaction_status(ipdb, txn["id"])
      if (status_resp.code == 200) && JSON.parse(status_resp.body)["status"] == "valid"
        return {"create" => create, "txn" => txn}
      else
        puts "Tried twice but failed. #{status_resp.code} #{status_resp.body}"
        return nil
      end
    end
  else
    puts "Error in create_asset: #{resp.code} #{resp.body}"
    return nil
  end
end

.create_txn(owner_before, output, asset_data, metadata) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
# File 'lib/bigchaindb.rb', line 17

def self.create_txn(owner_before, output, asset_data, )
  # owner_before is the issuer of the asset
  args = [
    "--asset-data", asset_data.to_json,
    "--metadata", .to_json,
    owner_before,
    output.to_json
  ]
  command = "bdb create #{args.shelljoin}"
  JSON.parse(`#{command}`)
end

.generate_keysObject



7
8
9
10
# File 'lib/bigchaindb.rb', line 7

def self.generate_keys
  JSON.parse(`bdb generate_keys`)
  # {"public"=> "x", "private"=> "y"} #Base58 256-bit numbers
end

.generate_output(owner_after, amount = 1) ⇒ Object



12
13
14
15
# File 'lib/bigchaindb.rb', line 12

def self.generate_output(owner_after, amount = 1)
  # owner_after = who can consume this output? Can be a single pubkey or a m-of-n ThresholdSha256 condition
  JSON.parse(`bdb generate_output --amount #{[amount,owner_after].shelljoin}`)
end

.get_asset(txn) ⇒ Object



40
41
42
43
# File 'lib/bigchaindb.rb', line 40

def self.get_asset(txn)
  args = [txn.to_json]
  JSON.parse(`bdb get_asset #{args.shelljoin}`)
end

.get_assets(ipdb, query) ⇒ Object



246
247
248
# File 'lib/bigchaindb.rb', line 246

def self.get_assets(ipdb, query)
  HTTParty.get(ipdb["url"] + "/assets?search=#{query}")
end

.get_outputs_by_pubkey(ipdb, pubkey, spent = :both) ⇒ Object



237
238
239
240
# File 'lib/bigchaindb.rb', line 237

def self.get_outputs_by_pubkey(ipdb, pubkey, spent = :both)
  return HTTParty.get(ipdb["url"] + "/outputs?public_key=#{pubkey}") if spent == :both
  return HTTParty.get(ipdb["url"] + "/outputs?public_key=#{pubkey}&spent=#{spent}") # true or false
end

.get_transaction_by_id(ipdb, txn_id) ⇒ Object



229
230
231
# File 'lib/bigchaindb.rb', line 229

def self.get_transaction_by_id(ipdb, txn_id)
  HTTParty.get(ipdb["url"] + "/transactions/#{txn_id}")
end

.get_transaction_status(ipdb, txn_id) ⇒ Object



242
243
244
# File 'lib/bigchaindb.rb', line 242

def self.get_transaction_status(ipdb, txn_id)
  HTTParty.get(ipdb["url"] + "/statuses?transaction_id=#{txn_id}")
end

.get_transactions_by_asset(ipdb, asset_id) ⇒ Object



233
234
235
# File 'lib/bigchaindb.rb', line 233

def self.get_transactions_by_asset(ipdb, asset_id)
  HTTParty.get(ipdb["url"] + "/transactions?asset_id=#{asset_id}")
end

.post_transaction(ipdb, txn) ⇒ Object



225
226
227
# File 'lib/bigchaindb.rb', line 225

def self.post_transaction(ipdb, txn)
  HTTParty.post(ipdb["url"] + "/transactions/", {:body => txn.to_json, :headers => {"Content-Type" => "application/json", "app_id" => ipdb["app_id"], "app_key" => ipdb["app_key"]}})
end

.sign(txn, privkey) ⇒ Object



45
46
47
48
# File 'lib/bigchaindb.rb', line 45

def self.sign(txn,privkey)
  args = [txn.to_json, privkey]
  JSON.parse(`bdb sign #{args.shelljoin}`)
end

.spend(txn, output_ids = []) ⇒ Object



50
51
52
53
54
55
56
57
58
# File 'lib/bigchaindb.rb', line 50

def self.spend(txn, output_ids = [])
  # Convert outputs in txn to signable/spendable inputs.
  if output_ids.any?
    args = [txn.to_json, output_ids.to_json]
  else
    args = [txn.to_json]
  end
  JSON.parse(`bdb spend #{args.shelljoin}`)
end

.testObject



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/bigchaindb.rb', line 172

def self.test
  # Generate key-pairs
  alice = Bdb.generate_keys
  puts "alice = #{alice.to_json}\n\n"
  bob = Bdb.generate_keys
  puts "bob = #{bob.to_json}\n\n"

  # Define the metadata for an asset, null should work too
  asset_data = { "name" => "mycoin", "symbol" => "MC" }
  puts "asset = #{asset_data.to_json}\n\n"

  # To create a CREATE txn, we need output and asset. metadata is optional
  # Let's generate the output first: we'll need receiver and amount
  output = Bdb.generate_output(alice["public"],100)
  puts "output = #{output.to_json}\n\n"

   = {"msg" => "creating mycoin asset"}
  create = Bdb.create_txn(alice["public"], output, asset_data, )
  puts "create = #{create.to_json}\n\n"

  signed_create = Bdb.sign(create, alice["private"])
  puts "signed_create = #{signed_create.to_json}\n\n"

  # This signed CREATE txn can be sent to BigChainDB over HTTP api
  ipdb = { "url" => ENV["IPDB_URL"], "app_id" => ENV["IPDB_APP_ID"], "app_key" => ENV["IPDB_APP_KEY"]}
  resp = post_transaction(ipdb, signed_create)
  puts "resp = #{resp.code} #{resp.body}"

  # Now let's create a TRANSFER txn and transfer 10 coins to bob
  # we'll need: inputs, outputs, asset and metadata
  # asset for transfer is just { "id": "id of the create txn"}
  asset = { "id" => signed_create["id"]}
  output = Bdb.generate_output(bob["public"],10)
  input = Bdb.spend(create)
  puts "input = #{input.to_json}\n\n"

  transfer = Bdb.transfer_txn(input, output, asset, {"msg" => "txferring"})
  puts "transfer = #{transfer.to_json}\n\n"

  signed_transfer = Bdb.sign(transfer, alice["private"])
  puts "signed_transfer = #{signed_transfer.to_json}\n\n"
  # Now send this to server
  resp = post_transaction(ipdb, signed_transfer)
  puts "resp = #{resp.code} #{resp.body}"

  # Get all txns for this asset
  txns = JSON.parse(get_transactions_by_asset(root_url, asset["id"]).body)

  # txfr status
  puts get_transaction_status(ipdb, txns.first["id"]).body
  puts get_transaction_status(ipdb, txns.last["id"]).body
end

.transfer_asset(ipdb, receiver_pubkeys_amounts, sender_pubkey, sender_privkey, inputs, asset_id, metadata = {"ts"=> Time.now.to_s}) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/bigchaindb.rb', line 65

def self.transfer_asset(ipdb, receiver_pubkeys_amounts, sender_pubkey, sender_privkey, inputs, asset_id,  = {"ts"=> Time.now.to_s})
  asset = { "id" => asset_id}
  new_inputs = []
  input_amount = 0

  if inputs.nil? || inputs.none?
    # ask IPDB for unspent outputs
    unspent = Bdb.unspent_outputs(ipdb, sender_pubkey)
    if unspent.none?
      return nil, "#{sender_pubkey} does not have any unspent outputs for asset #{asset_id}"
    end
    unspent.each do |u|
      txn = JSON.parse(Bdb.get_transaction_by_id(ipdb, u["transaction_id"]).body)
      next unless ((txn["operation"] == "CREATE") && txn["id"] == asset_id) || ((txn["operation"] == "TRANSFER") && txn["asset"]["id"] == asset_id)
      input_amount += txn["outputs"][u["output_index"]]["amount"].to_i
      new_inputs += Bdb.spend(txn, [u["output_index"]])
    end
  else
    # assume that every output for sender_pubkey in given inputs is unspent and can be used as input
    inputs.each do |inp|
      input_amount += inp["outputs"].select { |o| o["condition"]["details"]["public_key"] == sender_pubkey }.inject(0) { |sum,out| sum + out["amount"].to_i }
      new_inputs += Bdb.spend(inp, inp["outputs"].each_with_index.select { |o,i| o["condition"]["details"]["public_key"] == sender_pubkey }.map(&:last))
    end
  end

  outgoing_amount = 0
  receiver_pubkeys_amounts.each do |pa|
    if pa[:amount] <= 0
      return nil, "Invalid amount (<=0) found for #{pa[:pubkey]}"
    end
    outgoing_amount += pa[:amount]
  end

  if outgoing_amount > input_amount
    return nil, "input_amount #{input_amount} < outgoing_amount #{outgoing_amount}"
  end
  outputs = receiver_pubkeys_amounts.collect { |pa| Bdb.generate_output(pa[:pubkey],pa[:amount]) }
  if outgoing_amount < input_amount
    # left-over amount should be transferred back to sender
    outputs.push(Bdb.generate_output(sender_pubkey,input_amount - outgoing_amount))
  end
  
  transfer = Bdb.transfer_txn(new_inputs, outputs, asset, )
  signed_transfer = Bdb.sign(transfer, sender_privkey)
  resp = post_transaction(ipdb, signed_transfer)
  if resp.code == 202
    txn = JSON.parse(resp.body)
    puts "Transaction #{txn["id"]} posted. Now checking status..."
    sleep(5)
    status_resp = get_transaction_status(ipdb, txn["id"])
    if (status_resp.code == 200) && JSON.parse(status_resp.body)["status"] == "valid"
      return txn, "success"
    else
      puts "Trying again: #{status_resp.code} #{status_resp.body}"
      sleep(5)
      status_resp = get_transaction_status(ipdb, txn["id"])
      if (status_resp.code == 200) && JSON.parse(status_resp.body)["status"] == "valid"
        return txn, "success"
      else
        return nil, "Tried twice but failed. #{status_resp.code} #{status_resp.body}"
      end
    end
  else
    return nil, "Error in transfer_asset: #{resp.code} #{resp.body}"
  end
end

.transfer_txn(inputs, outputs, asset, metadata = nil) ⇒ Object



29
30
31
32
33
34
35
36
37
38
# File 'lib/bigchaindb.rb', line 29

def self.transfer_txn(inputs, outputs, asset,  = nil)
  args = {inputs: inputs, outputs: outputs, asset: asset, metadata: }
  puts "In transfer_txn, args = #{args.inspect}"
  if 
    args = [inputs.to_json, outputs.to_json, asset.to_json, .to_json]
  else
    args = [inputs.to_json, outputs.to_json, asset.to_json]
  end
  JSON.parse(`bdb transfer #{args.shelljoin}`)
end

.unspent_outputs(ipdb, pubkey) ⇒ Object



60
61
62
63
# File 'lib/bigchaindb.rb', line 60

def self.unspent_outputs(ipdb, pubkey)
  resp = self.get_outputs_by_pubkey(ipdb, pubkey, spent = false)
  JSON.parse(resp.body)
end