หลังจากที่เราเข้าใจ concept ภาพรวมกันไปแล้วใน ลองเขียน Code เพื่อสร้าง Transaction ใน web3 Tester provider คราวนี้เราจะมาลองส่งคำสั่งเข้า Blockchain ของจริงๆแล้วครับ
แต่อย่าพึ่งตกใจ ว่าต้องเสียเงินกับค่า Gas จริงๆ เพราะวันนี้ ที่จะให้ทดสอบก็คือ Testnet ซึ่งเราจะไปขอ Ethereum มาเป็นค่า gas ได้ฟรี แต่การกระทำอะไรต่างๆ ก็ค่อนข้างจะเหมือน Mainnet ที่เป็นของจริงเลย เป็นสถานที่เพื่อใช้ซ้อมมือของเรานั่นเองครับ (testnet คือ blockchain จริงๆ มีการรันทุกอย่างเหมือน mainnet แต่อาจจะไม่ได้สมบูรณ์เท่า mainnet เท่านั้น)
เปิด Google colab
เช่นเดิม เรายังใช้ Google colab กันอยู่ และติดตั้ง package แบบเดิมเลย ก็คือ
!pip install web3
แต่เราจะไม่ติดตั้ง Tester provider แล้ว ข้ามไปที่ package เพื่อซ่อม jsonschema แทนเลย ก็คือ
!pip install --force-reinstall jsonschema==3.2.0
หลังจาก execute แล้วก็กด RESTART RUNTIME (ตามที่ message log บอกเอาไว้ได้เลย)
สมัครใช้งาน Node
ขอยกภาพนี้มาอีกที เพื่อให้เห็นภาพ
จะเห็นได้ว่า Ethereum node คือตัวที่รับคำสั่ง ผ่านทาง protocol ใด ไม่ว่าจะเป็น HTTP, IPC, Websocket โดยมี web3 เป็นสะพานเชื่อมให้อยู่
จริงๆแล้ว การใช้งานใดๆที่เราต้องเรียก service ใน Blockchain นั้น เราจำเป็นต้องมี API Key ซึ่งเราจะต้องตั้ง Node ขึ้นมา เพื่อสร้าง API key ให้รันคำสั่งต่างๆตามที่เราต้องการได้ แต่ว่ามันใช้ทั้งเงินและเวลาในการตั้งไม่น้อยเลย ดังนั้นในกรณีที่เราทดสอบเล็กๆน้อยๆ มันก็ยิ่งไม่คุ้มมากขึ้นไปอีก ทางเลือกที่ดีกว่า ก็คือการที่เราไปใช้งาน share กับคนอื่น โดยสมัครใช้ที่เค้ามีให้บริการ Node as a service เป็นการเฉพาะเลย ถ้าเอาที่ผมใช้งานอยู่ ซึ่งมี free tier ให้ใช้งานฟรี และถ้าเอาไปใช้งาน production ที่มีการใช้งานมากขึ้น ผมดูมาแล้ว ว่าที่นี่ก็ค่อนข้างราคาถูกเลย เมื่อเทียบกับที่อื่น และในบทความนี้ ก็ใช้งานที่นี่ด้วยครับ ก็คือ https://alchemyapi.io
หลังจากที่สมัครแล้ว ให้เลือกสร้าง node แรกขึ้นมา โดยเลือก Network ชื่อ “Ropsten” ใน Ethereum network ได้เลย หรือว่าใครที่เลือกแล้วผิด เมื่อเข้ามาถึงหน้า dashboard ก็สามารถ create new ได้ โดยเลือกตั้งค่าแบบนี้ครับ
POC2 จะตั้งเป็นอะไรก็ได้ แล้วแต่สะดวกเลย ไม่มีผลอะไรกับโค้ดครับ เมื่อสร้างแล้ว เราก็จะได้ แบบนี้ออกมา
ในบทความนี้เราจะใช้ Protocol HTTP กันครับ
เชื่อมต่อ Network
เหมือนครั้งก่อน แต่คราวนี้เราจะใช้ network จริงๆกันแล้ว แม้ว่าเป็น Testnet แต่ก็เหมือนจริงอย่าง mainnet เลยล่ะ
from web3 import Web3
node_url = '{HTTP_URL_YOU_GET_FROM_ALCHEMY}'
w3 = Web3(Web3.HTTPProvider(node_url))
w3.isConnected()
บรรทัดแรก ก็คือการ load library ขึ้นมา บรรทัดต่อมาคือการตั้ง URL {HTTP_URL_YOU_GET_FROM_ALCHEMY} ซึ่ง url ที่ได้มาจาก HTTP ของ Alchemyapi ข้างบนนั่นเอง บรรทัดที่สาม คือการ Initial Connection แล้วเก็บไว้ใน w3 บรรทัดที่สี่ ก็คือตรวจสอบว่า ได้ Connect แล้วจริงหรือไม่ ถ้า Connect แล้วจะ return True
ขอเติม ETH
ถ้าเราจะทำธุรกรรมอะไรได้ เราก็ต้องมี ETH ก่อน ซึ่ง Testnet นั้น เราไม่สามารถเอาเงินจริงไปซื้อ ETH มาได้ และกลับกัน ถ้าเราได้ ETH มาแล้ว เราก็ไม่สามารถไปแปลงเป็นเงินจริงได้เช่นกัน เพราะมันคือ Testnet ดังนั้น ถ้าเราต้องทำธุรกรรม เราก็ต้องมี Gas (ซึ่งก็คือ ETH) แล้วเราจะหามาจากไหนล่ะ? เรื่องนี้ Faucet คือคำตอบ
Faucet คือบริการที่ให้เราไปกดเพื่อขอ ETH มาฟรีได้ หลักการก็คือ เอา Address เราไปกรอก แล้วกดรับมาได้ฟรีเลย
แต่ละ network จะมี web สำหรับเติม ETH ที่แตกต่างกัน สำหรับ Ropsten นั้นขอเติมได้ที่ https://faucet.egorfine.com/ ถ้าไม่ได้ลอง Google ropsten faucet ดูก็ได้นะครับ เพราะบางครั้งเค้าก็หมด หรือว่าบางครั้งเว็บ error ใช้งานไม่ได้ก็เจอได้เหมือนกัน เมื่อเราเปิดเว็บแล้ว ก็เอา Address เราใส่เข้าไปเลย ซึ่งผมจะใช้ Address ที่ได้มาจาก บทความที่เราสร้าง HD Wallet กันไว้ (ลองเขียน Code เพื่อสร้าง Wallet ตาม BIP32 BIP39 BIP44) เพราะมันต้องใช้ private key ด้วย ในการทำธุรกรรมด้วย
แม้ว่าได้ไม่เยอะ (0.3ETH) แต่ว่าจริงๆแล้ว ก็ไม่ต้องมีเยอะเลย เพราะหลักๆเราจะเน้น ที่ทดสอบโอน หรือว่าจ่ายค่า Gas เท่านั้น อย่าลืมว่า เราโอน 1 wei ก็ได้นะ ดังนั้น โอนกันได้เป็นล้านครั้งเลยล่ะ (แต่ว่าค่า Gas ก็กระจุยเหมือนกันนะ)
ได้มาแล้ว 0.3 ETH ต้องรอแป้บนึงนะ
Check Balance
ใน Web3 ก็มีคำสั่งให้เราสามารถเช็ค balance ได้ง่ายๆ ดังนี้เลย
my_address = '0x14Ca6e7EeA0Fa533986BC01501EFf9393AB50780'
to_address = '0x34652b56ab34E1aDE3FD6106dD6098E61597B72D'
w3.eth.get_balance(my_address)
บรรทัดหนึ่ง และ สอง คือการ ตั้งค่า wallet address ต้นทางปลายทาง บรรทัดที่สาม ก็คือ การตรวจสอบ Balance ของ wallet ต้นทางอย่างเดียว ซึ่งจะได้
300000000000000000
อย่าลืมว่าหน่วยเป็น Wei ดังนั้น ก็คือ 0.3 ether นั่นเอง (ถ้าอยากแปลงหน่วย ลองย้อนไปดูบทความก่อนหน้านี้ได้ครับ)
สร้างคำสั่งโอน
เรามาสร้างคำสั่งที่ใช้โอนกัน โดยเราจะใช้ python dict เป็นหลักในการตั้งค่าครับ ดังนี้
transaction = {
'to': to_address,
'value': w3.toWei(0.1, 'ether'),
'nonce': w3.eth.getTransactionCount(my_address),
'gas': 4600000,
'gasPrice': w3.toWei("30", "gwei"),
}
หมายความว่า เราจะโอนไปที่ to_address ที่เราได้ตั้งค่าเอาไว้ที่ข้างบนแล้ว โดยเลขที่เราต้องการโอนก็คือ 0.1 ether (และต้องแปลงกลับให้เป็น wei เสมอ) และ nonce คือค่า nonce ล่าสุด (ซึ่งจริงๆแล้ว คำสั่งนี้คือ transaction แรก มันจะเป็น 0 ครับ แต่ผมเขียนให้มัน dynamic ไปตามค่าที่เก็บใน Blockchain เลย ส่วน gas ก็คือ gas limit ที่เรากำหนดว่า ใช้ยังไงก็ตาม ห้ามใช้เกินนี้ โดยเป็นหน่วย wei เช่นเดียวกัน (อย่างที่เคยบอก ว่าถ้าใช้ไม่หมด มันจะคืนส่วนที่ไม่ได้ใช้กลับมา) ส่วน gasPrice นั้น ก็คือ กำหนดให้เป็น 30 gwei (แต่แปลงกลับให้เป็นหน่วย wei อย่างที่เคยเล่าไปแล้ว ว่าการทำงาน จะเป็นหน่วย wei เสมอ
Sign transaction
สิ่งนี้สำคัญมาก เพราะว่าเราต้อง sign transaction ลงไปด้วย เนื่องจากการทำงานของ Blockchain จำเป็นต้องตรวจสอบว่าเราเป็นคนที่สั่งทำธุรกรรมนั้นๆจริงๆ ดังนั้นจึงต้อง Sign ลงไป ดังนี้
key = '0x25dc5ad91c59f8bb28a6b2a33039a13cf8decbe5c5d7595b230525da5d1e7179'
signed = w3.eth.account.sign_transaction(transaction, key)
print(signed)
key นั้นก็คือ private_key ของกระเป๋าต้นทางที่สั่งโอนนั่นเองครับ ลองย้อนกลับไปดูบทความที่สร้างกระเป๋าดูได้ จะเห็นได้ว่า มันคือ key ที่อยู่ในบทความนั่นล่ะครับ (เปิดเผยขนาดนี้ ไม่นาน ether ใน wallet ปลิวแน่นอน แต่ไม่เป็นไรครับ มันเป็น Testnet) โดยหลังจากที่ sign แล้วจะเก็บใน signed แล้วผมก็สั่งให้ print ออกมาดู เราจะเห็นว่ามี HexBytes, r, s, v เพิ่มขึ้นมาหลังจากการ sign ซึ่งเราจะไม่อธิบายเดี๋ยวจะงงเปล่าๆ แต่นี่คือ message ที่ sign แล้ว พร้อมส่งไปเข้า blockchain แล้ว
ส่งธุรกรรมเข้า Blockchain
นี่คือ Highlight อีกแล้ว ก็คือการส่งธุรกรรมที่เราต้องการทำ เข้าไปใน Blockchain network เพื่อให้ดำเนินการอย่างที่เราตั้งใจ รันโค้ดตามนี้
tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction)
แค่นี้ครับ เราจะได้ TX ID เก็บใน tx_hash ซึ่งเราสามารถเรียก status ว่า mine แล้วหรือยังมาดูได้จาก
w3.eth.get_transaction_receipt(tx_hash)
สำหรับผลลัพท์ ถ้าได้ error TransactionNotFound
ไม่ต้องตกใจ ไม่ใช่โค้ด error แต่เป็นเพราะว่า transaction ยังไม่ถูก mine เท่านั้นเองครับ แค่นั้นเลย แต่ถ้า mine แล้วจะได้ผลลัพธ์ประมาณนี้
AttributeDict({'blockHash': HexBytes('0x5920f6596f97c51e7857be8dfe4d0f23e5b1d2897a807dfad4eb21c15127c660'),
'blockNumber': 12081390,
'contractAddress': None,
'cumulativeGasUsed': 2491262,
'effectiveGasPrice': 30000000000,
'from': '0x14Ca6e7EeA0Fa533986BC01501EFf9393AB50780',
'gasUsed': 21000,
'logs': [],
'logsBloom': HexBytes('0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'),
'status': 1,
'to': '0x34652b56ab34E1aDE3FD6106dD6098E61597B72D',
'transactionHash': HexBytes('0x6fb9738b7805df65828411cbaf70f732bb5f582a78495ed5fd86a7b7c97dbec3'),
'transactionIndex': 4,
'type': '0x0'})
หรือว่าเราจะเรียก detail ของ tx_hash ขึ้นมาดูก็ได้ ทั้งก่อนและหลัง mine ด้วย
w3.eth.get_transaction(tx_hash)
ซึ่งจะได้ผลลัพท์ก่อน mine ประมาณนี้
AttributeDict({'blockHash': None,
'blockNumber': None,
'from': '0x14Ca6e7EeA0Fa533986BC01501EFf9393AB50780',
'gas': 21000,
'gasPrice': 30000000000,
'hash': HexBytes('0x6fb9738b7805df65828411cbaf70f732bb5f582a78495ed5fd86a7b7c97dbec3'),
'input': '0x',
'nonce': 1,
'r': HexBytes('0x0ebfaa6f499fb77ccceb44c17fd878c35a3a8ab4abb0bbfe370506ce68e350a3'),
's': HexBytes('0x6634fdff0585d85d5d9ac676d93daffa52bf9ab7c0494fe8c1be0939426fc01c'),
'to': '0x34652b56ab34E1aDE3FD6106dD6098E61597B72D',
'transactionIndex': None,
'type': '0x0',
'v': 27,
'value': 100000000000000000})
และผลลัพท์หลัง mine ดังนี้
AttributeDict({'blockHash': HexBytes('0x5920f6596f97c51e7857be8dfe4d0f23e5b1d2897a807dfad4eb21c15127c660'),
'blockNumber': 12081390,
'from': '0x14Ca6e7EeA0Fa533986BC01501EFf9393AB50780',
'gas': 21000,
'gasPrice': 30000000000,
'hash': HexBytes('0x6fb9738b7805df65828411cbaf70f732bb5f582a78495ed5fd86a7b7c97dbec3'),
'input': '0x',
'nonce': 1,
'r': HexBytes('0x0ebfaa6f499fb77ccceb44c17fd878c35a3a8ab4abb0bbfe370506ce68e350a3'),
's': HexBytes('0x6634fdff0585d85d5d9ac676d93daffa52bf9ab7c0494fe8c1be0939426fc01c'),
'to': '0x34652b56ab34E1aDE3FD6106dD6098E61597B72D',
'transactionIndex': 4,
'type': '0x0',
'v': 27,
'value': 100000000000000000})
ข้อสังเกตุก็คือ blockNumber ต้องมีตัวเลข แปลว่ามันทำงานแล้ว และบันทึกอยู่ใน blockNumber นั้น
เพื่อความถูกต้องเราก็จะตรวจสอบว่าเงินเรามีเหลือเท่าไรด้วย
Web3.fromWei(w3.eth.get_balance(my_address), 'ether')
คือการตรวจสอบ balance ของเรา พร้อมทั้งแปลงให้เป็นหน่วย ether ในคราวเดียว และแน่นอนอย่าลืมตรวจสอบของปลายทางด้วย ว่าได้รับไปแล้วจริง
Web3.fromWei(w3.eth.get_balance(to_address), 'ether')
ตรวจสอบธุรกรรมด้วย Blockchain Explorer
ใน Testnet ก็มี blockchain explorer ด้วยเช่นกัน แต่ว่าเราต้องเข้าให้ถูกเท่านั้นครับ สำหรับ Ropsten network สามารถดูได้จาก https://ropsten.etherscan.io/ ด้วยการเอา tx_hash ที่เราได้รับไปค้น เช่น 0x910b7d5c5ad3cd6f49aafaa9370a4c8bea5e6198866d28fcd4c18459e745b3d6 หรือเปิด URL ตรงๆคือ https://ropsten.etherscan.io/tx/0x5920f6596f97c51e7857be8dfe4d0f23e5b1d2897a807dfad4eb21c15127c660 นั่นเอง
จบแล้วครับ สำหรับการสร้างและส่งธุรกรรม อันนี้คือธุรกรรมการโอน Native token (coin) แต่สำหรับคนที่ call contract (เช่นการโอน token หรือ ติดต่อกับ smart contract ตรง) ก็จะแตกต่างกันในการกำหนดข้อมูลในขั้นตอน สร้างคำสั่งโอน ที่จะยุ่งยากมากขึ้น และเตรียมค่าต่างๆมากขึ้น แต่หลังจากนั้นก็จะทำเหมือนกันหมดเลย
อ้อ เช่นเคย กับไฟล์ notebook python สามารถ download ได้ที่นี่ โดยไฟล์นี้จะเอาไปรันเลยไม่ได้นะครับ ต้องเปลี่ยนแปลงค่าต่างๆ ตามที่ตัวเองใช้งานด้วย หลักๆ คือ URL ที่ได้จาก alchemyapi , wallet address ทั้งสองอัน , private key ของกระเป๋าที่สั่งโอน
ขอให้สนุกกับการต่อยอดครับ