BTC
ETH
HTX
SOL
BNB
ดูตลาด
简中
繁中
English
日本語
한국어
ภาษาไทย
Tiếng Việt

บทความเพื่อทำความเข้าใจกลไกการเร่งสแน็ปช็อตของไคลเอ็นต์ Geth

以太坊爱好者
特邀专栏作者
บทความนี้มีประมาณ 4547 คำ การอ่านทั้งหมดใช้เวลาประมาณ 7 นาที
โครงสร้างการเร่งความเร็วสแน็ปช็อตของ Geth ช่วยลดความซับซ้อนของการอ่านสถานะตามลำดับความสำ
สรุปโดย AI
โครงสร้างการเร่งความเร็วสแน็ปช็อตของ Geth ช่วยลดความซับซ้อนของการอ่านสถานะตามลำดับความสำ

หมายเหตุบรรณาธิการ: บทความนี้มาจากผู้ที่ชื่นชอบ Ethereum (ID: ethfans)หมายเหตุบรรณาธิการ: บทความนี้มาจาก

ผู้ที่ชื่นชอบ Ethereum (ID: ethfans)

ผู้ที่ชื่นชอบ Ethereum (ID: ethfans)

, ผู้แต่ง: Péter Szilágyi, แปล: A Jian, พิมพ์ซ้ำโดย Odaily โดยได้รับอนุญาต

บทความนี้เป็นบทความแรกในชุด Geth Client Answers คุณสามารถถามคำถามเกี่ยวกับลูกค้า Geth ได้ ฉันจะใช้บทความเล็กๆ ทุกสัปดาห์เพื่อตอบคำถามที่ได้รับการโหวตมากที่สุด คำถามที่ได้รับการร้องขอมากที่สุดในสัปดาห์นี้คือ: คุณสามารถพูดคุยเกี่ยวกับความแตกต่างหลักระหว่างโครงสร้างฐานข้อมูลแบบแฟลตและโครงสร้างแบบดั้งเดิมได้หรือไม่?

สถานะของ Ethereum

ก่อนที่จะเจาะลึกโครงสร้างการเร่งความเร็ว (โครงสร้างการเร่งความเร็ว) เรามาทบทวนแนวคิดของ "สถานะ" ใน Ethereum และวิธีการจัดเก็บสถานะเมื่อพูดถึงระดับต่างๆ ของสิ่งที่เป็นนามธรรม

Ethereum มีสถานะที่แตกต่างกันสองประเภท: การรวบรวมบัญชี และ การรวบรวมสล็อตบัญชีต่อสัญญา จากมุมมองที่เป็นนามธรรม ข้อมูลทั้งสองชนิดเป็นคู่คีย์-ค่า การรวบรวมบัญชีจะจับคู่ที่อยู่กับที่อยู่นั้น 'nonce ยอดคงเหลือ ฯลฯ ช่องเก็บข้อมูลของสัญญาแมปค่าใด ๆ (กำหนดและใช้โดยสัญญา) กับค่าบางอย่าง

น่าเสียดายที่ในขณะที่จัดเก็บคู่คีย์-ค่าเหล่านี้เป็นข้อมูลแบบแฟลตอาจมีประสิทธิภาพมาก การตรวจสอบความถูกต้องของคู่คีย์-ค่าจะกลายเป็นเรื่องยากในการคำนวณ เมื่อใดก็ตามที่มีการแก้ไขข้อมูล เราจะต้องแฮชข้อมูลทั้งหมดจากล่างขึ้นบน

เพื่อหลีกเลี่ยงความจำเป็นในการแฮชฐานข้อมูลทั้งหมด เราสามารถแยกฐานข้อมูลออกเป็นส่วนๆ ที่อยู่ติดกันและสร้างโครงสร้างแบบต้นไม้ได้! ข้อมูลที่เป็นต้นฉบับและมีประโยชน์ที่สุดจะถูกวางไว้บนโหนดปลายสุด และแต่ละโหนดภายในบนแผนผังคือค่าแฮชของเนื้อหาด้านล่างโหนด ด้วยวิธีนี้ เมื่อเราต้องการแก้ไขค่าบางอย่าง เราจำเป็นต้องทำการดำเนินการแฮชของเลขลอการิทึมเท่านั้น โครงสร้างข้อมูลประเภทนี้มีชื่อที่รู้จักกันดีคือ "Merkel tree"

แต่ยังไม่จบ วิธีนี้ยังขาดความซับซ้อนในการคำนวณ แม้ว่าโครงสร้าง Merkle tree จะมีประสิทธิภาพมากเมื่อแก้ไขข้อมูลที่มีอยู่ หากการแทรกและการลบข้อมูลเปลี่ยนขอบเขตของบล็อคข้อมูลขนาดเล็กที่อยู่ด้านล่าง จะทำให้ค่าแฮชที่คำนวณได้ทั้งหมดเป็นโมฆะ

ในขณะนี้ แทนที่จะจัดกลุ่มฐานข้อมูลสุ่มสี่สุ่มห้า เราสามารถใช้คีย์เองเพื่อจัดระเบียบข้อมูล และจัดเรียงข้อมูลให้อยู่ในรูปแบบต้นไม้ตามคำนำหน้าทั่วไป! ด้วยวิธีนี้ การดำเนินการทั้งการแทรกและการลบจะไม่ส่งผลกระทบต่อโหนดทั้งหมด แต่จะส่งผลต่อโหนด (ลอการิทึม) บนเส้นทางจากรากของต้นไม้ไปยังใบไม้เท่านั้น โครงสร้างข้อมูลนี้เรียกว่า "Patricia tree"

การรวมสองวิธีข้างต้น - ลำดับชั้นของต้นไม้ Patricia และอัลกอริทึมแฮชของต้นไม้ Merkle - เป็นสิ่งที่เรียกว่า "ต้นไม้ Merkel-Patricia" ซึ่งใช้ในทางปฏิบัติ โครงสร้างข้อมูลที่แสดงถึงสถานะของ Ethereum ไม่ว่าจะเป็นการแก้ไข การแทรก การลบ หรือการตรวจสอบ มีเพียงลอการิทึมที่ซับซ้อนเท่านั้น! ข้อยกเว้นเล็กน้อยเพียงอย่างเดียวคือคีย์บางคีย์จะถูกแฮชก่อนการแทรกเพื่อให้การลองสมดุล

ที่เก็บข้อมูลสถานะของ Ethereum

ข้างต้นอธิบายว่าทำไม Ethereum จึงใช้โครงสร้างต้นไม้ Merkle Patricia เพื่อจัดเก็บสถานะของมัน น่าเสียดายที่ในขณะที่การดำเนินการที่จำเป็นนั้นรวดเร็ว แต่ละตัวเลือกมาพร้อมกับการเสียสละ ความซับซ้อนของลอการิทึมของการดำเนินการอัพเดตและตรวจสอบหมายความว่าการอ่านและการจัดเก็บสำหรับแต่ละคีย์นั้นเป็นลอการิทึม (การอ่านแบบลอการิทึมและการจัดเก็บลอการิทึม) นี่เป็นเพราะแต่ละโหนดภายในของโครงสร้างต้นไม้ต้องถูกจัดเก็บแยกกันบนฮาร์ดดิสก์

ในขณะนี้ ฉันไม่ทราบแน่ชัดว่าโครงสร้างบัญชีมีความลึกเพียงใด แต่เมื่อประมาณปีที่แล้ว สถานะบัญชีได้เติมเต็มโครงสร้างบัญชี 7 ระดับ ซึ่งหมายความว่าทุกการทำงานของทรี (เช่น ยอดการอ่าน การเขียนไม่มี) สัมผัสกับโหนดภายในอย่างน้อย 7-8 โหนด ดังนั้นจึงเข้าถึงฐานข้อมูลถาวรอย่างน้อย 7-8 โหนด LevelDB จัดระเบียบข้อมูลได้ถึง 7 ชั้น ดังนั้นจึงมีตัวคูณเพิ่มเติม ผลลัพธ์สุดท้ายคือการเข้าถึงสถานะเดียวคาดว่าจะขยายการเข้าถึงดิสก์แบบสุ่ม 25-50 รายการ คุณคูณค่านั้นด้วยสถานะทั้งหมดที่อ่านและเขียนของธุรกรรมทั้งหมดในบล็อก และคุณจะได้ตัวเลขที่น่ากลัว

[แน่นอนว่า การใช้งานไคลเอ็นต์ทั้งหมดพยายามให้โอเวอร์เฮดต่ำที่สุดเท่าที่จะเป็นไปได้ Geth ใช้พื้นที่หน่วยความจำที่ใหญ่ขึ้นเพื่อแคชโหนดต่างๆ นอกจากนี้ ยังใช้กลไกการตัดในหน่วยความจำเพื่อหลีกเลี่ยงการเขียนข้อมูลลงดิสก์ซึ่งจะถูกลบหลังจากบล็อกไม่กี่บล็อก แต่สิ่งนี้ต้องการบทความอื่นเพื่อให้ชัดเจน ]

สิ่งที่น่ากลัวก็คือตัวเลขนี้เป็นค่าใช้จ่ายในการรันโหนด Ethereum ที่รับประกันว่าจะตรวจสอบทุกสถานะตลอด 24 ชั่วโมงทุกวัน

เราทำได้ดีกว่านี้ไหม?

การเข้าชมทั้งหมดไม่เท่ากัน

  • การดำเนินการของ Ethereum อาศัยการพิสูจน์สถานะการเข้ารหัสลับ ตราบใดที่เราต้องการรักษาความสามารถในการตรวจสอบข้อมูลทั้งหมด เราไม่สามารถแก้ไขปัญหาการขยายการอ่านและเขียนของฮาร์ดดิสก์ได้ นั่นคือ เราทำได้และทำได้ เชื่อถือข้อมูลที่เราตรวจสอบแล้ว

  • การตรวจสอบวัตถุสถานะแต่ละรายการซ้ำๆ นั้นไม่มีความหมาย แต่ถ้าคุณต้องตรวจสอบทุกครั้งที่คุณดึงข้อมูลจากฮาร์ดดิสก์ แสดงว่าคุณกำลังทำสิ่งที่ไร้ความหมาย โครงสร้างแบบทรีของ Merkle Patricia ได้รับการออกแบบโดยเนื้อแท้สำหรับการดำเนินการเขียน แต่จะกลายเป็นภาระสำหรับการดำเนินการอ่าน เราไม่สามารถกำจัดมันได้ และเราไม่สามารถทำให้ผอมลงได้ แต่นั่นไม่ได้หมายความว่าเราต้องใช้มันในทุกโอกาส

  • สถานการณ์ของสถานะการเข้าถึงโหนด Ethereum สามารถแบ่งออกเป็นสามประเภทต่อไปนี้:

เมื่อนำเข้าบล็อกใหม่ การดำเนินการของรหัส EVM จะสร้างจำนวนการอ่านและเขียนสถานะที่สมดุลมากขึ้นหรือน้อยลง อย่างไรก็ตาม บล็อกที่ใช้สำหรับการโจมตีแบบปฏิเสธการให้บริการอาจสร้างการดำเนินการอ่านมากกว่าการดำเนินการเขียน

เมื่อตัวดำเนินการโหนดเรียกสถานะ (เช่น การเรียก ethcall และการดำเนินการที่คล้ายกัน) การดำเนินการโค้ด EVM จะสร้างการดำเนินการอ่านเท่านั้น (แน่นอนว่าอาจมีการดำเนินการเขียน แต่ข้อมูลที่สร้างขึ้นโดยการดำเนินการเหล่านี้จะถูกละทิ้งในที่สุดและจะไม่คงอยู่ ภายในฮาร์ดไดร์ฟ)

เมื่อโหนดซิงโครไนซ์บล็อกเชน ซิงโครไนเซอร์จะขอสถานะจากโหนดระยะไกล และผู้ร้องขอจะขุดข้อมูลและกระจายไปยังซิงโครไนเซอร์ผ่านเครือข่าย

จากรูปแบบการเข้าถึงข้างต้น การดำเนินการของโหนดจำนวนมากสามารถทำได้เร็วขึ้นมาก หากเราสามารถลัดวงจรการดำเนินการอ่านโดยไม่ต้องสัมผัสแผนผังสถานะ สิ่งนี้สามารถเปิดรูปแบบการเข้าถึงใหม่บางอย่าง (เช่น การวนซ้ำสถานะ) ที่ก่อนหน้านี้มีราคาแพงเกินไปที่จะเป็นไปได้

แน่นอนว่าการเสียสละเป็นสิ่งที่หลีกเลี่ยงไม่ได้ โครงสร้างการเร่งความเร็วใหม่จะแนะนำโอเวอร์เฮดเพิ่มเติมโดยไม่ต้องถอดโครงสร้างทรีออก คำถามเดียวคือ: ค่าโสหุ้ยพิเศษจ่ายออกไปเพียงพอที่จะรับประกันการลองหรือไม่?

โปรดปฏิบัติตาม

เราได้พัฒนาโครงสร้างต้นไม้ Merkle Patricia อันมหัศจรรย์เพื่อแก้ปัญหาทั้งหมดของเรา และตอนนี้เราต้องการให้การดำเนินการอ่านข้ามไป ดังนั้น เราควรใช้โครงสร้างการเร่งแบบใดเพื่อทำให้การดำเนินการอ่านเร็วขึ้นอีกครั้ง เห็นได้ชัดว่าถ้าเราไม่ต้องการโครงสร้างแบบต้นไม้ เราก็สามารถโยนความซับซ้อนทั้งหมดที่มาพร้อมกับโครงสร้างแบบต้นไม้ทิ้งไป และเราสามารถกลับสู่สถานะดั้งเดิมได้โดยตรง

ตามที่กล่าวไว้ในตอนต้นของบทความนี้ ในทางทฤษฎี วิธีการจัดเก็บข้อมูลของสถานะของ Ethereum ในสถานะที่เหมาะสมควรเป็นคู่ของคีย์-ค่าธรรมดา หากไม่มีข้อ จำกัด ของ Merkel Patricia tree ก็ไม่มีอะไรมาหยุดเราได้ เพื่อให้แผนการในอุดมคตินี้เป็นจริง!

ไม่นานมานี้ Geth ได้เปิดตัวโครงสร้างการเร่งความเร็วสแน็ปช็อต (ไม่ได้เปิดใช้งานตามค่าเริ่มต้น) สแน็ปช็อตคือมุมมองที่สมบูรณ์ของสถานะของ Ethereum ที่บล็อกที่กำหนด การแยกรายละเอียดการใช้งานออกไป มันเพียงแค่สแต็คช่องจัดเก็บบัญชีและสัญญาทั้งหมดเข้าด้วยกัน ซึ่งแสดงด้วยคู่คีย์-ค่าแบบแฟลต

เมื่อใดก็ตามที่เราต้องการเข้าถึงบัญชีหรือช่องเก็บข้อมูลใดช่องหนึ่ง เราต้องจ่ายเพียงหนึ่งการดำเนินการสืบค้นของ LevelDB แทนที่จะต้องสืบค้น 7-8 ครั้งในแต่ละทรี ตามทฤษฎีแล้ว การอัปเดตสแน็ปช็อตก็ง่ายมากเช่นกัน หลังจากประมวลผลบล็อก เราจำเป็นต้องดำเนินการเขียน LevelDB เพิ่มเติมอีก 1 รายการสำหรับช่องเก็บข้อมูลแต่ละช่องที่จะอัปเดต

โครงสร้างการเร่งสแน็ปช็อตช่วยลดความซับซ้อนของการดำเนินการอ่านจาก O(log n) เป็น O(1) (คูณด้วยค่าโสหุ้ยของ LevelDB) โดยมีค่าใช้จ่ายในการเปลี่ยนความซับซ้อนของการดำเนินการเขียนจาก O(log n) เป็น O( 1 + บันทึก n) (คูณด้วยโอเวอร์เฮดของ LevelDB) และเพิ่มพื้นที่จัดเก็บดิสก์จาก O(n บันทึก n) เป็น O(n + n บันทึก n)

ปีศาจอยู่ในรายละเอียด

การรักษาความพร้อมใช้งานของสแน็ปช็อตสถานะ Ethereum นั้นไม่ใช่เรื่องง่าย ตราบใดที่บล็อกยังคงถูกสร้างขึ้นทีละบล็อก วางซ้อนกันบนบล็อกสุดท้าย วิธีการรวมการเปลี่ยนแปลงล่าสุดลงในสแน็ปช็อตอย่างคร่าวๆ จะทำงานได้ดี อย่างไรก็ตาม แม้จะมีการปรับโครงสร้างบล็อกเชนเพียงเล็กน้อย (แม้แต่บล็อกเดียว) กลไกสแน็ปช็อตก็พังลงเพราะการดำเนินการเลิกทำนั้นไม่ได้ออกแบบไว้เลย สำหรับรูปแบบการแสดงข้อมูลแบบแฟลต การเขียนอย่างต่อเนื่องเป็นการดำเนินการแบบทางเดียว และที่แย่ไปกว่านั้น เราไม่มีทางเข้าถึงสถานะที่เก่ากว่าได้ (เช่น dApps บางตัวจำเป็นต้องเข้าถึงสถานะของ 3 บล็อกที่แล้ว หรือสถานะของ 64 บล็อกในโหมดซิงโครไนซ์แบบเร็ว/สแนป)

เพื่อเอาชนะข้อจำกัดเหล่านี้ สแนปช็อตของไคลเอนต์ Geth ประกอบด้วยสองส่วน: เลเยอร์ฮาร์ดดิสก์ถาวร ซึ่งเป็นสแน็ปช็อตที่สมบูรณ์ของสถานะที่บล็อกเก่า (เช่น 128 บล็อกบนสุด) และดิฟเลเยอร์ A ในหน่วยความจำ แผนผังของส่วนประกอบที่ใช้ในการรวบรวมการดำเนินการเขียนล่าสุด

  • เมื่อประมวลผลบล็อกใหม่ เราจะไม่รวมการเขียนเหล่านี้โดยตรงไปยังเลเยอร์ของฮาร์ดดิสก์ แต่เพียงแค่สร้างเลเยอร์ความแตกต่างในหน่วยความจำใหม่ที่มีการเปลี่ยนแปลงเหล่านี้ เมื่อเลเยอร์ต่างภายในหน่วยความจำสะสมจนมีจำนวนเลเยอร์สูงพอ เลเยอร์ล่างสุดจะเริ่มรวมการอัปเดตและส่งไปยังเลเยอร์ของฮาร์ดดิสก์ เมื่อจำเป็นต้องอ่าน state object เราจะเริ่มค้นหาจากชั้น diff ด้านบนและลงไปจนพบในชั้น diff หรือในชั้นของฮาร์ดดิสก์

  • การแสดงข้อมูลนี้มีประสิทธิภาพมากและแก้ปัญหาต่างๆ ได้ เนื่องจากชั้น diff ภายในหน่วยความจำก่อตัวเป็นต้นไม้ การจัดโครงสร้างใหม่ภายใน 128 บล็อกจำเป็นต้องนำชั้น diff ของบล็อกหลักออกเท่านั้น แล้วจึงเริ่มสร้าง dApps และ farsyncer ที่ต้องการสถานะที่เก่ากว่าสามารถเข้าถึงสถานะล่าสุด 128 สถานะล่าสุด ค่าโสหุ้ยกลายเป็นการค้นหาการแมป 128 รายการ แต่การค้นหาในหน่วยความจำ 128 รายการนั้นมีลำดับความสำคัญเร็วกว่าการอ่านฮาร์ดดิสก์ 8 ครั้งและการขยายระดับฐานข้อมูล 4 ถึง 5 เท่า

  • แน่นอนว่ายังมีข้อผิดพลาดมากมาย ฉันจะไม่ลงลึกมากเกินไป แต่เพียงระบุรายการต่อไปนี้:

  • การทำลายตัวเอง (การดำเนินการทำลายตัวเองตามสัญญา) (และการดำเนินการลบ) นั้นยากเป็นพิเศษในการจัดการเพราะพวกมันต้องการการลัดวงจรในการลงมาของดิฟเลเยอร์

  • หากมีการจัดระเบียบสายโซ่ใหม่ลึกกว่าชั้นฮาร์ดดิสก์ถาวร สแน็ปช็อตปัจจุบันจะถูกละทิ้งและสร้างใหม่ทั้งหมด การดำเนินการทั้งชุดมีราคาแพงมาก

  • เมื่อโหนดปิดลง เลเยอร์ diff ในหน่วยความจำจะต้องคงอยู่ในบันทึกและโหลดเพื่อสำรอง มิฉะนั้นสแน็ปช็อตจะไร้ประโยชน์หลังจากรีสตาร์ท

  • ใช้ชั้นความแตกต่างที่ต่ำที่สุดเป็นตัวสะสมที่จะถูกล้างข้อมูลไปยังดิสก์เมื่อมีการใช้หน่วยความจำเกินที่กำหนดเท่านั้น ซึ่งช่วยให้สามารถเขียนข้อมูลซ้ำซ้อนลงในช่องเดียวกันระหว่างกลุ่มต่างๆ ได้

  • ควรจัดสรรแคชการอ่านสำหรับชั้นของฮาร์ดดิสก์ เพื่อที่ฮาร์ดดิสก์จะไม่เสียหายหากสัญญาเข้าถึงสล็อตเก็บข้อมูลเดิมซ้ำๆ

ใช้ตัวกรองบานสะสมในเลเยอร์ diff ในหน่วยความจำเพื่อตรวจจับอย่างรวดเร็วว่าวัตถุสถานะมีแนวโน้มที่จะมีอยู่ในเลเยอร์ diff หรือควรข้ามไปที่ดิสก์โดยตรง

ข้อมูลต้นฉบับ (ที่อยู่บัญชี คีย์การจัดเก็บสัญญา) ไม่ได้ถูกกำหนดให้เป็นคีย์ แต่ค่าแฮชของข้อมูลเหล่านี้จะถูกใช้เป็นคีย์เพื่อให้แน่ใจว่าลำดับการวนซ้ำของสแน็ปช็อตจะเหมือนกับของ Merkle Patricia tree

เวลาในการสร้างชั้นของฮาร์ดดิสก์ถาวรนั้นยาวนานกว่าเวลาในการตัดหน้าต่างแผนผังสถานะ ดังนั้นแม้แต่ตัวสร้างก็จำเป็นต้องติดตามการทำงานของเชนแบบไดนามิก

การอยู่ร่วมกันของความงามและความอัปลักษณ์

โครงสร้างการเร่งความเร็วสแน็ปช็อตของ Geth ช่วยลดความซับซ้อนของการอ่านสถานะตามลำดับความสำคัญ ซึ่งหมายความว่าการโจมตี DoS แบบอ่านเป็นลำดับความสำคัญที่ยากต่อการเปิดตัว และการเรียก ethcall ก็เป็นลำดับความสำคัญที่เร็วกว่าเช่นกัน (สมมติว่าไม่มีคอขวดของ CPU)

แน่นอนว่าทั้งหมดนี้ไม่มีค่าใช้จ่าย หลังจากการซิงโครไนซ์เริ่มต้นเสร็จสิ้น โหนดที่เข้าร่วมในเครือข่ายหลักต้องใช้เวลา 9 ถึง 10 ชั่วโมงในการสร้างสแน็ปช็อตเริ่มต้น (จากนั้นจึงรักษาความพร้อมใช้งาน) และต้องใช้ฮาร์ดดิสก์เพิ่มเติมมากกว่า 15 GB

บทส่งท้าย

ส่วนที่แย่อยู่ตรงไหน? เราใช้เวลา 6 เดือนในการสร้างความมั่นใจให้มากพอที่จะปล่อยกลไกสแน็ปช็อต และยังไม่ใช่ฟีเจอร์เริ่มต้น จำเป็นต้องเปิดใช้งานด้วยแฟล็ก --snapshot และยังคงมีงานปรับปรุงเกี่ยวกับการใช้หน่วยความจำและการกู้คืนข้อขัดข้อง ทำ.

สรุปแล้วเราภูมิใจในการปรับปรุงนี้มาก มีงานจำนวนมากเข้ามาเกี่ยวข้อง และมันก็คลำหาในความมืด ดำเนินการทุกอย่างด้วยตัวเองและภาวนาให้มันได้ผล สิ่งที่น่าสนใจอีกอย่างคือ leaf sync เวอร์ชันแรกเขียนขึ้นเมื่อสองปีครึ่งที่แล้ว แต่ถูกบล็อกเนื่องจากเราขาดโครงสร้างการเร่งความเร็วที่จำเป็นในการขับเคลื่อน

开发者
ETH
ยินดีต้อนรับเข้าร่วมชุมชนทางการของ Odaily
กลุ่มสมาชิก:
https://t.me/Odaily_News
กลุ่มสนทนา:
https://t.me/Odaily_GoldenApe
บัญชีทางการ:
https://twitter.com/OdailyChina
สารบัญบทความ
คลังบทความของผู้เขียน
以太坊爱好者
อันดับบทความร้อน
Daily
Weekly
ดาวน์โหลดแอพ Odaily พลาเน็ตเดลี่
ให้คนบางกลุ่มเข้าใจ Web3.0 ก่อน
IOS
Android