0%

大家都知道Webpack可以打包許多的Javascript files成一個,方便取用
如果我們的Javascript使用到Webassembly的話可否比照辦理,答案是肯定的
But,我不會用Webpack打包,我也不是專門前端的人,不想花太多時間研究
於是我就改用腦殘版Parcel
零設定就產生了,有興趣的話可以參考demo
接下來的重點是如何放在如何生成Webassembly

AssemblyScript

AssemblyScript
Typescript的subset,會寫前端的話這套適用,不過我是System Language的愛好者

Rust

Rust對Webassembly的支援還真是高啊..只要Cargo.toml中定義成Shared Library
然後下

1
$ cargo build --target wasm32-unknown-unknown  --release

就好了,不過百廢待舉啊

C/C++

基本上有兩個方案
Emscripten 走原先ASM.JS的老路,不過無法直接將生成的wasm放到我的demo上跑
Minimal C/C++ language toolset for building wasm files
有潛力,不過我沒一次成功過,也不能搭配CMake一起使用,還是個半殘品

結論

在觀望吧,現在還不是成熟的時刻

C++ 的Serialization / Deserialization已經很成熟了
有了Boost 和 Cereal可以選,不過它們需要ifstreamofstream做參數傳入
如果我們需要用於網路傳輸跟Database該怎麼辦
Boost ASIO幫我們解決了這個問題

Output Wrapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
template <typename Container, typename SinkType, typename CharType>
class container_sink
{
public:
typedef CharType char_type;
typedef boost::iostreams::sink_tag category;

container_sink(Container& container)
: container_(container)
{
static_assert(sizeof(SinkType) == sizeof(CharType), "invalid size");
}

std::streamsize write(const char_type* buffer, std::streamsize size)
{
const auto safe_sink = reinterpret_cast<const SinkType*>(buffer);
container_.insert(container_.end(), safe_sink, safe_sink + size);
return size;
}

private:
Container& container_;
};

template <typename Container>
using byte_sink = container_sink<Container, uint8_t, char>;
using data_sink = boost::iostreams::stream<byte_sink<data_chunk>>;

如何使用

1
2
3
4
5
6
7
data_chunk data;
data_sink ostream(data);
boost::archive::binary_oarchive oa(ostream);
const gps_position g(35, 59, 24.567f);
oa << g;
ostream.flush();
return ostream;

Input Wrapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
template <typename Container, typename SourceType, typename CharType>
class container_source
{
public:
typedef CharType char_type;
typedef boost::iostreams::source_tag category;

container_source(const Container& container)
: container_(container), position_(0)
{
static_assert(sizeof(SourceType) == sizeof(CharType), "invalid size");
}

std::streamsize read(char_type* buffer, std::streamsize size)
{
const auto amount = container_.size() - position_;
const auto result = std::min(size,
static_cast<std::streamsize>(amount));

// TODO: use ios eof symbol (template-based).
if (result <= 0)
return -1;

const auto value = static_cast<typename Container::size_type>(result);
const auto limit = position_ + value;
const auto start = container_.begin() + position_;

const auto end = container_.begin() + limit;
std::copy(start, end, buffer);
position_ = limit;
return result;
}

private:
const Container& container_;
typename Container::size_type position_;
};

template <typename Container>
using byte_source = container_source<Container, uint8_t, char>;
using data_source = boost::iostreams::stream<byte_source<data_chunk>>;

如何使用

1
2
3
4
data_chunk data;
data_source istream(data);
boost::archive::binary_iarchive ia(istream);
istream >> g;

程式語言的Package management要做好很難
於是就有讓人吐槽的地方,這次我們的對象就是golang
談談我不喜歡的兩個點吧

Package path

當要下載相依Package的時候,預先放在${GOPATH}/src
而不適放在Repository個某的目錄底下,這種方式我不喜歡
不喜歡的點在於
– 萬一修改了相依package的程式碼,而沒在Repository沒看到任何修改,忘記這件事的機率還蠻大的
– 如果修改了相依Package的程式碼,想要Send Request給Pacagage的Maintainer,但是對方不接受,於是要自己Fork出一份,這就遇到產生另外一個問題

import url issue

在golang的世界隨處可見

1
import "github.com/xxx/yyy"

這樣的程式碼,當你決定要Fork出自己的Pacakge之後
可能就變成

1
import "github.com/zzz/yyy"

所有上面的程式碼都要跟著改掉,非常冗餘的資訊
一次commit就修改所有一模一樣的import path
Relative imports
這提案還在討論,不過我想通過的機會也不大

先決條件

首先我們先安裝必要的Python3和Nodejs

1
2
$ apt install -y python3 nodejs npm
$ npm install -g ethereumjs-testrpc

接著安裝開發版的Ethereum,由於她不識真正的Ethereum,僅限開發使用

1
$ npm install -g ethereumjs-testrpc

接著執行

1
$ testrpc

就看到開發版的Ethereum跑起來了,建立了十個帳戶
接著開另外一個Terminal來操作,首先我們安裝Truffle

1
$ npm install -g truffle

建立一個新項目

1
2
3
$ mkdir demo 
$ cd demo
$ truffle init

這會生成一個command line的應用程式,如果需要一個GUI版的可以將江最後一行改成,不過操作還是以CLI版為主

1
$ truffle unbox webpack

接著開始建立合約

第一個合約

我們首先建立第一筆合約

1
$  truffle create contract Test

會產生ontracts/Test.sol這個檔案,不過打開來什麼都沒有
因此我們手動修改合約內容

1
2
3
4
5
6
7
pragma solidity ^0.4.4;

contract Test {
function multiply(uint a) public pure returns(uint d) {
return a * 7;
}
}

接著新建migrations/2_deploy_contracts.js

1
2
3
4
var Test = artifacts.require("./Test.sol");
module.exports = function(deployer) {
deployer.deploy(Test);
};

接著編譯合約

1
$ truffle compile

產生的合約在./build/contracts/Test.json,有興趣可以打開來看
接著部屬合約,在部屬合約之前要先修改truffle.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*"
},
live: {
host: "178.25.19.88", // Random IP for example purposes (do not use)
port: 80,
network_id: 1, // Ethereum public network
}
},
};

先不用館live那組`,保留給真正的Ethereum使用,目前關心的只有development那組,接著我們把合約部屬到development network,也就是Testrpc上

1
$ truffle migrate --net development

切回Testrpc的Terminal,可以看到我們的合約被放上去了

驗證合約

1
2
3
4
$ truffle console
truffle(development)> Test.deployed().then(function(instance){contract = instance;});
truffle(development)> ontract.multiply(10)
igNumber { s: 1, e: 1, c: [ 70 ] }

另外一個方法是跑Unit Test
我們新增test/TestTest.sol,然後寫入以下資料

1
2
3
4
5
6
7
8
9
10
11
12
13
pragma solidity ^0.4.16;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Test.sol";

contract TestTest {
Test test = Test(DeployedAddresses.Test());
function testUserCanAdoptPet() public {
uint returnedId = test.multiply(10);
uint expected = 70;
Assert.equal(returnedId, expected, "10 * 7 = 70");
}
}

接著跑

1
$ truffle test

開發一個DApp

這就是前面幾個東西組合加上適合的前端
Javascript用web3.js
Python用Web3.py
Java和Android用web3j
這邊有範例
Ethereum Pet Shop
fileHash
詳細就部戲說了

Final

這邊只有說要怎麼寫Smart Contract,怎麼Deply Contract`,以及怎麼開發Dapp
至於Ethereum 的架設一個字都沒提
並且每個Blockchain的Dapp開發情形不同,只是給一個大智的輪廓

最近這段時間對區塊鏈做了一點功課,無關發幣灑幣之類的東西,純粹技術上的研究
一開始看了A blockchain in 200 lines of code
看了之後還真的以為大概就這樣,看了現實的專案卻看不懂,真是好傻好天真
遇上幾個困難的點,首先是交易這件事
上面的教學完全沒提到交易,不過Bitcoin或是Ethereum都把交易看作核心的一部分
基於Account的交易反倒容易理解,看到Bitcoin的UTXO Model真是一頭霧水
對於不懂的人可以參考
比特幣UTXO模型介紹-如何解讀比特幣交易
UTXO 与账户余额模型
除此之外,還有Persistence這點要考慮,資料要怎麼寫入硬碟保存,怎麼讀取,Serialization / /Deserialization 都是學問
不過還有一點頭大的,既然是分散式結構,就會有Role的不同
有些是Client,有些是Node,有些是Miner
Node之間如何達成Consensus,Pow?Pos?Dpos?Others?
P2P表示的是既是Server也是Client,同時需要處理兩方面的情況
這邊有個很好的範例
Building Blockchain in Go
除了Network那邊稍弱之外,對有心開發自己Blockchain的人來說,算是一個非常好的範例教學

Final

雖然技術上有不少東西可以學,不過我對Blockchain的未來不看好
倒不是技術有問題,而是我認為有價值的東西通常都需要政府來做
發幣賣錢這件事我不喜歡,不過也不是我的看法一定正確,畢竟我也不是個經濟學專家
只是覺得除了中心化解法之外,多了一種選擇

最近幫朋友幫忙寫一個可以Demo的Protottype,有三個角色
Server,Client,Observer
許多Client會送資料給Server,Server處理這些資料之後匯集給Observer
說來不難的東西,因為用了我不熟的技術,golang + javascript,浪費太多時間
果然隔行如隔山啊,寫寫在路上遇到的問題

SSL / TLS Transport

由於想要練習,所以直接用SSL來當作傳輸協定,跟TCP大同小異,不過困難的是Certificate的產生
Server端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
cert, err := tls.LoadX509KeyPair("certs/server.pem", "certs/server.key")
if err != nil {
log.Fatalf("server: loadkeys: %s", err)
}
config := tls.Config{Certificates: []tls.Certificate{cert}}
config.Rand = rand.Reader
service := "0.0.0.0:8000"
listener, err := tls.Listen("tcp", service, &config)
if err != nil {
log.Fatalf("server: listen: %s", err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("server: accept: %s", err)
break
}
defer conn.Close()
log.Printf("server: accepted from %s", conn.RemoteAddr())
tlscon, ok := conn.(*tls.Conn)
if ok {
log.Print("ok=true")
state := tlscon.ConnectionState()
for _, v := range state.PeerCertificates {
log.Print(x509.MarshalPKIXPublicKey(v.PublicKey))
}
go handleClient(conn)
} else {
log.Print("reject connection")
}
}

Client端

1
2
3
4
5
6
7
8
9
10
cert, err := tls.LoadX509KeyPair("certs/client.pem", "certs/client.key")
if err != nil {
log.Fatalf("server: loadkeys: %s", err)
}
config := tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true}
conn, err := tls.Dial("tcp", "127.0.0.1:8000", &config)
if err != nil {
log.Fatalf("client: dial: %s", err)
}
clientFunc(conn)

至於Certificate是用Openssl產生的,於是產生第二個問題

Websocket

這個坑實在死的不明不白
Server端原先這樣寫

1
2
3
4
5
6
7
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
log.Print("New websocket arrive")
serveWs(hub, w, r)
})
if err := http.ListenAndServeTLS("localhost:8888", "certs/server.pem", "certs/server.key", nil); err != nil {
log.Fatal("ListenAndServe:", err)
}

而Observer端則是

1
ws = new WebSocket("wss://127.0.0.1:8888/ws")

看起來沒什麼問題,不過不然,目前的websocket不能接受 Self hosted certificate,然後就怎麼連都連不上了
為了省事,直接用wshttp傳輸了

Aggregate Json

我想要做的功能是假設
Client A傳送

1
[{Name: 'aaa', Age: 18}, {Name: 'bbb', Age: 81}]

Client B傳送`

1
[{Name: 'ccc', Age: 28}, {Name: 'bbb', Age: 82}]

Oberver會看到

1
[{Name: 'aaa', Age: 18}, {Name: 'bbb', Age: 81}, {Name: 'ccc', Age: 28}, {Name: 'bbb', Age: 82}]

我不太熟悉golang對json的操作,於是寫了醜陋無比的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
type keyvalue map[string]string
var keyvalueslice []keyvalue
for _, v := range Map {
var m1 []keyvalue
if err := json.Unmarshal([]byte(v), &m1); err != nil {
log.Print(err)
}
for _, obj := range m1 {
keyvalueslice = append(keyvalueslice, obj)
}
}
b, _ := json.Marshal(keyvalueslice)

實在感到羞恥啊…

React / Webpack

雖然知道React跟Webpack,不過實際拿來用是另外一回事
React還好,因為我沒用到太深的東西,不過webpack.config.js很複雜
要找Websocket Bug的時候,SourceMap還生不出來,只好土法煉鋼

Conclusion

隔行如隔山,不跳脫舒適圈去搞些平時不會搞的東西,什麼時候死都不知道
不過這次經驗真是讓我充滿挫折 Orz

原先的NodeJS要跑Addon,通常都是寫Native C++ Code
不過現在Rust的影響力日漸增加,路也不只一條
在我的Github分別提供了兩種做法

C++

搭配node-gyp使用
程式碼就不列了,寫出其他細節

binding.gyp的寫法如下

1
2
3
4
5
6
7
8
{
"targets": [
{
"target_name": "fib",
"sources": [ "fib.cc" ]
}
]
}

編譯產生出fib.node,它的本質就是shared object

1
$ node-gyp configure build

至於Nodejs的使用方式

1
2
const Fib = require('fib');
console.log(Fib.fib(40));

Rust

Rust的產生端比較簡單,重點在於Cargo.toml
注意crate-type那欄指定dylib,也就是shared object

1
2
3
4
5
6
7
8
9
10

[package]
name = "fib_rs"
version = "0.1.0"
authors = ["hm"]

[lib]
name = "fib"
path = "src/fib.rs"
crate-type = ["dylib"]

編譯產生libfib.so

1
$ cargo build --release

Nodejs的使用方式就比C++版複雜一點,需要node-ffi

1
2
3
4
5
const ffi = require('ffi');
const rust = ffi.Library('libfib', {
fib: ['int', ['int']]
});
console.log(rust.fib(40));

隨著LLVM 3.3版本出來,記錄一下如河編譯,編譯環境是Mint 15。
使用CMake來幫助編譯。

CMake是用來產生個平台(Windows/MacOSX/Linux/FreeBSD等)建置專案的解決方案,在Visual Studio產生Solutions,在Linux產生正統的Makefile。 更多的CMake用法可以在網路找到。

前期作業

先把所有Code下載下來

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm
$ cd llvm/tools
$ svn co http://llvm.org/svn/llvm-project/cfe/trunk clang
$ svn co https://llvm.org/svn/llvm-project/lld/trunk/ lld
$ svn co https://llvm.org/svn/llvm-project/lldb/trunk/ lldb
$ cd clang/tools
$ svn co http://llvm.org/svn/llvm-project/clang-tools-extra/trunk extra
$ cd ../../../projects/
$ svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt
$ svn co https://llvm.org/svn/llvm-project/openmp/trunk/ openmp
$ svn co https://llvm.org/svn/llvm-project/libcxx/trunk/ libcxx
$ svn co https://llvm.org/svn/llvm-project/libcxxabi/trunk/ libcxxabi
$ svn co https://llvm.org/svn/llvm-project/libunwind/trunk/ libunwind
$ cd ..

Build Swig

編譯

建立一個build目錄放置CMake的設定黨,由於CMake支援Out-of-source Build技術,原先的目錄可以不遭受污染,就算失敗就只要砍掉build的目錄即可。我們使用Out-of-source Build的方式來建置Makefile。

1
2
3
4
$ mkdir build && cd build
$ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr/local ..
$ make -j 4
$ make install

這裡的cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr/local 等價於傳統的 ./configure --prefix=/usr/local/方式,預設的LLVM跟Clang是使用Debug Mode,這裡使用Release Mode。

看了知乎這篇
之後才知道inline博大精深

先看程式碼

1
2
3
4
5
inline void f(void) { }
int main()
{
f();
}

用g++編譯這段程式,不管下-O2或是-O0都編譯的過
而如果用gcc編譯的話,-O0會出現unrefernce error,但如果下-std=gnu89又沒問題

根據知乎上面寫的,正確的修復方式應該為

1
extern void f(void) {}

C++跟C99之後,語意分歧點越來越多啊

Rollback changelist是很常做的事情,不過在git這種分散式控制系統就有點麻煩了
假設我們現在有這樣的Commit history

1
2
3
4
5
$ git log --oneline
e922d2b (HEAD -> master) ver 4
1b01602 ver 3
10dd293 ver 2
72d57f9 ver 1

而我們現在要退回ver 2該怎麼做

危險的作法

直接開大絕

1
2
$ git reset 10dd293 --hard
$ git push origin master

然而自己一個人玩玩還行,團體行動絕對沒有人建議這麼做

安全的作法

1
2
3
4
5
6
7
8
$ git checkout 10dd293 -b v2 # 切換新分支
$ git merge -s ours master
$ git log --oneline
ce6c1ea (HEAD -> v2) Merge branch 'master' into v2
e922d2b (master) ver 4
1b01602 ver 3
10dd293 ver 2
72d57f9 ver 1

可以看見HEAD指向v2的new commit了,比較一下

1
$ git diff HEAD..10dd293

然後可以把v2推向remote了

1
2
3
$ git push origin master
$ git push origin v2:master
$ git push origin HEAD:master

三者等價

Reference

安全地回滚远程分支