Go - String, Rune, Byte

str := "str"
fmt.Printf("%T \n", str)
fmt.Println([]rune(str))
fmt.Println([]byte(str))

// string 
// [115 116 114]
// [115 116 114]

初學 Go 的時候,不太了解runebyte之間的區別,一個string既是rune構成的,又是byte構成的,上頭的程式碼 []rune[]byte 輸出的結果相同。那麼兩者差別在哪呢?下面就透過程式碼做一些簡介。

原始碼的第88行和第92行告訴我們:byte是 uint8,等同一個byte也就是八個bit,rune 則是 int32,等同四個byte也就是三十二個bit。上面的英文程式碼輸出沒有差,下面換成中文來試試看:

chinese := "哈囉你好"
fmt.Println([]rune(chinese))
fmt.Println([]byte(chinese))

// [21704 22217 20320 22909]
// [229 147 136 229 155 137 228 189 160 229 165 189]

[]rune[]byte 輸出的差異在於中文一個字需要佔用三個byte。四個中文字是四個rune,等同十二個byte。一個rune可以是英文字母、中文字、特殊符號,但byte永遠就是一個byte。

chinese := "哈囉你好"
fmt.Println(chinese[:2])
fmt.Println(chinese[:3])

// �
// 哈

上面程式碼的第一個 Println 只取了哈囉你好的前兩個byte,然而中文字是三個byte所構成,因此印出來會是奇怪的亂碼。使用 byte處理 UTF-8 不同語言的字串十分麻煩,這就是為什麼 rune 會誕生。 下方有兩個不同的for 迴圈:

for _, char := range chinese {
	fmt.Printf("%T %v %s \n", char, char, string(char))
}

// int32 21704 哈
// int32 22217 囉
// int32 20320 你
// int32 22909 好
for i := 0; i < len(chinese); i++ {
	fmt.Printf("%T %v %s \n", chinese[i], chinese[i], string(chinese[i]))
}

// Output: 
// uint8 229 å 
// uint8 147  
// uint8 136  
// uint8 229 å 
// uint8 155 
// int8 137  
// uint8 228 ä 
// uint8 189 ½ 
// uint8 160   
// uint8 229 å 
// uint8 165 ¥ 
// uint8 189 ½ 

第一個迴圈使用range,一次會取出一個rune,也就是int32,用string函式將其轉為字串後會是中文字。第二個迴圈則是用index來取,一次會取出一個byte。

Go - Two ways to reverse bits

When input is 32 bits unsigned integer, there are two ways to reverse bits.

Solution 1

func reverseBits(num uint32) uint32 {
    return bits.Reverse32(num)
}

Solution 2

func reverseBits(num uint32) uint32 {
    var result uint32   
    for i := 0; i < 32; i++ {
        result += num >> i << 31 >> i
    }
    return result
}

Solana Program & Account

Sealevel

在官方文件中,對 Sealevel 的介紹為: Parallel smart contracts run-time

以太坊的 EVM 與改良後的 EOS WASM 為單執行緒,一次只能有一份合約修改狀態機。然而 Solana 的Sealevel 一次可以處理千萬份合約交易,這是因為 Sealevel 雖然與 EVM 一樣同為 VM,卻不負責執行交易,此外,Solana 使用 BPF 的技術來讓交易在 Validator 的本地硬體執行,而非虛擬機內,加速了處理的效能。

此外,Solana 將合約(指令)與資料存儲分離,以下會針對這部分進行介紹。

Program

主打快速便宜的Solana有著和以太坊截然不同的Programming model,Solana 使用 Rust 作為編程語言,並將智能合約稱為 Program

Solana Program 與 以太坊智能合約不同之處在於:Solana Program 是 stateless

以太坊的 world state 包含了所有以太坊帳戶、他們的 balance 、智能合約、智能合約使用到的資料等。這個機制的缺點在於:當用戶一直增加、新的智能合約被部署, world state 儲存的東西就越來越多,也就是說,如果想要成為一個 full node , 電腦會需要更多的儲存空間。

而在 stateless 的區塊鏈,Program 不需要儲存數據,就只是簡單的指令,數據需要另外儲存在帳戶內。當調用一個Program 內的函數,需要將儲存這個函數所使用到的數據的帳戶地址也傳遞進去。

下列介紹一些 Solana 中的 Native Program。Native Program 類似於 Fabric 中的系統合約或是以太坊的預編譯合約,是作為 validator 的一部分運行,且可能隨著版本升級進行升級

目前支持的 Native Program 有:

  • System Program
    • 創建新帳號
    • 指派 account 給它所屬的 program
    • 轉帳
    • program id :11111111111111111111111111111111
  • Stake Program
    • 管理質押者質押與存放獎勵的帳號
  • Vote Program
    • 管理質押者投票相關資料
  • BPF Loader
    • 部署合約到鏈上
    • 升級鏈上合約
    • 執行鏈上合約
  • Secp256k1 Program
    • 幫助從簽名中還原出公鑰與地址,如同以太坊中的 ecrecover 函數

等等….

Account

下面介紹三種 Solana Account :

Native Account

Solana 原生的帳戶,例如 System、Stake、Vote。

Program Account

用以儲存 executable code。當客戶端和 Solana program 互動,它們會呼叫儲存在 program account 內的代碼。一個 program 可以用一到多個 data account 來儲存 state 。

Data Account

用以儲存 state data 。又分為兩種:

  1. System owned accounts
  2. PDA (Program Derived Address) accounts

account

上圖是與帳戶相關的欄位。每個帳戶都有自己的地址(公鑰)與擁有者。除了擁有者,其他人都沒辦法更改 Data account 儲存的資料,也沒辦法提款(但是可以存錢進去)。

現在假設我們要寫一隻計數器程序。我們需要建立兩個帳戶,一個用來儲存代碼,一個用來儲存計數的變數。如下圖: account

PDA (Program Derived Address) 是什麼?

Solana 將代碼與數據分離讓升級 Program 變得簡單。在Solana 可以在重用數據帳戶的同時,部署新版本的 Program 到相同地址,升級後數據並不會丟失。

然而,Solana也有一個令人尷尬的缺點。當每次要調用程序(Program),想要修改狀態時,都要傳入Data account 地址,且需要相對應的私鑰才能進行修改,增加了開發上的麻煩,這個私鑰應該要存在哪裡呢?直接存在web2 作為環境變數好像有點奇怪,將私鑰存在程序本身、把data附加到程序上,變得類似以太坊是更好的辦法。PDA 由此而生。

PDA ,也就是程序派生帳戶,由一組種子與程序id 透過 findProgramAddres function 生成,如果找到的地址位於 ed2559 曲線上,便透過一個叫做 bump 方法進行跳躍,直到找到一個不位於 ed2559 的地址。這個方法生成的結果具有唯一性。

pda-curve

有了 PDA ,程序可以直接對需要 PDA 的指令進行簽名。

account-matrix


圖片來源

Rent

另外,值得一提的是,在 Solana 建立 accounts 存放資料是要花錢的,這個行為被稱作 Rent。

使用下列指令可以查看存放 15000 bytes 的資料需要多少資金。

solana rent 15000

截圖 2022-11-21 下午7.23.37.png

第三行有一個詞叫做 Rent-exempt,這是什麼意思呢?其實就是:如果該帳號持有超過兩年份的租金,就可以免收費。以上圖為例,建立帳號時存入 0.10529088 SOL,若想廢棄該帳號的使用,將這些錢全部轉出來,該帳號就會自動被銷毀

目前,在 Solana 上建立帳號強制 Rent-exempt。

Take a deep look at Solana

Solana wiki

Solana cookbook

Go - strings

寫到 leetcode 459. Repeated Substring Pattern 時,看了討論區發現 strings.Repeat(s string, count int) 這個好用的函式,所以決定開一篇文用以記錄 Golang strings 裏面值得記起來的函式或用法,方便自己複習。

Repeat

重複傳入的字串指定次數

  package main
  import (
    "fmt"
    "strings"
  )

  func main() {
    fmt.Println("ba" + strings.Repeat("na", 2))
    // banana
  }

Fields

將字串以空格為基準進行切割,回傳字串陣列

  package main
  import (
    "fmt"
    "strings"
  )

  func main() {
	  fmt.Printf("Fields are: %q", strings.Fields("  foo bar  baz   "))
    // Fields are: ["foo" "bar" "baz"]
  }

Go - Convert between string & int & rune

package main

import (
	"fmt"
	"strconv"
)

func main() {
	n := 10
	str := strconv.Itoa(n) // convert to string
	for _, val := range str {
		fmt.Printf("%T  %v\n", val, val)
		fmt.Printf("%T  %v\n", int(val)-'0', int(val)-'0') // convert rune/int32 to int
	}

	n2, err := strconv.Atoi(str) // convert string to int
	if err == nil {
		fmt.Printf("%T  %v\n", n2, n2)
	}
}