Giter VIP home page Giter VIP logo

exonum-client's Introduction

Light Client for Exonum Blockchain

Build status npm version Coverage Status js-standard-style

A JavaScript library to work with Exonum blockchain from browser and Node.js. Used to sign transactions before sending to blockchain and verify blockchain responses using cryptographic proofs. Contains numerous helper functions. Find out more information about the architecture and tasks of light clients in Exonum.

If you are using Exonum in your project and want to be listed on our website & GitHub list — write us a line to [email protected].

Library compatibility with Exonum core:

JavaScript light client Exonum core
0.18.4 1.0.*
0.18.3 1.0.0-rc.1
0.17.1 0.12.*
0.16.9 0.11.*
0.16.9 0.10.*
0.13.0 0.9.*
0.10.2 0.8.*
0.9.0 0.7.*
0.6.1 0.6.*
0.6.1 0.5.*
0.3.0 0.4.0
0.3.0 0.3.0
0.2.0 0.2.0
0.1.1 0.1.*

Getting started

There are several options to include light client library in the application:

The preferred way is to install Exonum Client as a package from npm registry:

npm install exonum-client

Otherwise you can download the source code from GitHub and compile it before use in browser.

Include in browser:

<script src="node_modules/exonum-client/dist/exonum-client.min.js"></script>

Usage in Node.js:

let Exonum = require('exonum-client')

Data types

Exonum uses protobufjs library to serialize structured data into protobuf format.

Each transaction is signed before sending into blockchain. Before the transaction is signed it is converted into byte array under the hood.

The data received from the blockchain should be converted into byte array under the hood before it will be possible to verify proof of its existence using cryptographic algorithm.

Developer can both define data structures on the fly or use precompiled stubs with data structures.

To define Protobuf structures use protobufjs library.

Example:

const MessageSchema = new Type('CustomMessage')
  .add(new Field('balance', 1, 'uint32'))
  .add(new Field('name', 2, 'string'))
const Message = Exonum.newType(MessageSchema)

Exonum.newType function requires a single argument of protobuf.Type type.

Hash

Exonum uses cryptographic hashes of certain data for transactions and proofs.

Different signatures of the hash function are possible:

Exonum.hash(data, type)
type.hash(data)
Argument Description Type
data Data to be processed using a hash function. Object
type Definition of the data type. Custom data type or transaction.

An example of hash calculation:

// Define a data structure
const Message = new Type('User')
  .add(new Field('balance', 1, 'uint32'))
  .add(new Field('name', 2, 'string'))
// Define a data type
const User = Exonum.newType(Message)

// Data to hash
const data = {
  balance: 100,
  name: 'John Doe'
}
// Get a hash
const hash = User.hash(data)

It is also possible to get a hash from byte array:

Exonum.hash(buffer)
Argument Description Type
buffer Byte array. Array or Uint8Array.

An example of byte array hash calculation:

const arr = [8, 100, 18, 8, 74, 111, 104, 110, 32, 68, 111, 101]
const hash = Exonum.hash(arr)

Signature

The procedure for signing data using signing key pair and verifying of obtained signature is commonly used in the process of data exchange between the client and the service.

Built-in Exonum.keyPair helper function can be used to generate a new random signing key pair.

Sign data

The signature can be obtained using the secret key of the signing pair.

There are three possible signatures of the sign function:

Exonum.sign(secretKey, data, type)
type.sign(secretKey, data)
Exonum.sign(secretKey, buffer)
Argument Description Type
secretKey Secret key as hexadecimal string. String
data Data to be signed. Object
type Definition of the data type. Custom data type.
buffer Byte array. Array or Uint8Array.

The sign function returns value as hexadecimal String.

Verify signature

The signature can be verified using the author's public key.

There are two possible signatures of the verifySignature function:

Exonum.verifySignature(signature, publicKey, data, type)
type.verifySignature(signature, publicKey, data)
Argument Description Type
signature Signature as hexadecimal string. String
publicKey Author's public key as hexadecimal string. String
data Data that has been signed. Object
type Definition of the data type. Custom data type.

The verifySignature function returns value of Boolean type.

An example of signature creation and verification:

// Define a data structure
const Message = new Type('User')
  .add(new Field('balance', 1, 'uint32'))
  .add(new Field('name', 2, 'string'))
const User = Exonum.newType(Message)

// Define a signing key pair
const keyPair = Exonum.keyPair()

// Data that has been hashed
const data = {
  balance: 100,
  name: 'John Doe'
}
// Signature obtained upon signing using secret key
const signature = Exonum.sign(keyPair.secretKey, data, User)
// Verify the signature
const result = Exonum.verifySignature(signature, keyPair.publicKey, data, User)

Transactions

Transaction in Exonum is an operation to change the data stored in blockchain. Transaction processing rules is a part of business logic implemented in a service.

Sending data to the blockchain from a light client consist of 3 steps:

  1. Describe the fields of transaction using custom data types
  2. Sign data of transaction using signing key pair
  3. Send transaction to the blockchain

Read more about transactions in Exonum, or see the example of their usage.

Define transaction

An example of a transaction definition:

const Transaction = new Type('CustomMessage')
  .add(new Field('to', 2, 'string'))
  .add(new Field('amount', 3, 'uint32'))

const SendFunds = new Exonum.Transaction({
  schema: Transaction,
  service_id: 130,
  method_id: 0
})

Exonum.Transaction constructor requires a single argument of Object type with the next structure:

Property Description Type
schema Protobuf data structure. Object
service_id Service ID. Number
method_id Method ID. Number

schema structure is identical to that of custom data type.

Sign transaction

An example of a transaction signing:

// Signing key pair
const keyPair = Exonum.keyPair()

// Transaction data to be signed
const data = {
  from: 'John',
  to: 'Adam',
  amount: 50
}

// Create a signed transaction
const signed = SendFunds.create(data, keyPair)

Send transaction

To submit transaction to the blockchain send function can be used.

Exonum.send(explorerBasePath, transaction, attempts, timeout)
Property Description Type
explorerBasePath API address of transaction explorer on a blockchain node. String
transaction Signed transaction bytes. String, Uint8Array or Array-like
attempts Number of attempts to check transaction status. Pass 0 in case you do not need to verify if the transaction is accepted to the block. Optional. Default value is 10. Number
timeout Timeout between attempts to check transaction status. Optional. Default value is 500. Number

The send function returns a Promise with the transaction hash. The promise resolves when the transaction is committed (accepted to a block).

An example of a transaction sending:

// Define transaction explorer address
const explorerBasePath = 'http://127.0.0.1:8200/api/explorer/v1/transactions'
const transactionHash = await Exonum.send(explorerBasePath, signed.serialize())

Send multiple transactions

To submit multiple transactions to the blockchain sendQueue function can be used. Transactions will be sent in the order specified by the caller. Each transaction from the queue will be sent to the blockchain only after the previous transaction is committed.

Exonum.sendQueue(explorerBasePath, transactions, attempts, timeout)
Property Description Type
explorerBasePath API address of transaction explorer on a blockchain node. String
transactions List of transactions. Array
attempts Number of attempts to check each transaction status. Pass 0 in case you do not need to verify if the transactions are accepted to the block. Optional. Default value is 10. Number
timeout Timeout between attempts to check each transaction status. Optional. Default value is 500. Number

The sendQueue function returns a Promise with an array of transaction hashes. The promise resolves when all transactions are committed.

Cryptographic proofs

A cryptographic proof is a format in which a Exonum node can provide sensitive data from a blockchain. These proofs are based on Merkle trees and their variants.

Light client library validates the cryptographic proof and can prove the integrity and reliability of the received data.

Read more about design of cryptographic proofs in Exonum.

Merkle tree proof

const proof = new Exonum.ListProof(json, ValueType)
console.log(proof.entries)

The ListProof class is used to validate proofs for Merkelized lists.

Argument Description Type
json The JSON presentation of the proof obtained from a full node. Object
ValueType Data type for values in the Merkelized list. Custom data type

The returned object has the following fields:

Field Description Type
merkleRoot Hexadecimal hash of the root of the underlying Merkelized list String
entries Elements that are proven to exist in the list, together with their indexes Array<{ index: number, value: V }>
length List length Number

See an example of using a ListProof.

Map proof

const proof = new Exonum.MapProof(json, KeyType, ValueType)
console.log(proof.entries)

The MapProof class is used to validate proofs for Merkelized maps.

Argument Description Type
json The JSON presentation of the proof obtained from a full node. Object
KeyType Data type for keys in the Merkelized map. Custom or built-in data type
ValueType Data type for values in the Merkelized map. Custom data type

Keys in a map proof can either be hashed (which is the default option) or raw. To obtain a raw version for KeyType, use MapProof.rawKey(KeyType). The key type is determined by the service developer when the service schema is created. Raw keys minimize the amount of hashing, but require that the underlying type has fixed-width binary serialization.

The returned object has the following fields:

Field Description Type
merkleRoot Hexadecimal hash of the root of the underlying Merkelized map String
missingKeys Set of keys which the proof asserts as missing from the map Set<KeyType>
entries Map of key-value pairs that the are proven to exist in the map Map<KeyType, ValueType>

See an example of using a MapProof.

Integrity checks

Verify block

Exonum.verifyBlock(data, validators)

Each new block in Exonum blockchain is signed by validators. To prove the integrity and reliability of the block, it is necessary to verify their signatures. The signature of each validator are stored in the precommits.

The verifyBlock function throws an error if a block is invalid.

Argument Description Type
data Structure with block and precommits. Object
validators An array of validators public keys as a hexadecimal strings. Array

Verify table

Exonum.verifyTable(proof, stateHash, fullTableName)

Verify table existence in the root tree.

Returns root hash for the table as hexadecimal String.

Argument Description Type
proof The JSON presentation of the proof obtained from a full node. Object
stateHash Hash of current blockchain state stored in each block. String
fullTableName Name of the table, such as token.wallets. String

Built-in structures

The library exports Protobuf declarations from the core crate. Consult Protobuf files included into the library for more details.

Helpers

Generate key pair

const pair = Exonum.keyPair()
{
  publicKey: "...", // 32-byte public key
  secretKey: "..." // 64-byte secret key
}

Exonum.keyPair function generates a new random Ed25519 signing key pair using the TweetNaCl cryptographic library.

Get random number

const rand = Exonum.randomUint64()

Exonum.randomUint64 function generates a new random Uint64 number of cryptographic quality using the TweetNaCl cryptographic library.

Converters

Hexadecimal to Uint8Array

const hex = '674718178bd97d3ac5953d0d8e5649ea373c4d98b3b61befd5699800eaa8513b'
Exonum.hexadecimalToUint8Array(hex)

Hexadecimal to String

const hex = '674718178bd97d3ac5953d0d8e5649ea373c4d98b3b61befd5699800eaa8513b'
Exonum.hexadecimalToBinaryString(hex)

Uint8Array to Hexadecimal

const arr = new Uint8Array([103, 71, 24, 23, 139, 217, 125, 58, 197, 149, 61])
Exonum.uint8ArrayToHexadecimal(arr)

Uint8Array to Binary String

const arr = new Uint8Array([103, 71, 24, 23, 139, 217, 125, 58, 197, 149, 61])
Exonum.uint8ArrayToBinaryString(arr)

Binary String to Uint8Array

const str = '0110011101000111000110000001011110001011110110010111110100111010'
Exonum.binaryStringToUint8Array(str)

Binary String to Hexadecimal

const str = '0110011101000111000110000001011110001011110110010111110100111010'
Exonum.binaryStringToHexadecimal(str)

String to Uint8Array

const str = 'Hello world'
Exonum.stringToUint8Array(str)

Contributing

The contributing to the Exonum Client is based on the same principles and rules as the contributing to exonum-core.

Coding standards

The coding standards are described in the .eslintrc file.

To help developers define and maintain consistent coding styles between different editors and IDEs we used .editorconfig configuration file.

Test coverage

All functions must include relevant unit tests. This applies to both of adding new features and fixing existed bugs.

Changelog

Detailed changes for each release are documented in the CHANGELOG file.

Other languages support

Light Clients for Java and Python

License

Exonum Client is licensed under the Apache License (Version 2.0). See LICENSE for details.

exonum-client's People

Contributors

alexmarinenko avatar boguslavsky avatar bullet-tooth avatar dependabot-preview[bot] avatar dependabot[bot] avatar ilammy avatar katenegrienko avatar pepyakin avatar qvantor avatar slowli avatar stanislav-tkach avatar wingedfox avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

exonum-client's Issues

Source file structure

IMO, the package could definitely benefit from refining its structure:

/src
  index.js # exports exonum package with all dependencies
  /types
    index.js # reexports all datatypes
    primitive.js # primitive types: Uint8, Uint16, ...
    generic.js # newType()
    message.js # newMessage()
  /crypto
    index.js # hash, sign, verify, and keyPair
  /blockchain
    index.js # reexports blockchain datatypes
    merkle.js # MerkleProof
    merkle-patricia.js # MerklePatriciaProof
    block.js # Block

Corresponding package structure:

exonum
  types
    Uint8
    Uint16
    ...
    newType
    ...
  crypto
    hash
    sign
    verify
    keyPair
  blockchain
    Block
    Precommit
    MerkleProof
    MerklePatriciaProof

Notes:

  • blockchain will potentially contain other blockchain-related instances, such as Blockchain or Service
  • I'm not sure which is better: types or type
  • I wonder if Pubkey or Signature should be primitive datatypes, or just byte arrays with specific length

Can I disable check transaction status after send?

In some case, e.g. if we created long-timed block (5 - 10 min or min 100+ transaction per block), current client isn't good way to sending transaction. If big block (or long timeout) we haven't transaction status "committed" as long, as we collected data at mempool. But current logic create periodical check status.

May be we can disable query to explorer api? In example, set explorerBasePath (in send function) to Null or attempts counter to 0? With this we can send more transaction per second (and with less memory usage) from one client too.

Verify an error messages in tests

Verify not only error type but also message:
expect(() => Exonum.someMethod(params)).to.throw(Error);
to
expect(() => Exonum.someMethod(params)).to.throw(Error, 'Thrown error message...');

Fix Jshint

  • should probably specify ./src/**/*.js here; otherwise, only a single file is linted
  • fix jshint errors

Uniform coding style?

It could be beneficial to use a uniform coding style for all JS projects. I like semistandard (it is currently used in pwbox), although it is a subject of discussion. Such a tool can also be used as a linter, which is necessary for writing quality code.

Rework NewMessage

Issue: signature field is part of NewMessage entity.
Need to pass data as { body: ..., signature: ... }.

Documentation refactoring

It could be beneficial to split documentation from /README.md into several files and put them into the /doc folder, as per the npm spec, probably as tutorials (which can be later referenced from jsdocs via @tutorial tags). Each file could reference a specific section of the exonum module, e.g.:

  • Data types and messages
  • Cryptography
  • Merkle proofs
  • Work with blockchain

Main README could contain one or two examples how to use the lightweight client.

Also, there are several issues with the documentation contents, such as the repeated usage of bits instead of bytes in specifying datatype lengths. And sometimes the lengths of datatypes in examples is seemingly wrong. For example, in some cases signatures take 32 bytes, whereas they should take 64 bytes.

exonum.MapProof.merkleRoot is inconsistent when the type of boolen in protobuf

platform: Ubuntu 18.04
rust: rustc 1.38.0-nightly (78ca1bda3 2019-07-08)
exonum: 0.11-master
exonum-client: 0.16.4

Hi, there is a strange situation when I add boolean data in cryptocurrency.proto under cryptocurrency-advance (diff below). After adding this field, the exonum.MapProof cannot get the correct Merkle root from the javascript client.

Could someone help to check whether it is an issue?

diff --git a/examples/cryptocurrency-advanced/backend/src/api.rs b/examples/cryptocurrency-advanced/backend/src/api.rs
index 8006eb7..c01cb1f 100644
--- a/examples/cryptocurrency-advanced/backend/src/api.rs
+++ b/examples/cryptocurrency-advanced/backend/src/api.rs
@@ -81,6 +81,13 @@ impl PublicApi {
         let to_table: MapProof<Hash, Hash> =
             general_schema.get_proof_to_service_table(CRYPTOCURRENCY_SERVICE_ID, 0);
 
+        let to_wallet =
+            currency_schema.wallets().get_proof(query.pub_key).check().unwrap();
+
+        println!("show entries {:?}", to_wallet.entries().collect::<Vec<_>>());
+        println!("show missing_keys {:?}", to_wallet.missing_keys().collect::<Vec<_>>());
+        println!("show merkle root {:?}", to_wallet.merkle_root());
+
         let to_wallet: MapProof<PublicKey, Wallet> =
             currency_schema.wallets().get_proof(query.pub_key);
 
diff --git a/examples/cryptocurrency-advanced/backend/src/proto/cryptocurrency.proto b/examples/cryptocurrency-advanced/backend/src/proto/cryptocurrency.proto
index 04252b0..7435e59 100644
--- a/examples/cryptocurrency-advanced/backend/src/proto/cryptocurrency.proto
+++ b/examples/cryptocurrency-advanced/backend/src/proto/cryptocurrency.proto
@@ -54,4 +54,6 @@ message Wallet {
   uint64 history_len = 4;
   // `Hash` of the transactions history.
   exonum.Hash history_hash = 5;
+  // test
+  bool test_fail = 6;
 }
diff --git a/examples/cryptocurrency-advanced/backend/src/wallet.rs b/examples/cryptocurrency-advanced/backend/src/wallet.rs
index 5a33dea..d4ed040 100644
--- a/examples/cryptocurrency-advanced/backend/src/wallet.rs
+++ b/examples/cryptocurrency-advanced/backend/src/wallet.rs
@@ -32,6 +32,8 @@ pub struct Wallet {
     pub history_len: u64,
     /// `Hash` of the transactions history.
     pub history_hash: Hash,
+    /// test
+    pub test_fail: bool,
 }
 
 impl Wallet {
@@ -49,6 +51,7 @@ impl Wallet {
             balance,
             history_len,
             history_hash,
+            test_fail: false,
         }
     }
     /// Returns a copy of this wallet with updated balance.
diff --git a/examples/cryptocurrency-advanced/frontend/src/plugins/blockchain.js b/examples/cryptocurrency-advanced/frontend/src/plugins/blockchain.js
index 8b084a0..f654c22 100644
--- a/examples/cryptocurrency-advanced/frontend/src/plugins/blockchain.js
+++ b/examples/cryptocurrency-advanced/frontend/src/plugins/blockchain.js
@@ -116,9 +116,12 @@ module.exports = {
               return Exonum.verifyBlock(data.block_proof, validators).then(() => {
                 // verify table timestamps in the root tree
                 const tableRootHash = Exonum.verifyTable(data.wallet_proof.to_table, data.block_proof.block.state_hash, SERVICE_ID, TABLE_INDEX)
-
+                console.log('tableRootHash: ' + tableRootHash)
                 // find wallet in the tree of all wallets
                 const walletProof = new Exonum.MapProof(data.wallet_proof.to_wallet, Exonum.PublicKey, Wallet)
+                console.log(walletProof.entries)
+                console.log(walletProof.missingKeys)
+                console.log('walletProof merkleRoot: ' + walletProof.merkleRoot)
                 if (walletProof.merkleRoot !== tableRootHash) {
                   throw new Error('Wallet proof is corrupted')
                 }

The results are below.
From the backend side:

show entries [(PublicKey(cf0c5b2c...), Wallet { pub_key: PublicKey(cf0c5b2c...), name: "ababa", balance: 100, history_len: 1, history_hash: Hash(416e9a05...), test_fail: false })]
show missing_keys []
show merkle root Hash(b6cb148d...)

From the frontend side:
Screen Shot 2019-07-10 at 11 43 12

You can see the result of merkleRoot is different from "b6cb148d..." (backend result and tableRootHash in the frontend side) and "336bb671...". (walletProof merkleRoot in the frontend side).

Tracking issue for 0.1 release

@boguslavsky:

  • Bug fixes and style improvements. PR: #12
  • Rework modules export. Task: #5, PR: #13
  • Update format. Task: #20, PR: #24
  • Fix Jshint. Task: #15
  • Rework error handling: change console.error to throwing errors. Task: #16, PR: #28
  • Documentation refactoring. Task: #1, PR: #32
  • Publish as npm package. Task: #18
  • Publish as bower package. Task: #19
  • Coveralls code coverage + badge. Task: #22

Make it work with Typescript

I'm making use of exonum-client using Typescript on NodeJS v10.
I did:
$ npm i @types/node --save
$ npm i exonum-client --save

Now TSLint is giving me this error:

[ts] Could not find a declaration file for module 'exonum-client'. '/Users/username/workarea/node_proj/project/node_modules/exonum-client/lib/index.js' implicitly has an 'any' type. Try 'npm install @types/exonum-client' if it exists or add a new declaration (.d.ts) file containing 'declare module 'exonum-client';'

I'd request that $ npm i @types/exonum-client is made available. If not, then kindly supply a exonum-client/index.d.ts with exports and declarations done properly.

Make send headers for tx.send

Actual

Method tx.send(explorerBasePath, type, data, secretKey, attempts, timeout) don't have opportunity send request headers

Expected

Method tx.send(explorerBasePath, type, data, secretKey, attempts, timeout, headers) have opportunity send request headers

 return _axios2.default.post('' + explorerBasePath, {
    tx_body: txBody
  }, headers}).then...

Exonum datatypes interface and newType()

The current definition of Exonum datatypes works, but it can probably be improved as follows.

Each datatype has a set of methods

DataType.prototype = {
  serialize: function () {
    // serializes the value as Uint8Array
  },
  hash: function () {
    // calculates the hash of the serialized data
    // internally, simply calls nacl's hash on this.serialize()
  },
  isFixedLength: function () {
    // Whether the datatype has fixed byte length. It determines how the fields of this type
    // are (de)serialized as fields of other types
  }
};
Datatype.deserialize = function (bytes) {
  // deserializes the value from Uint8Array
};

Example:

// Built-in types
var x = new exonum.types.Uint8(120);
x.serialize(); // === Uint8Array([ 120 ])
x.hash(); // === exonum.types.Hash([ ... ])
var y = exonum.types.Uint8.deserialize(new Uint8Array([ 125 ]));
// Constructed types
var SomeType = exonum.types.newType({ /* type spec */ });
var z = new SomeType(/* initializer; TBD */);
z.serialize();
z.hash();
SomeType.deserialize(new Uint8Array([ /* ... */ ]));

This interface could be especially useful for datatypes constructed with newType(), but it could work for primitive/predefined datatypes for consistency.

Possible problems/topics for discussion:

  • Primitive integer datatypes cease to behave like integers. For example, x + y in the example above will cease working properly (although it could be partially brought back by defining the valueOf method). But it may be a good thing; we probably wouldn't want to allow arbitrary operations on datatypes. Raw values could be extracted from primitive types in a uniform fashion, e.g., via raw property.
  • deserialize may double as a constructor: If the constructor is supplied with a Uint8Array, then deserialization is performed. Note that this will work for hashes, pubkeys and signatures.
  • newType() should be discussed in detail in another issue; I feel that its interface is not optimal either

Travis CI

Travis CI integration should be enabled for the project. In order to do this, scripts.test should probably specified in package.json because Travis executes npm test for testing by default.

Exonum v0.2 compatibility?

Hi

I am used this "template" and all works, but update of exonum-client breaks compatibility ("Unable to verify transaction") for all exonum versions (0.2, 0.1.1) (my code isn't changed)

With what version is the client compatible now?

Use Flow type system?

Since we (supposedly) build secure software, we need to worry about type safety everywhere (including the client), and allow to take care of type safety for developers using the client. Flow seems like a straightforward way to accomplish this; although there are some alternatives (e.g., TypeScript or Elm, or even compiling Rust code directly to wasm or asm.js).

@deniskolodin Do you think Elm would be an optimal way to pursue? I'm a bit worried that a "fully" functional Elm's approach can have certain performance implications. For example, the tutorial on lists does not seem to unroll recursion, not even in the tail-form (i.e.,

length : List a -> Int
length list = length2 list 0

length2 : List a -> Int -> Int
length2 list acc =
  case list of
    [] ->
        acc
    first :: rest ->
        length2 rest 1 + acc

so it doesn't work with lists of length more than several thousand. Am I doing something wrong, or is this just how Elm rolls (heh)? Also, in general it seems Flow is a bit more supported than Elm, although I may be biased.

Type specifications

Type specifications right now are quite redundant. While such specs might make sense in Rust, they could be improved in JavaScript to look like

[
  { name: 'foo', type: types.Uint8 },
  { name: 'bar', type: types.String },
  { name: 'bazz', type: SomeCustomType }
]

Future compatibility. Note that types declared in this way are sequences; besides them, we might define other compositions in the future (e.g., enums, optional types, arrays, and/or Any). They can be straightforwardly defined though related constructions, e.g.,

{
  oneOf: [
    types.Uint32,
    types.String
  ]
}

for enums, { array: types.Uint32 } for arrays, etc. Thus, we probably shouldn't worry about specification extensions right now.

FixedBuffer serialization

Figure out about FixedBuffer serialization. It seems FixedBuffer should be serialized using pointer.

Merkle (Patricia) proof interface and implementation

Merkle (Patricia) tree proofs essentially prove that a certain element (or several elements) belongs to a certain data collection (list in the case of Merkle trees; map in the case of MPTs). Essentially, a MTs and MPTs are views of lists or maps, in which some elements are unknown. So, it could be logical to implement them as such.

Interface

Thus, it could be logical to have the following interface for MTs and MPTs:

  • constructor(json): creates an MT or MPT based on JSON retrieved from the server. Verifies the internal structure of the tree (see below) and throws TypeError if the provided JSON is invalid
  • hash(): returns the hash of the collection, calculated as per the current algorithm
  • [MT only?] length (read-only property): returns the length of the underlying collection
  • get(index): gets the element at the specified index (integer for MTs, Hash in the case of MPTs). Returns undefined if the element was not in the provided JSON
  • has(index): returns true if the requested index is in the collection view, false otherwise
  • [MPT only?] maybeHas(index): returns true if the specified index may be present in the collection (not necessarily in the current view). Hence, the proof of absence for index is !view.maybeHas(index).
  • Symbol.iterator and entries(): returns the iterator for [index, value] pairs in the view, like the JS Map (maybe, pull other methods from Map too, e.g., forEach, keys and values).

Internal construction

Merkle tree

Recursive definition:

MT<ItemType> = oneOf({
  stub: Hash, // "collapsed" part of the tree
  branch: { left: MT<ItemType>, right: MT<ItemType> },
  sprout: { child: MT<ItemType> }, // a non-branching intermediate node 
  val: ItemType, // value
});

ListView<ItemType> = {
  root: MT<ItemType>,
  length: Uint64
};

Consistency checks:

  • All values are at the depth expected by length
  • All stubs are at the depth not exceeding the same depth
  • The left child in any branch cannot be a sprout
  • (optional) The tree must be non-redundant, e.g., a branch cannot have both stub children

Methods:

  • get(index) can be implemented by traversing the tree from the root
  • hash() can be implemented by traversing the tree from the leaves

Merkle Patricia tree

Recursive definition:

MPT<ItemType> = {
  bits: BitSlice, // key bits connecting the node to its parent
  node: oneOf({
    stub: Hash, // "collapsed" part of the tree
    branch: { left: MPT<ItemType>, right: MPT<ItemType> },
    val: ItemType // value
  })
};

MapView<ItemType> = MPT<ItemType>;

bits may be woven into all type variants if deemed necessary.

Consistency checks:

  • bits may be empty only for the root node
  • For branches, left.bits must start with 0 and right.bits with 1
  • Key length of all values must be as expected (256 bits)
  • Key length of all stubs must not exceed 256 bits
  • (optional) The tree must be non-redundant, e.g., a branch cannot have both stub children

Methods:

  • get(index) and maybeHas(index) can be implemented by traversing the tree from the root
  • hash() can be implemented by traversing the tree from the leaves

Rework serialization

It could be better to calculate the length of the buffer needed for serialization, and use Uint8Array buffers for increased efficiency.
Implemented as a part of PR #8.

Review code style

Reviewing defensive coding style (explicit type comparisons, === false, etc.)

Check library version

Make it possible to check library version via access to some built-in variable, like:

$.fn.jquery // "2.1.4"

BadRequest("Invalid 'hash' parameter: Odd number of digits")

Hey there!

I'm trying to submit transactions (and they do endup in the blockchain)!
But I'm getting this weird error printed on the server side...

Sat, 19 May 2018 15:32:15 +0100 ERROR iron::iron Error handling:
Request {
    url: Url { generic_url: "http://192.168.43.29:8000/api/explorer/v1/transactions/[object%20Object]" }
    method: Get
    remote_addr: V4(192.168.43.29:59393)
    local_addr: V4(0.0.0.0:8000)
}
Error was: Compat { error: BadRequest("Invalid \'hash\' parameter: Odd number of digits") }

Submitting the same signed transaction through curl works without any problems:

{
  "body": {
    "issuer": "fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a",
    "expiry": "never",
    "description": "print <3 Rust?"
  },
  "protocol_version": 0,
  "service_id": 1,
  "message_id": 0,
  "signature":"15011786453fafa2b432ae8fa1e41eb77ebdc1c831e694c0a362f1c0f51aff5d821f47a08d0f89872ff1a4a7ae7b297cc05f821f807b05e1d25632a457d03800"
}

The client code:

#!/usr/bin/env node
let Exonum = require('exonum-client')
const zeroPubKey = '0000000000000000000000000000000000000000000000000000000000000000'

let ServiceDelegate = Exonum.newType({
  fields: [
    { name: 'issuer', type: Exonum.PublicKey },
    { name: 'expiry', type: Exonum.String },
    { name: 'description', type: Exonum.String },
    { name: 'resolution', type: Exonum.PublicKey }
  ]
})

const keyPair = {
  publicKey: 'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a',
  secretKey: '978e3321bd6331d56e5f4c2bdb95bf471e95a77a6839e68d4241e7b0932ebe2b' +
  'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a'
}

const data_sd = {
  issuer: keyPair.publicKey,
  expiry: 'never',
  description: 'print <3 Rust?',
  resolution: zeroPubKey
}

////////////////////////////////////////////////
// Testing signing and signature verification //
////////////////////////////////////////////////

let signature_sd = Exonum.sign(keyPair.secretKey, data_sd, ServiceDelegate)

if (Exonum.verifySignature(signature_sd, keyPair.publicKey, data_sd, ServiceDelegate)) {
    console.log("OK, signature verified!")
} else {
    console.log("Nope")
}

/////////////////////////////////////
// Let's compose a transaction now //
/////////////////////////////////////

let createServiceDelegate = Exonum.newMessage({
  protocol_version: 0,
  service_id: 1,
  message_id: 0,
  fields: [
    { name: 'issuer', type: Exonum.PublicKey },
    { name: 'expiry', type: Exonum.String },
    { name: 'description', type: Exonum.String }
  ]
})

const data_create_sd = {
  issuer: keyPair.publicKey,
  expiry: 'never',
  description: 'print <3 Rust?'
}

let signature_create_sd = createServiceDelegate.sign(keyPair.secretKey, data_create_sd)
if (Exonum.verifySignature(signature_create_sd, keyPair.publicKey, data_create_sd, createServiceDelegate)) {
    console.log("OK, signature verified: "+signature_create_sd)
} else {
    console.log("Nope")
}

const transactionEndpoint = 'http://192.168.43.29:8000/api/services/empathy/v1/delegates'
const explorerBasePath = 'http://192.168.43.29:8000/api/explorer/v1/transactions/'

Exonum.send(transactionEndpoint, explorerBasePath, data_create_sd, signature_create_sd, createServiceDelegate)
  .then(tx => {
      console.log(tx)
  })
  .catch(err => {
      console.log(err)
  })

Exonum-client version: 0.8.0
Exonum version: 0.7.0

Bugs in documentation & how to use the client with the rust server

Hey peeps,

Thanks for providing such great open source libraries. Found some issues.

Documentation has bugs e.g:

var SendFunds = Exonum.newMessage({
    size: 24,
    network_id: 0,
    protocol_version: 0,
    service_id: 0,
    message_id: 0,
    signature: '07038584a4a77510ea5eced45f54dc030f5864ab6a5a2190666b47c676bcf15a' +
     '1f2f07703c5bcafb5749aa735ce8b7c366752be882314f5bbbc9a6af2ae634fc',
    fields: {
        from: {type: Exonum.Hash, size: 8, from: 0, to: 8},
        to: {type: Exonum.Hash, size: 8, from: 8, to: 16},
        amount: {type: Exonum.Uint64, size: 8, from: 16, to: 24}
    }
});

Where Exonum.Hash seems to actually be 32-bytes rather than 8 and thus the actual size is wrong and causes a runtime error.

There is no mention of where the above signature actually comes from nor any mention of the primitives required to form a keyPair (even though it is mandatory to know that tweetnacl is the underlying cryptographic library being used). If you use Exonums default way of signing the body and follow the docs the result doesn't work:

         var keyA = Exonum.keyPair();

         var walletType = Exonum.newType({
             size: 40,
             fields: {
                 pub_key: {type: Exonum.PublicKey, size: 32, from: 0, to: 32},
                 name: {type: Exonum.String, size: 8, from: 32, to: 40}
             }
         });

         var walletData = {
             pub_key: keyA.publicKey,
             name: "Someone"
         };

         var walletSignature = Exonum.sign(keyA.secretKey, walletData, walletType);

         var walletJson = {
             body: walletData,
             network_id: 0,
             protocol_version: 0,
             service_id: 1,
             message_id: 1,
             signature: walletSignature
         };

         fetch('http://127.0.0.1:8000/api/services/some_chain/v1/wallets/transaction', {method: 'POST', body: JSON.stringify(walletJson)})
             .then(result => {
                 console.log(result);
             })
             .catch(error => {
                 console.log(error);
             });

Which begs the question how do you form the signature as per the curl requests in the documentation in order to match what is expected?

Update transaction format

Add fields:

  • message_id
  • network_id
  • protocol_version
  • service_id

Example of precommit:

{
    “body”: {
        “block_hash”: “29a5b5797b1fcf3e32236440f5fa79c64662b8ba29c791d4ee4e0e498b11263c”,
        “height”: “4”,
        “propose_hash”: “90cc7ebb90e8873e98a6cb662471b3baae68eb5f1ab205f13a898037323edc76”,
        “round”: 4,
        “time”: {
            “nanos”: 804000000,
            “secs”: 1486720350
        },
        “validator”: 0
    },
    “message_id”: 4,
    “network_id”: 0,
    “protocol_version”: 0,
    “service_id”: 0,
    “signature”: “11380a7faf53dcfea888642d9027db417162e5f75939db7efd6e892abb8543f4c2b7922ec4689a0f096da3a3e50600cd78a7d28e49b89d0de43f8e92d53c490e”
}

Drop unnecessary size field

Why we have to set size directly for JS library?

name: {type: Exonum.String, size: 8, from: 0, to: 8},

We can calculate size as to - from or use internal constants associated with type like we do in core library. Right?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.