Facebook Libra focus on Mempool

Apisak Srihamat
4 min readJul 30, 2019

--

วันนี้เรามาศึกษา 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 ที่ให้บริการอยู่ดังนี้ครับ

  1. AddTransactionWithValidation
  2. GetBlock
  3. CommitTransactions
  4. HealthCheck

เนื่องจากเรายังมีความสนใจต่อเนื่องจากความเดิมตอนที่แล้วเรื่อง add new transaction เพราะฉะนั้นเราจะไปเริ่มที่ AddTransactionWithValidation นะครับซึ่งคงต้องอ้างถึง code ใน function submit_transaction_inner ของ AdmissionControl ซักหน่อย ในส่วน services อื่นๆคงจะได้กลับมาดูกันต่อไป

ใน function submit_transaction_inner ของ AdmissionControl มีการเรียก get_account_state ของ validator ซึ่งก็จะไปเรียกใช้ update_to_latest_ledger_async ผ่าน storage_read_client ซึ่งเรารู้แล้วในบทความ AdmissionControl Part 1 ก่อนหน้านี้เพื่อนำข้อมูล balance และ sequence number มารวมกับ signed_txn และ gas_cost เพื่อสร้าง object AddTransactionWithValidationRequest ส่งไป function add_txn_to_mempool
function add_trx_to_mempool ของ admission control จะเห็นว่า add โดยไม่มี error ก็จะถือว่าเสร็จงานเรียบร้อยแล้วครับ
function add_transaction_with_validation ของ mempool service

มาถึงขั้นตอนนี้ผมสังเกตุเห็นว่า mempool จะไม่ใช้การ debug เหมือน admission control แต่จะใช้การ trace น่าจะเป็นเพราะในทางปฎิบัติ mempool มีความเคลื่อนไหวอยู่เสมอนะครับอาจจะใช้ trace เพื่อประโยชน์ในการดูพฤติกรรมไม่รอช้าทดสอบรัน RUST_LOG=trace cargo run -p libra_swarm -- -s

logs แสดงให้เห็นชัดเจนว่ามีการ SyncEvent, commit_transaction และ get_block อยู่ตลอดเวลา (แม้ผมจะไม่ได้ทำอะไรเลยที่ prompt Libra% ก็ตาม)
ทดสอบ Account Mint 1,000 coins
ผลลัพธ์ account mint 1,000 coins จะเห็นว่ามีการ adding transaction to mempool โดย address ของ sender = 0 หลังจากนั้นไม่นานก็เกิดการ removing transaction from mempool
ทดสอบ Transfer จาก account#0 ไปที่ account#1 จำนวน 800 coins
ผลลัพธ์ Transfer จาก account#0 ไปที่ account#1 จำนวน 800 coins จะเห็นว่ามีการ adding transaction to mempool โดย address ของ sender = address ของ account#0 หลังจากนั้นไม่นานก็เกิดการ removing transaction from mempool

ซึ่งการ Adding แล้วเกิดการ Removing นั้นเราสามารถเข้าใจได้ว่า mempool เองจะต้องมีตัวจัดการที่มีลักษณะการทำงานที่เป็น background tasks ที่คอยช่วยเรียกตรวจสอบ transaction pending submitted ภายหลังจาก added เข้ามาแล้วไปเป็น committed หรืออื่นๆถ้าพบปัญหา

อย่างไรก็ตาม function add_transaction_with_validation ของ mempool service จะทำการเรียก function add_txn ของ mempool core ซึ่ง function นี้ค่อนข้างยาวแต่มีการทำงานดังนี้ครับ

  1. ตรวจสอบ balance ว่ามีมากกว่า gas ที่ต้องการใช้สำหรับการ transfer ครั้งนี้ + gas ที่ต้องการของ pending transfer อื่นๆ ของ sender (ว่าแต่ไม่สนใจ coins ที่ต้องการ transfer เหรอ ทำไมสนใจแต่ gas เทียบกับ balance ???)
  2. ตรวจสอบ sequence number ว่าไม่น้อยกว่า sequence number ล่าสุดในปัจจุบัน
  3. 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 อาจจะได้รอนานกว่านี้ครับ)
  4. insert transaction into mempool ด้วย function insert ของ object transaction store

เนื่องจากผมสนใจขั้นตอนการ insert transaction into mempool ซึ่งเป็นขั้นตอนที่ 4 ครับ เราจึงจะไปที่ขั้นตอนนั้นกัน

function insert ของ object TransactionStore

จากการอ่าน code function insert ของ object TransactionStore ด้วยตาเปล่าๆ ไม่ใช้ computer run program จะแบ่งการทำงานได้ดังนี้

  1. Check if transaction นี้มีอยู่แล้วใน mempool ก็ request update แล้ว return status
  2. Check if mempool is full ถ้า full ก็ return status
  3. Check if transaction(s) ของ address นี้มีจำนวนมากกว่าพื้นที่สำหรับแต่ละ user (เงื่อนไขนี้น่าจะทดลองดูแฮะ ^^)
  4. ซึ่งถ้าผ่านการตรวจสอบข้างต้นก็จะ insert เข้าสู่ storage และ indexes อื่นๆ
  5. เรียก 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 ของ TransactionStore

จุดเริ่มของ function check_for_update กล่าวถึงการเตรียมสร้าง status valid เอาไว้ครับ ว่าแต่ตกลง add transaction ใน mempool มีกี่ status กันนะ เราก็เปิด code ดู enum MempoolAndTransactionStatusCode ได้ความว่า มีทั้งหมด 6 status

  1. Valid: Transaction พึ่งจะถูกส่งเข้า mempool
  2. InsufficientBalance: ผู้ส่ง (sender) มี coins ไม่พอสำหรับจะทำ transaction
  3. InvalidSeqNumber: sequence number เก่าแล้ว
  4. MempoolIsFull: mempool เต็ม
  5. TooManyTransactions: account ถึง maximum capacity per account
  6. InvalidUpdate: Invalid update, ทำได้แค่เพิ่ม gas price
function check_if_full ของ TransactionStore

(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

function process_ready_transactions ของ TransactionStore

เมื่ออ่านดูก็จะได้คำตอบของคำถามว่า 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

ผลลัพธ์ของการ tail -f /tmp./tmp[xxxxxx]/metrics/[client_id].metrics | grep mempool ดูเหมือนมันน่าจะใช้เพื่อทำความเข้าใจพฤติกรรมการทำงานร่วมกับ consensus นะครับ เหมือนที่จะเตรียมวิธีการศึกษาไว้ในครั้งต่อๆไป :)

อ่อ ยังเหลืออีกคำถามนึง ว่าแต่แล้วใครกันล่ะที่เรียกใช้ removing transaction from mempool ที่เราเจอใน trace ในช่วงแรก? พบว่าเป็น mempool’s service commit_transactions

function commit_transactions ของ mempool service

ซึ่งผู้เรียกใช้เป็น 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)

Facebook Libra focus on create testnet-like

Facebook Libra focus on consensus (Part 2)

--

--

Apisak Srihamat

Master of Science AIT, Embedded systems course UC Irvine, Bachelor of Computer Engineering KMITL, Love innovation ideas.