Skip to content

Commit

Permalink
Merge branch '202-add-ipfs-api-to-wasm' into 'develop'
Browse files Browse the repository at this point in the history
Resolve "Add IPFS API to WASM"

Closes #202

See merge request in3/c/in3-core!183
  • Loading branch information
simon-jentzsch committed Mar 16, 2020
2 parents ee9ef1a + 1af19c1 commit d28729e
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 22 deletions.
3 changes: 2 additions & 1 deletion wasm/src/build_js.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ cat in3w.js | sed "s/uncaughtException/ue/g" >> $TARGET_JS
cat "$1/in3.js" >> $TARGET_JS
cat "$1/in3_util.js" >> $TARGET_JS
cat "$1/in3_eth_api.js" >> $TARGET_JS
cat "$1/in3_ipfs_api.js" >> $TARGET_JS
# we return the default export
echo " return IN3; })();" >> $TARGET_JS

Expand All @@ -23,7 +24,7 @@ if [ -e in3w.wasm ]
then cp in3w.wasm ../module/
fi
if [ $2 == "true" ]
then
then
cat "$1/package.json" | sed 's/wasm/asmjs/g' > ../module/package.json
cat "$1/../README.md" | sed 's/wasm/asmjs/g' > ../module/README.md
fi
24 changes: 23 additions & 1 deletion wasm/src/in3.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,12 @@ export default class IN3Generic<BigIntType, BufferType> {
*/
public eth: EthAPI<BigIntType, BufferType>


/**
* ipfs API.
*/
public ipfs: IpfsAPI<BufferType>

/**
* collection of util-functions.
*/
Expand Down Expand Up @@ -1098,4 +1104,20 @@ export declare interface Utils<BufferType> {
*/
private2address(pk: Hex | BufferType): Address

}
}
/**
* API for storing and retrieving IPFS-data.
*/
export declare interface IpfsAPI<BufferType> {
/**
* retrieves the content for a hash from IPFS.
* @param multihash the IPFS-hash to fetch
*
*/
get(multihash: string): Promise<BufferType>
/**
* stores the data on ipfs and returns the IPFS-Hash.
* @param content puts a IPFS content
*/
put(content: BufferType): Promise<string>
}
1 change: 1 addition & 0 deletions wasm/src/in3.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ class IN3 {
this.needsSetConfig = !!config
this.ptr = 0;
this.eth = new EthAPI(this)
this.ipfs = new IpfsAPI(this)
}

/**
Expand Down
28 changes: 28 additions & 0 deletions wasm/src/in3_ipfs_api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
class IpfsAPI {
constructor(client) {
this.client = client
this.encoding = 'base64'
}

/**
* retrieves the content for a hash from IPFS.
* @param multihash the IPFS-hash to fetch
*
*/
get(multihash) {
return this.client.sendRPC('ipfs_get', [multihash, this.encoding])
.then(response => response || Promise.reject(new Error(response.error || 'Hash not found')))
.then(result => this.client.util.base64Decode(result))
.then(result => this.client.util.toBuffer(result))
}

/**
* stores the data on ipfs and returns the IPFS-Hash.
* @param content puts a IPFS content
*/
put(data) {
let encoded = this.client.util.base64Encode(this.client.util.toBuffer(data))
return this.client.sendRPC('ipfs_put', [encoded, this.encoding])
.then(response => response || Promise.reject(new Error(response.error || 'Hash not found')))
}
}
96 changes: 78 additions & 18 deletions wasm/src/in3_util.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@ let convertBuffer = toUint8Array

// Overriding default convert
function setConvertBigInt(convertBigFn) {
convertBigInt = convertBigFn
convertBigInt = convertBigFn
}

function setConvertBuffer(convertBufFn) {
convertBuffer = convertBufFn
convertBuffer = convertBufFn
}

if (typeof(_free) == 'undefined') _free = function(ptr) {
in3w.ccall("ifree", 'void', ['number'], [ptr])
}
/**
* internal function calling a wasm-function, which returns a string.
* @param {*} name
* @param {...any} params_values
*/
if (typeof (_free) == 'undefined') _free = function (ptr) {
in3w.ccall("ifree", 'void', ['number'], [ptr])
}
/**
* internal function calling a wasm-function, which returns a string.
* @param {*} name
* @param {...any} params_values
*/
function call_string(name, ...params_values) {
const res = in3w.ccall(name, 'number', params_values.map(_ => _ && _.__proto__ === Uint8Array.prototype ? 'array' : typeof _), params_values)
if (!res) return null
Expand Down Expand Up @@ -53,8 +53,45 @@ function promisify(self, fn, ...args) {
})
}

// Credit to: https://stackoverflow.com/questions/8936984/uint8array-to-string-in-javascript
function Utf8ArrayToStr(array) {
var out, i, len, c;
var char2, char3;

out = "";
len = array.length;
i = 0;
while (i < len) {
c = array[i++];
switch (c >> 4) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
// 0xxxxxxx
out += String.fromCharCode(c);
break;
case 12: case 13:
// 110x xxxx 10xx xxxx
char2 = array[i++];
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
}
}

return out;
}

function toUtf8(val) {
if (!val) return val
if (val.constructor == Uint8Array) {
return Utf8ArrayToStr(val)
}
if (typeof val === 'string' && val.startsWith('0x')) {
const hex = fixLength(val).substr(2)
let str = ''
Expand Down Expand Up @@ -108,7 +145,7 @@ function abiEncode(sig, ...params) {
}

function ecSign(pk, data, hashMessage = true, adjustV = true) {
data = toUint8Array(data)
data = toUint8Array(data)
pk = toUint8Array(pk)
return call_buffer('ec_sign', 65, pk, hashMessage ? 1 : 0, data, data.byteLength, adjustV ? 1 : 0)
}
Expand Down Expand Up @@ -235,14 +272,14 @@ function toNumber(val) {
try {
return val.toNumber()
}
catch (ex) {
return toNumber(val.toHexString())
}
catch (ex) {
return toNumber(val.toHexString())
}
throw new Error('can not convert a ' + (typeof val) + ' to number');
}
}

function toBuffer (val, len = -1) {
function toBuffer(val, len = -1) {
return convertBuffer(val, len)
}
/**
Expand Down Expand Up @@ -272,10 +309,10 @@ function toUint8Array(val, len = -1) {
val = val.toArrayLike(Uint8Array)
if (!val)
val = new Uint8Array(0)
// since rlp encodes an empty array for a 0 -value we create one if the required len===0
// since rlp encodes an empty array for a 0 -value we create one if the required len===0
if (len == 0 && val.byteLength == 1 && val[0] === 0)
return new Uint8Array(0)
// if we have a defined length, we should padLeft 00 or cut the left content to ensure length
// if we have a defined length, we should padLeft 00 or cut the left content to ensure length
if (len > 0 && val.byteLength && val.byteLength !== len) {
if (val.byteLength > len) return val.slice(val.byteLength - len)
let b = new Uint8Array(len)
Expand All @@ -294,6 +331,27 @@ function toSimpleHex(val) {
hex = hex.substr(2);
return '0x' + hex;
}
/**
* decodes to base64
*/
function base64Decode(val) {
// calculate the length
if ((typeof val) !== 'string') throw new Error('Must be a string as input')
let lip = val.length
let len = lip / 4 * 3
if (lip > 1 && val[lip - 2] == '=' && val[lip - 1] == '=')
len -= 2
else if (val[lip - 1] == '=')
len -= 1

return call_buffer('base64Decode', len, val)
}
/**
* encodes to base64
*/
function base64Encode(val) {
return call_string('base64Encode', val, val.length)
}
/**
* returns a address from a private key
*/
Expand Down Expand Up @@ -388,6 +446,8 @@ const util = {
soliditySha3,
createSignatureHash,
toUint8Array,
base64Decode,
base64Encode
}

// add as static proporty and as standard property.
Expand Down Expand Up @@ -417,7 +477,7 @@ class SimpleSigner {
if (!pk || pk.length != 32) throw new Error('Account not found for signing ' + account)
return ecSign(pk, data, type, ethV)

}
}

}

Expand Down
15 changes: 14 additions & 1 deletion wasm/src/wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
#include "../../c/src/core/util/mem.h"
#include "../../c/src/third-party/crypto/ecdsa.h"
#include "../../c/src/third-party/crypto/secp256k1.h"
#include "../../c/src/third-party/libb64/cdecode.h"
#include "../../c/src/third-party/libb64/cencode.h"
#include <emscripten.h>
#include <string.h>
#include <time.h>
Expand Down Expand Up @@ -88,7 +90,7 @@ EM_JS(char*, in3_cache_get, (char* key), {
var val = Module.in3_cache.get(UTF8ToString(key));
if (val) {
var len = (val.length << 2) + 1;
var ret = stackAlloc(len);
var ret = stackAlloc(len);
stringToUTF8(val, ret, len);
return ret;
}
Expand Down Expand Up @@ -212,6 +214,17 @@ void EMSCRIPTEN_KEEPALIVE ctx_set_response(in3_ctx_t* ctx, in3_request_t* r, int
sb_add_chars(&r->results[i].result, msg);
}

uint8_t* EMSCRIPTEN_KEEPALIVE base64Decode(char* input) {
size_t len = 0;
uint8_t* b64 = base64_decode(input, &len);
return b64;
}

char* EMSCRIPTEN_KEEPALIVE base64Encode(uint8_t* input, int len) {
char* b64 = base64_encode(input, len);
return b64;
}

in3_t* EMSCRIPTEN_KEEPALIVE in3_create(chain_id_t chain) {
// register a chain-verifier for full Ethereum-Support
#ifdef ETH_FULL
Expand Down
3 changes: 2 additions & 1 deletion wasm/test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
"main": "js/src/index.js",
"scripts": {
"build": "tsc -p .",
"test": "mocha --timeout 15000 --full-trace testRunner.js testApi.js testUtil.js",
"test": "mocha --timeout 15000 --full-trace testRunner.js testApi.js testUtil.js testApi.js testIpfs.js",
"test_report": "mocha --timeout 15000 --reporter mocha-junit-reporter --reporter-options mochaFile=mocha.xml --full-trace testRunner.js testApi.js testUtil.js",
"test_api": "mocha --inspect-brk --timeout 15000 --full-trace testApi.js",
"test_ipfs": "mocha --inspect-brk --timeout 15000 --full-trace testIpfs.js",
"test_fail": "mocha --timeout 15000 --full-trace testApi.js testRunner.js ",
"test_all": "mocha --timeout 15000 --full-trace *.js",
"test_runner": "mocha --inspect-brk --timeout 15000 --full-trace testRunner.js"
Expand Down
15 changes: 15 additions & 0 deletions wasm/test/responses/ipfs_get.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"content": {
"id": 1,
"result": "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ=",
"jsonrpc": "2.0",
"in3": {
"lastValidatorChange": 0,
"lastNodeList": 8525528,
"execTime": 14,
"rpcTime": 0,
"rpcCount": 0,
"currentBlock": 17091918
}
}
}
15 changes: 15 additions & 0 deletions wasm/test/responses/ipfs_put.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"multihash": {
"id": 1,
"result": "QmbGySCLuGxu2GxVLYWeqJW9XeyjGFvpoZAhGhXDGEUQu8",
"jsonrpc": "2.0",
"in3": {
"lastValidatorChange": 0,
"lastNodeList": 8525528,
"execTime": 23,
"rpcTime": 0,
"rpcCount": 0,
"currentBlock": 17091918
}
}
}
20 changes: 20 additions & 0 deletions wasm/test/testIpfs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require('mocha')
const { assert } = require('chai')
const { createClient, mockResponse, IN3, beforeTest } = require('./util/mocker')

describe('API-Tests', () => {
beforeEach(beforeTest)
afterEach(IN3.freeAll)

it('ipfs_put', async () => {
mockResponse('ipfs_put', 'multihash')
const multihash = await createClient({ chainId: '0x7d0', proof: 'none' }).ipfs.put("Lorem ipsum dolor sit amet")
assert.equal(multihash, 'QmbGySCLuGxu2GxVLYWeqJW9XeyjGFvpoZAhGhXDGEUQu8')
})

it('ipfs_get', async () => {
mockResponse('ipfs_get', 'content')
const content = await createClient({ chainId: '0x7d0' }).ipfs.get('QmbGySCLuGxu2GxVLYWeqJW9XeyjGFvpoZAhGhXDGEUQu8')
assert.equal(IN3.util.toUtf8(content), 'Lorem ipsum dolor sit amet')
})
})
5 changes: 5 additions & 0 deletions wasm/test/util/mocker.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ function createClient(config = {}) {
needsUpdate: false,
contract: '0xac1b824795e1eb1f6e609fe0da9b9af8beaab60f',
registryId: '0x23d5345c5c13180a8080bd5ddbe7cde64683755dcce6e734d95b7b573845facb'
},
'0x7d0': {
needsUpdate: false,
contract: '0xac1b824795e1eb1f6e609fe0da9b9af8beaab60f',
registryId: '0x23d5345c5c13180a8080bd5ddbe7cde64683755dcce6e734d95b7b573845facb'
}
},
...config
Expand Down

0 comments on commit d28729e

Please sign in to comment.