0%

程式語言的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

安全地回滚远程分支

再不寫點東西,這邊就長草了
這幾天在看go-redis專案,顧名思義就是在golang當中對redis操作的程式庫

其中有一段程式碼是這樣

1
2
3
4
5
6
func ExampleClient() {
err := client.Set("key", "value", 0).Err()
if err != nil {
panic(err)
}
}

結果去redis.go裡面查看,找不到Set這個函數的實作
只好用grep去找哪邊可能實作這個函數
最後讓我在command.go找到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func (c *cmdable) Set(key string, value interface{}, expiration time.Duration) *StatusCmd {
args := make([]interface{}, 3, 4)
args[0] = "set"
args[1] = key
args[2] = value
if expiration > 0 {
if usePrecise(expiration) {
args = append(args, "px", formatMs(expiration))
} else {
args = append(args, "ex", formatSec(expiration))
}
}
cmd := NewStatusCmd(args...)
c.process(cmd)
return cmd
}

在同一個檔案中找到cmdable的定義

1
2
3
type cmdable struct {
process func(cmd Cmder) error
}

回頭看我們的redis.go,發現一樣的東西

1
2
3
4
type baseClient struct {
// Ignore unrelated fields
process func(Cmder) error
}

因為有同樣的Singature,所以可以把baseClient當cmder`來用

所以?

雖然找到了我想要的答案,不過我不喜歡這方法
由於我找不到Set這函數,於是我需要grep找到可能的實作 => 發現baseClient和cmadble的相似處
那為什麼不直接用繼承關係就好了,這樣可以找到相依性

1
2
3
4
5
6
struct ICmdable {
virtual std::error process(...) = 0;
};
struct baseClient : ICmdable {
std::error process(...) override;
};

這樣可以看出baseClient必須繼承’ICmdable’這個介面
不過可能引申出多重繼承的問題,老話一句,沒有什麼方法一體適用

現在在科技業沒講個深度學習會被翻白眼,Prototype是一回事,放進Product又是另一回事
之前一般來說通用信的選擇是Tensorflow,雖說Prototype跟Product可以一起完成
不過C++那端難寫就算了,Python那邊也麻煩的要死,沒太多精力搞這個
後來看到PyTorchOnnx,以及Caffe2改變了想法
用易學易用的PyTorch建構出Onnx Mdoel,透過Onnx轉換成Caffe2 Model,加上對終端最佳化的Caffe2 Library
變成另外一種可行的解法
我自己的實驗結果就放在 GitHub
其中C++ Demo的部分是從Caffe2 Android Example那邊學來的
雖然看起來不多,不過真正讓他可以動倒是花了不少時間