# Ethereum transaction ### 一般交易由以下幾個部分組成 ```javascript= From To Amount Fee Note ``` ### Ethereum 的交易由以下幾個部份組成 ```javascript= nonce From To Amount gasPrice gasUsed/gasLimit Note ``` ### 針對其中幾個部分進行說明: #### nonce: prevent double spending[[1: what is nonce]](https://kb.myetherwallet.com/en/transactions/what-is-nonce/) 可以透過API得到,也可以透過自己錢包的交易數量推得。 #### gasPrice:每單位 Gas 願意付出多少 ETH,一般使用 Gwei(1e9 to wei) 作為單位。gasPrice * gasUsed 即為此次交易的手續費 Fee。 #### gasUsed:此次交易使用的 gas 數量。 #### gasLimit: 你願意花費在交易上的最大數量的 Gas 單位。 ### 進行ETH交易 1. 取得 chainId, chainId是取決於Ethereum交易的網路 | ropsten | rinkeby | mordor | Eth mainnet | Eth based mainnet | |:-------:|:-------:|:------:|:-----------:|:-----------------:| | 3 | 4 | 63 | 1 | 61 | 2. 將上述 ETH 交易內容進行 rlp 編碼的格式排列 ```javascript= class Transaction{ int nonce; BigInt gasPrice; int gasLimit; String to; BigInt amount; String note; Transaction(this.nonce, this.gasPrice, this.gasLimit, this.to, this.amount, this.note); } var tx = Transaction(0, 49 * 1e9 ,21000, '0x15b1a87b648384033fdca1b7656cb534b91ffe56', 0.002 * 1e18, 'This is a transaction on ropsten'); var v = chainId; var r = BigInt.zero; var s = BigInt.zero; var rlpData = [nonce, gasPrice, gasLimit, to, amount, note, v, r, s]; ``` 3. 將rlpData 使用rlpEncode function進行rlp 編碼,得到 encodeRlpData,再進行rlp 編碼之前會將 rlpData裡面的各元素根據其型別轉為Buffer,可參考 toBuffer function。 ```javascript= Uint8List toBuffer(dynamic data) { if (data is Uint8List) return data; if (data is String) { if (isHexString(data)) { return Uint8List.fromList(hex.decode(padToEven(stripHexPrefix(data)))); } else { return Uint8List.fromList(utf8.encode(data)); } } else if (data is int) { return Uint8List.fromList(intToBuffer(data)); } else if (data is BigInt) { return Uint8List.fromList(encodeBigInt(data)); } else if (data is List<int>) { return Uint8List.fromList(data); } throw TypeError(); } ``` 4. var hash = keccak256(encodeRlpData) 5. [r,s,v] = ECDSA(hash) 6. 最後將簽名後得到的r,s,v 替換上述的rlpData,再將進行rlp encode後得到的Buffer轉為HEX,加上前綴{0x}, 即為可用於Ethereum HEX。 ### RLP encode: rlp 是 recursive length prefix 的縮寫,目的是用於對巢狀的陣列進行編碼[[eth wiki]](https://eth.wiki/fundamentals/rlp)[(中文)RLP 編碼規則](https://www.itread01.com/content/1541817991.html)。 規則一: 對於值在[0, 127]之間(因為基於ASCII標準表,所以超過128就處理不了啦)的單個字元,其編碼是其ASCII編碼。 比如,單個字元‘0’在ASCII標準表裡是48(十六進位制是0x30),所以在RLP這裡也是48。 規則二: 如果字串長度是0-55個位元組,那麼在前面加上(128+字串長度)作為字首。 比如,空字串編碼就是128,即128 = 128 + 0; “0”這個字串只有1個字元,編碼就是12948; “0123”在ASCII標準表是48495051,長度是4個位元組,字首為132,那麼RLP 轉換後就是13248495051。 規則三: 如果字串長度大於55, 那麼在前面加上(183+字串長度的二進位制編碼的長度)和(字串長度)做字首。(為什麼是183?因為在規則二里,最大就是183(128+55)。) 規則四: 如果列表中所有字串的總長度按規則1到3編碼後,小於等於55,那麼在前面加上(192+該長度)做字首。列表裡的字串編碼參照規則1到3(183和192之間差了9個位元組,9個位元組用來表示長度。 規則五: 如果列表中所有字串的總長度按規則1到3編碼後,超過55,那麼在前面加上(247+該長度的二進位制編碼的長度)和(該長度)做字首。列表裡的字串編碼參照規則1到3(為什麼是247?因為192+55=247)。 ```javascript= Uint8List encode(dynamic input) { if (input is List && !(input is Uint8List)) { final output = <Uint8List>[]; for (var data in input) { output.add(encode(data)); } final data = _concat(output); return _concat([encodeLength(data.length, 192), data]); } else { final data = toBuffer(input); // 對於值在[0x00,0x7f]範圍內的單個字節, if (data.length == 1 && data[0] < 128) { return data; } else { return _concat([encodeLength(data.length, 128), data]); } } } Uint8List encodeLength(int length, int offset) { if (length < 56) { return Uint8List.fromList([length + offset]); } else { final String hexLen = _intToHex(length); final int lLength = hexLen.length ~/ 2; return _concat([ Uint8List.fromList([offset + 55 + lLength]), Uint8List.fromList(hex.decode(hexLen)) ]); } } Uint8List _concat(List<Uint8List> lists) { final list = <int>[]; lists.forEach(list.addAll); return Uint8List.fromList(list); } ```