Facebook Libra focus on Mempool
วันนี้เรามาศึกษา Libra’s source code กันต่อในส่วนของ Mempool จากความเดิมตอนที่แล้วเรื่อง AdmissionControl Part2 ที่เราค้างเรื่องการ add new transaction เข้าสู่ Mempool ไว้นะครับ ซึ่งการ add new transaction ไปที่ Mempool ก็เพื่อทำการ validation ร่วมกับ transaction ที่มีอยู่แล้วใน Mempool สาเหตุที่ AdmissionControl ไม่สามารถทำการ validation มากไปกว่านี้เพราะว่าโดย design ของ Libra แล้ว AdmissionControl จะรู้เกี่ยวกับ transaction นั้นๆแค่ transaction เดียว ณ เวลาใดๆ ในขณะที่ Mempool จะรู้รายละเอียดของหลายๆ transaction ที่ pending อยู่ ณ เวลาใดๆ โดยมันจะทำหน้าที่คล้ายๆ Ordering queue ที่รอการเรียกข้อมูลภายในตัวมันไปกระทำการ consensus.
เมื่อเปิด code ดูจะรู้ว่าจริงๆแล้ว Mempool เองมี 4 gRPC services ที่ให้บริการอยู่ดังนี้ครับ
- AddTransactionWithValidation
- GetBlock
- CommitTransactions
- HealthCheck
เนื่องจากเรายังมีความสนใจต่อเนื่องจากความเดิมตอนที่แล้วเรื่อง add new transaction เพราะฉะนั้นเราจะไปเริ่มที่ AddTransactionWithValidation นะครับซึ่งคงต้องอ้างถึง code ใน function submit_transaction_inner ของ AdmissionControl ซักหน่อย ในส่วน services อื่นๆคงจะได้กลับมาดูกันต่อไป
มาถึงขั้นตอนนี้ผมสังเกตุเห็นว่า mempool จะไม่ใช้การ debug เหมือน admission control แต่จะใช้การ trace น่าจะเป็นเพราะในทางปฎิบัติ mempool มีความเคลื่อนไหวอยู่เสมอนะครับอาจจะใช้ trace เพื่อประโยชน์ในการดูพฤติกรรมไม่รอช้าทดสอบรัน RUST_LOG=trace cargo run -p libra_swarm -- -s
ซึ่งการ Adding แล้วเกิดการ Removing นั้นเราสามารถเข้าใจได้ว่า mempool เองจะต้องมีตัวจัดการที่มีลักษณะการทำงานที่เป็น background tasks ที่คอยช่วยเรียกตรวจสอบ transaction pending submitted ภายหลังจาก added เข้ามาแล้วไปเป็น committed หรืออื่นๆถ้าพบปัญหา
อย่างไรก็ตาม function add_transaction_with_validation ของ mempool service จะทำการเรียก function add_txn ของ mempool core ซึ่ง function นี้ค่อนข้างยาวแต่มีการทำงานดังนี้ครับ
- ตรวจสอบ balance ว่ามีมากกว่า gas ที่ต้องการใช้สำหรับการ transfer ครั้งนี้ + gas ที่ต้องการของ pending transfer อื่นๆ ของ sender (ว่าแต่ไม่สนใจ coins ที่ต้องการ transfer เหรอ ทำไมสนใจแต่ gas เทียบกับ balance ???)
- ตรวจสอบ sequence number ว่าไม่น้อยกว่า sequence number ล่าสุดในปัจจุบัน
- Add expiration time, node.config.toml, system_transaction_timeout_secs = 86,400 ~ 1,440 minutes ~ 24 hours ~ 1 day (ช้าสุดที่เป็นไปได้ testnet config ไว้ 1 วัน config นี้น่าจะถูกปรับให้เหมาะสมต่อไป เนื่องจาก priority มี gas เข้ามาเกี่ยวข้อง คนที่อยากประหยัด gas อาจจะได้รอนานกว่านี้ครับ)
- insert transaction into mempool ด้วย function insert ของ object transaction store
เนื่องจากผมสนใจขั้นตอนการ insert transaction into mempool ซึ่งเป็นขั้นตอนที่ 4 ครับ เราจึงจะไปที่ขั้นตอนนั้นกัน
จากการอ่าน code function insert ของ object TransactionStore ด้วยตาเปล่าๆ ไม่ใช้ computer run program จะแบ่งการทำงานได้ดังนี้
- Check if transaction นี้มีอยู่แล้วใน mempool ก็ request update แล้ว return status
- Check if mempool is full ถ้า full ก็ return status
- Check if transaction(s) ของ address นี้มีจำนวนมากกว่าพื้นที่สำหรับแต่ละ user (เงื่อนไขนี้น่าจะทดลองดูแฮะ ^^)
- ซึ่งถ้าผ่านการตรวจสอบข้างต้นก็จะ insert เข้าสู่ storage และ indexes อื่นๆ
- เรียก function process_ready_transactions
เอาล่ะคงต้องศึกษาดูหมดทุกกรณีเลยล่ะครับ
เริ่มที่ (1) mempool รู้ได้ยังไงว่ามี transaction นี้อยู่แล้วก่อนดีกว่า ที่ function check_for_update จะเห็นว่ามีการใช้ get_mut เพื่อ get ค่าจาก hashmap transactions ของ TransactionStore ของ mempool เมื่อได้ current_version (transaction) มาแล้วจึงเทียบ gas price กับ txn (new transaction)
txn.gas_price ≤ current.gas_price ถ้าน้อยกว่าหรือเท่ากับ จะถือว่าเป็น InvalidUpdate
txn.gas_price > current.gas_price ถ้ามากกว่าก็จัดการ update current_version ด้วย txn
จุดเริ่มของ function check_for_update กล่าวถึงการเตรียมสร้าง status valid เอาไว้ครับ ว่าแต่ตกลง add transaction ใน mempool มีกี่ status กันนะ เราก็เปิด code ดู enum MempoolAndTransactionStatusCode ได้ความว่า มีทั้งหมด 6 status
- Valid: Transaction พึ่งจะถูกส่งเข้า mempool
- InsufficientBalance: ผู้ส่ง (sender) มี coins ไม่พอสำหรับจะทำ transaction
- InvalidSeqNumber: sequence number เก่าแล้ว
- MempoolIsFull: mempool เต็ม
- TooManyTransactions: account ถึง maximum capacity per account
- InvalidUpdate: Invalid update, ทำได้แค่เพิ่ม gas price
(2) เพื่อตัดสินว่า mempool full หรือไม่ จะเปรียบเทียบระหว่าง 2 ค่า
capacity อ้างอิงไฟล์ node.config.toml = 10,000,000
system_ttl_index เป็น index สำหรับเก็บค่า system expiration time ของแต่ละ transaction
ซึ่งถ้าพบว่า full จะ remove ข้อมูล transactions จาก parking log index. (non-ready txns หรือ transaction ที่ยังไม่สามารถ include ใน block ต่อไป)
อาจจะมีคำถามว่าแล้วเมื่อไหร่ transactions ควรจะเป็น ready/non-ready ขอติดไว้ก่อนนะครับ เดี๋ยวเอาไว้เราไปดูกันตอนขั้นตอน process_ready_transactions ครับ
(3) Check if transaction(s) ของ address นี้มีจำนวนมากกว่าพื้นที่สำหรับแต่ละ user จะเปรียบเทียบระหว่าง 2 ค่า
capacity_per_user อ้างอิงไฟล์ node.config.toml = 100
txns.len() เป็นจำนวนของ transaction ที่มี address เดียวกัน ใช้ get_mut เช่นเคย
ถ้าพื้นที่ไม่เพียงพอก็ทำการ return status code TooManyTransactions
(4) insert เข้าสู่ storage และ indexes อื่นๆ
indexes ก็คือ system_ttl_index, expiration_time_index
storage ก็คือ TransactionStore นั่นเอง โดยใช้วิธีการอ้างถึง txns เลย ซึ่งจะ insert ด้วย sequence_number และ txn
(5) process_ready_transactions
เมื่ออ่านดูก็จะได้คำตอบของคำถามว่า ready transaction คือทุกๆ transactions ของ account ที่มี sequence เรียงต่อจาก current sequence number จะถูกส่งเข้าไปที่ priority index (ordering for consensus) และ timeline index (txns for SharedMempool) นอกจากนั้นถือว่าเป็น Non-Ready transaction ก็จะถูกส่งเข้าไปที่ parking lot index ครับ
เท่านี้เป็นอันจบการศึกษาการทำงานของ function insert ของ object TransactionStore ที่ถูกเรียกมาทุกครั้งเมื่อเราใช้คำสั่ง account mint หรือ transfer
มีเรื่องนึงที่ผ่านตาใน libra code ที่น่าสนใจคือ OP_COUNTER นะครับ จะเห็นว่ามีการใช้งานค่อนข้างเยอะสำหรับ admission control และ mempool ซึ่งหน้าที่ของมันคือใช้ log จำพวก counter ต่างๆนะครับ เราจะอ่าน log ประเภทนี้ได้จาก path tmp folder metrics/file [client_id].metrics กรณีนี้ผมสนใจเฉพาะ mempool ผมใช้วิธีคล้ายๆที่เคยทำคือ
$ tail -f /tmp./tmp[xxxxxx]/metrics/[client_id].metrics | grep mempool
อ่อ ยังเหลืออีกคำถามนึง ว่าแต่แล้วใครกันล่ะที่เรียกใช้ removing transaction from mempool ที่เราเจอใน trace ในช่วงแรก? พบว่าเป็น mempool’s service commit_transactions
ซึ่งผู้เรียกใช้เป็น transaction manager ที่อยู่ในส่วนงาน consensus ครับ ส่วนนี้ก็เป็นส่วนที่น่าสนใจรวมถึง SharedMempool และ gRPC service อื่นๆของ mempool แต่เนื่องจากเวลาหมดอีกแล้ว ไว้รอบหน้าว่ากันใหม่ (ถ้ามีเวลาครับ)
บทความอื่นๆเกี่ยวกับการทำความเข้าใจ source code libra มีดังนี้ครับ
Facebook Libra on Windows is easy
Facebook Libra focus on account create
Facebook Libra focus on account mint
Facebook Libra focus on query balance
Facebook Libra focus on admission control (Part 1)
Facebook Libra focus on admission control (Part 2)
Facebook Libra focus on consensus (Part 1)