บล็อกเชน เป็นเทคโนโลยีที่เป็นนวัตกรรมใหม่ที่สัญญาว่าจะปรับปรุงชีวิตมนุษย์ในด้านต่างๆ โดยจะถ่ายโอนกระบวนการและผลิตภัณฑ์จริงไปยังพื้นที่ดิจิทัล รับประกันความเร็วและความน่าเชื่อถือของธุรกรรมทางการเงิน ลดต้นทุน และยังช่วยให้คุณสร้างแอปพลิเคชัน DAPP สมัยใหม่โดยใช้สัญญาอัจฉริยะในเครือข่ายแบบกระจายอำนาจ
เมื่อพิจารณาถึงประโยชน์มากมายและการใช้งานบล็อกเชนที่หลากหลาย อาจดูน่าแปลกใจที่เทคโนโลยีที่มีแนวโน้มนี้ยังไม่ได้ถูกนำมาใช้ในทุกอุตสาหกรรม ปัญหาคือบล็อกเชนแบบกระจายอำนาจสมัยใหม่ขาดความสามารถในการขยายขนาด Ethereum ประมวลผลธุรกรรมประมาณ 20 รายการต่อวินาที ซึ่งไม่เพียงพอที่จะตอบสนองความต้องการของธุรกิจที่มีการเปลี่ยนแปลงตลอดเวลาในปัจจุบัน ในเวลาเดียวกัน บริษัทที่ใช้เทคโนโลยีบล็อกเชนลังเลที่จะละทิ้ง Ethereum เนื่องจากมีการป้องกันระดับสูงจากการแฮ็กและความล้มเหลวของเครือข่าย
เพื่อให้มั่นใจถึงการกระจายอำนาจ ความปลอดภัย และความสามารถในการปรับขนาดในบล็อกเชน ซึ่งจะช่วยแก้ปัญหา Scalability Trilemma ซึ่งเป็นทีมพัฒนา
กระบวนการสำคัญใน Plasma Cash
1. ผู้ใช้เรียกฟังก์ชันสัญญาอัจฉริยะว่า "เงินฝาก" โดยส่งผ่านจำนวน ETH ที่เขาต้องการฝากเข้าในโทเค็น Plasma Cash ฟังก์ชันสัญญาอัจฉริยะจะสร้างโทเค็นและสร้างเหตุการณ์เกี่ยวกับโทเค็นนั้น
2. โหนด Plasma Cash ที่สมัครรับเหตุการณ์สัญญาอัจฉริยะจะได้รับเหตุการณ์เกี่ยวกับการสร้างเงินฝากและเพิ่มธุรกรรมเกี่ยวกับการสร้างโทเค็นลงในพูล
3. โหนด Plasma Cash พิเศษจะนำธุรกรรมทั้งหมดจากพูล (สูงสุด 1 ล้าน) เป็นระยะๆ และสร้างบล็อกจากรายการเหล่านั้น คำนวณแผนผัง Merkle และแฮชตามลำดับ บล็อกนี้ถูกส่งไปยังโหนดอื่นเพื่อตรวจสอบ โหนดจะตรวจสอบว่าแฮชของ Merkle นั้นถูกต้องหรือไม่ และธุรกรรมนั้นถูกต้องหรือไม่ (เช่น ผู้ส่งโทเค็นเป็นเจ้าของหรือไม่) หลังจากตรวจสอบบล็อกแล้ว โหนดจะเรียกใช้ฟังก์ชัน `submitBlock` ของสัญญาอัจฉริยะ ซึ่งจะบันทึกหมายเลขบล็อกและแฮช Merkle ไว้ที่ Edge Chain สัญญาอัจฉริยะจะสร้างเหตุการณ์ที่บ่งชี้ถึงความสำเร็จในการเพิ่มบล็อก ธุรกรรมจะถูกลบออกจากพูล
4. โหนดที่ได้รับเหตุการณ์การส่งบล็อกจะเริ่มใช้ธุรกรรมที่เพิ่มลงในบล็อก
5. ในบางจุด เจ้าของ (หรือไม่ใช่เจ้าของ) ของโทเค็นต้องการถอนออกจาก Plasma Cash ในการดำเนินการนี้ เขาเรียกใช้ฟังก์ชัน `startExit` โดยส่งข้อมูลเกี่ยวกับธุรกรรม 2 รายการล่าสุดบนโทเค็น ซึ่งยืนยันว่าเขาเป็นเจ้าของโทเค็น สัญญาอัจฉริยะที่ใช้แฮช Merkle จะตรวจสอบการมีอยู่ของธุรกรรมในบล็อกและส่งโทเค็นเพื่อถอนออก ซึ่งจะเกิดขึ้นภายในสองสัปดาห์
6. หากการดำเนินการถอนโทเค็นเกิดขึ้นโดยมีการละเมิด (โทเค็นถูกใช้ไปหลังจากขั้นตอนการถอนเริ่มต้น หรือโทเค็นนั้นเป็นของคนอื่นอยู่แล้วก่อนการถอน) เจ้าของโทเค็นสามารถหักล้างการถอนได้ภายในสองสัปดาห์
ความเป็นส่วนตัวเกิดขึ้นได้สองวิธี
1. ห่วงโซ่รากไม่รู้อะไรเลยเกี่ยวกับธุรกรรมที่สร้างขึ้นและส่งต่อภายในห่วงโซ่ลูก ข้อมูลเกี่ยวกับผู้ที่ฝากและถอน ETH จาก Plasma Cash ยังคงเป็นข้อมูลสาธารณะ
2. ลูกโซ่อนุญาตให้ทำธุรกรรมโดยไม่ระบุชื่อโดยใช้ zk-SNARK
กองเทคโนโลยี
- NodeJS
- Redis
- อีเธอเรียม
- soild
การทดสอบ
ในขณะที่พัฒนา Plasma Cash เราได้ทดสอบความเร็วของระบบและได้ผลลัพธ์ดังต่อไปนี้:
- เพิ่มธุรกรรมมากถึง 35 รายการต่อวินาทีลงในพูล
- สามารถจัดเก็บธุรกรรมได้สูงสุด 1 รายการในบล็อก
การทดสอบดำเนินการบนเซิร์ฟเวอร์ 3 เครื่องต่อไปนี้:
1. Intel Core i7-6700 Quad-Core Skylake รวมอยู่ด้วย NVMe SSD – 512GB, 64GB DDR4 RAM
โหนดเงินสดพลาสม่าที่ตรวจสอบความถูกต้อง 3 รายการได้รับการยกขึ้น
2. AMD Ryzen 7 1700X Octa-Core “Summit Ridge” (Zen), SATA SSD – 500 GB, 64 GB DDR4 RAM
โหนด ETH ของ Ropsten testnet ได้รับการยกระดับแล้ว
โหนดเงินสดพลาสม่าที่ตรวจสอบความถูกต้อง 3 รายการได้รับการยกขึ้น
3. Intel Core i9-9900K Octa-Core พร้อม NVMe SSD – 1 TB, 64 GB DDR4 RAM
1 โหนดการส่ง Plasma Cash ได้รับการยกขึ้น
โหนดเงินสดพลาสม่าที่ตรวจสอบความถูกต้อง 3 รายการได้รับการยกขึ้น
มีการเปิดตัวการทดสอบเพื่อเพิ่มธุรกรรมไปยังเครือข่าย Plasma Cash
รวม: 10 Plasma Cash nodes ในเครือข่ายส่วนตัว
ทดสอบ 1
มีการจำกัดการทำธุรกรรม 1 ล้านรายการต่อบล็อก ดังนั้น ธุรกรรม 1 ล้านรายการจึงแบ่งออกเป็น 2 บล็อก (เนื่องจากระบบจัดการเพื่อเป็นส่วนหนึ่งของธุรกรรมและส่งในขณะที่กำลังถูกส่ง)
สถานะเริ่มต้น: บล็อกสุดท้าย #7; ธุรกรรมและโทเค็น 1 ล้านรายการถูกเก็บไว้ในฐานข้อมูล
00:00 — เริ่มต้นสคริปต์การสร้างธุรกรรม
01:37 - มีการสร้างธุรกรรม 1 ล้านรายการและเริ่มส่งไปยังโหนด
01:46 — โหนดส่งรับธุรกรรม 240 รายการจากพูลและฟอร์มบล็อก #8 เรายังเห็นว่ามีการเพิ่มธุรกรรม 320 รายการลงในพูลภายใน 10 วินาที
01:58 — บล็อก #8 ได้รับการลงนามและส่งเพื่อตรวจสอบความถูกต้อง
02:03 — บล็อก #8 ได้รับการตรวจสอบแล้ว และฟังก์ชัน `submitBlock` ของสัญญาอัจฉริยะถูกเรียกด้วยแฮช Merkle และหมายเลขบล็อก
02:10 — สคริปต์สาธิตทำงานเสร็จสิ้น ซึ่งส่งธุรกรรม 1 ล้านรายการใน 32 วินาที
02:33 - โหนดเริ่มได้รับข้อมูลที่บล็อก #8 ถูกเพิ่มเข้าไปในรูทเชน และเริ่มทำธุรกรรม 240 รายการ
02:40 - ธุรกรรม 240 รายการถูกลบออกจากพูล ซึ่งอยู่ในบล็อก #8 แล้ว
02:56 — โหนดส่งรับธุรกรรม 760k ที่เหลือจากพูลและเริ่มคำนวณแฮช Merkle และบล็อกการลงนาม #9
03:20 - โหนดทั้งหมดมีธุรกรรมและโทเค็น 1 ล้าน 240 รายการ
03:35 — บล็อก #9 ได้รับการลงนามและส่งเพื่อตรวจสอบความถูกต้องไปยังโหนดอื่น
03:41 - เกิดข้อผิดพลาดของเครือข่าย
04:40 — การรอการตรวจสอบบล็อก #9 หมดเวลาแล้ว
04:54 — โหนดส่งรับธุรกรรม 760k ที่เหลือจากพูลและเริ่มคำนวณแฮช Merkle และบล็อกการลงนาม #9
05:32 — บล็อก #9 ได้รับการลงนามและส่งเพื่อตรวจสอบความถูกต้องไปยังโหนดอื่น
05:53 — บล็อก #9 ได้รับการตรวจสอบและส่งไปยังรูทเชน
06:17 - โหนดเริ่มได้รับข้อมูลที่บล็อก #9 ถูกเพิ่มเข้าไปในรูทเชน และเริ่มทำธุรกรรม 760 รายการ
06:47 — พูลเคลียร์ธุรกรรมที่อยู่ในบล็อก #9 แล้ว
09:06 - โหนดทั้งหมดมีธุรกรรมและโทเค็น 2 ล้านรายการ
ทดสอบ 2
มีการจำกัดไว้ที่ 350 ต่อบล็อก เป็นผลให้เรามี 3 บล็อก
สถานะเริ่มต้น: บล็อกสุดท้าย #9; ธุรกรรมและโทเค็น 2 ล้านรายการถูกเก็บไว้ในฐานข้อมูล
00:00 — สคริปต์การสร้างธุรกรรมเปิดตัวแล้ว
00:44 - มีการสร้างธุรกรรม 1 ล้านรายการและเริ่มส่งไปยังโหนด
00:56 — โหนดส่งรับธุรกรรม 320 รายการจากพูลและฟอร์มบล็อก #10 เรายังเห็นว่ามีการเพิ่มธุรกรรม 320 รายการลงในพูลภายใน 10 วินาที
01:12 — บล็อก #10 ได้รับการลงนามและส่งไปยังโหนดอื่นเพื่อตรวจสอบความถูกต้อง
01:18 — สคริปต์สาธิตทำงานเสร็จสิ้น ซึ่งส่งธุรกรรม 1 ล้านรายการใน 34 วินาที
01:20 — บล็อก #10 ได้รับการตรวจสอบและส่งไปยังรูทเชน
01:51 - โหนดทั้งหมดได้รับข้อมูลจาก root chain ที่เพิ่มบล็อก #10 และเริ่มใช้ธุรกรรม 320 รายการ
02:01 - พูลเคลียร์ธุรกรรม 320 รายการที่ถูกเพิ่มในบล็อก #10
02:15 — โหนดส่งรับธุรกรรม 350 รายการจากพูลและบล็อกแบบฟอร์ม #11
02:34 — บล็อก #11 ได้รับการลงนามและส่งไปยังโหนดอื่นเพื่อตรวจสอบความถูกต้อง
02:51 — บล็อก #11 ได้รับการตรวจสอบและส่งไปยังรูทเชน
02:55 — โหนดสุดท้ายทำธุรกรรมเสร็จสิ้นจากบล็อก #10
10:59 — ธุรกรรมที่มีการส่งบล็อก #9 ใช้เวลานานมากใน root chain แต่เสร็จสมบูรณ์และโหนดทั้งหมดได้รับข้อมูลเกี่ยวกับมัน และเริ่มทำธุรกรรม 350 รายการ
11:05 - พูลเคลียร์ธุรกรรม 320 รายการที่ถูกเพิ่มในบล็อก #11
12:10 - โหนดทั้งหมดมีธุรกรรมและโทเค็น 1 ล้าน 670 รายการ
12:17 — โหนดส่งรับธุรกรรม 330 รายการจากพูลและฟอร์มบล็อก #12
12:32 — บล็อก #12 ได้รับการลงนามและส่งไปยังโหนดอื่นเพื่อตรวจสอบความถูกต้อง
12:39 — บล็อก #12 ได้รับการตรวจสอบและส่งไปยังรูทเชน
13:44 - โหนดทั้งหมดได้รับข้อมูลจาก root chain ที่เพิ่มบล็อก #12 และเริ่มใช้ธุรกรรม 330 รายการ
14:50 - โหนดทั้งหมดมีธุรกรรมและโทเค็น 2 ล้านรายการ
ทดสอบ 3
ในเซิร์ฟเวอร์เครื่องแรกและเซิร์ฟเวอร์ที่สอง โหนดตรวจสอบความถูกต้องหนึ่งโหนดถูกแทนที่ด้วยโหนดที่ส่ง
สถานะเริ่มต้น: บล็อกสุดท้าย #84; 0 ธุรกรรมและโทเค็นที่บันทึกไว้ในฐานข้อมูล
00:00 — มีการเปิดตัวสคริปต์ 3 ตัวที่สร้างและส่งธุรกรรม 1 ล้านรายการต่อครั้ง
01:38 — มีการสร้างธุรกรรม 1 ล้านรายการและเริ่มการส่งไปยังโหนด #3
01:50 — ส่งโหนด #3 รับธุรกรรม 330 รายการจากพูลและบล็อกแบบฟอร์ม #85 (f21) เรายังเห็นว่าธุรกรรม 350 รายการถูกเพิ่มลงในพูลภายใน 10 วินาที
01:53 — มีการสร้างธุรกรรม 1 ล้านรายการและเริ่มการส่งไปยังโหนด #1
01:50 — ส่งโหนด #3 รับธุรกรรม 330 รายการจากพูลและบล็อกแบบฟอร์ม #85 (f21) เรายังเห็นว่าธุรกรรม 350 รายการถูกเพิ่มลงในพูลภายใน 10 วินาที
02:01 — ส่งโหนด #1 รับธุรกรรม 250 รายการจากพูลและฟอร์มบล็อก #85 (65e)
02:06 — บล็อก #85 (f21) ได้รับการลงนามและส่งไปยังโหนดอื่นเพื่อตรวจสอบความถูกต้อง
02:08 — สคริปต์สาธิตของเซิร์ฟเวอร์ #3 ซึ่งส่งธุรกรรม 1 ล้านรายการใน 30 วินาที เสร็จสิ้นการทำงาน
02:14 — บล็อก #85 (f21) ได้รับการตรวจสอบและส่งไปยังรูทเชน
02:19 — บล็อก #85 (65e) ได้รับการลงนามและส่งไปยังโหนดอื่นเพื่อตรวจสอบความถูกต้อง
02:22 — มีการสร้างธุรกรรม 1 ล้านรายการและเริ่มการส่งไปยังโหนด #2
02:27 — บล็อก #85 (65e) ได้รับการตรวจสอบและส่งไปยังรูทเชน
02:29 — โหนดส่ง #2 รับธุรกรรม 111855 รายการจากพูลและบล็อกแบบฟอร์ม #85 (256)
02:36 — บล็อก #85 (256) ได้รับการลงนามและส่งไปยังโหนดอื่นเพื่อตรวจสอบความถูกต้อง
02:36 — สคริปต์สาธิตของเซิร์ฟเวอร์ #1 ซึ่งส่งธุรกรรม 1 ล้านรายการใน 42.5 วินาที เสร็จสิ้นการทำงาน
02:38 — บล็อก #85 (256) ได้รับการตรวจสอบและส่งไปยังรูทเชน
03:08 — สคริปต์เซิร์ฟเวอร์ #2 ทำงานเสร็จแล้ว ซึ่งส่งธุรกรรม 1 ล้านรายการใน 47 วินาที
03:38 - โหนดทั้งหมดได้รับข้อมูลจาก root chain ที่บล็อก #85 (f21), #86(65e), #87(256) ถูกเพิ่มและเริ่มใช้ธุรกรรม 330k, 250k, 111855
03:49 - พูลถูกเคลียร์ที่ 330k, 250k, 111855 ธุรกรรมที่ถูกเพิ่มในบล็อก #85 (f21), #86(65e), #87(256)
03:59 — โหนดส่ง #1 รับธุรกรรม 888145 รายการจากพูลและบล็อกแบบฟอร์ม #88 (214) โหนดส่ง #2 รับธุรกรรม 750k จากพูลและบล็อกฟอร์ม #88 (50a) โหนดส่ง #3 รับธุรกรรม 670k จาก พูลและแบบฟอร์มบล็อก #88 (d3b)
04:44 — บล็อก #88 (d3b) ได้รับการลงนามและส่งไปยังโหนดอื่นเพื่อตรวจสอบความถูกต้อง
04:58 — บล็อก #88 (214) ได้รับการลงนามและส่งไปยังโหนดอื่นเพื่อตรวจสอบความถูกต้อง
05:11 — บล็อก #88 (50a) ได้รับการลงนามและส่งไปยังโหนดอื่นเพื่อตรวจสอบความถูกต้อง
05:11 — บล็อก #85 (d3b) ได้รับการตรวจสอบและส่งไปยังรูทเชน
05:36 — บล็อก #85 (214) ได้รับการตรวจสอบและส่งไปยังรูทเชน
05:43 - โหนดทั้งหมดได้รับข้อมูลจากห่วงโซ่รูทที่บล็อก #88 (d3b), #89(214) ได้รับการเพิ่มแล้ว และเริ่มใช้ธุรกรรม 670k, 750k
06:50 — เนื่องจากการสื่อสารล้มเหลว บล็อก #85 (50a) จึงไม่ได้รับการยืนยัน
06:55 — ส่งโหนด #2 รับธุรกรรม 888145 รายการจากพูลและแบบฟอร์มบล็อก #90 (50a)
08:14 — บล็อก #90 (50a) ได้รับการลงนามและส่งไปยังโหนดอื่นเพื่อตรวจสอบความถูกต้อง
09:04 — บล็อก #90 (50a) ได้รับการตรวจสอบและส่งไปยังรูทเชน
11:23 - โหนดทั้งหมดได้รับข้อมูลจากห่วงโซ่รูทที่เพิ่มบล็อก #90 (50a) และเริ่มใช้ธุรกรรม 888145 ในเวลาเดียวกัน เซิร์ฟเวอร์ #3 ได้ใช้ธุรกรรมจากบล็อก #88 (d3b), #89(214) แล้ว
12:11 - สระว่ายน้ำทั้งหมดว่างเปล่า
13:41 — โหนดทั้งหมดของเซิร์ฟเวอร์ #3 มีธุรกรรมและโทเค็น 3 ล้านรายการ
14:35 — โหนดทั้งหมดของเซิร์ฟเวอร์ #1 มีธุรกรรมและโทเค็น 3 ล้านรายการ
19:24 — โหนดทั้งหมดของเซิร์ฟเวอร์ #2 มีธุรกรรมและโทเค็น 3 ล้านรายการ
อุปสรรค
ในระหว่างการพัฒนา Plasma Cash เราพบปัญหาต่อไปนี้ ซึ่งเราค่อยๆ แก้ไขและกำลังแก้ไข:
1. ความขัดแย้งในการโต้ตอบของฟังก์ชันต่างๆ ของระบบ ตัวอย่างเช่น ฟังก์ชั่นการเพิ่มธุรกรรมลงในพูลได้บล็อกการส่งและตรวจสอบความถูกต้องของบล็อก และในทางกลับกัน ซึ่งทำให้ความเร็วลดลง
2. ยังไม่ชัดเจนในทันทีว่าจะส่งธุรกรรมจำนวนมากพร้อมทั้งลดต้นทุนการถ่ายโอนข้อมูลให้เหลือน้อยที่สุดได้อย่างไร
3. ยังไม่ชัดเจนว่าจะจัดเก็บข้อมูลอย่างไรและที่ไหนเพื่อให้ได้ผลลัพธ์ที่สูง
4. ยังไม่ชัดเจนว่าจะจัดระเบียบเครือข่ายระหว่างโหนดอย่างไร เนื่องจากขนาดของบล็อกที่มีธุรกรรม 1 ล้านรายการจะใช้พื้นที่ประมาณ 100 MB
5. การทำงานในโหมดเธรดเดียวจะตัดการเชื่อมต่อระหว่างโหนดเมื่อมีการคำนวณที่ยาวนาน (เช่น การสร้างแผนผัง Merkle และการคำนวณแฮช)
เราจัดการกับเรื่องทั้งหมดนี้อย่างไร?
เวอร์ชันแรกของโหนด Plasma Cash เป็นแบบผสมผสานที่สามารถทำทุกอย่างได้ในเวลาเดียวกัน: ยอมรับธุรกรรม ส่งและตรวจสอบบล็อก และจัดเตรียม API สำหรับการเข้าถึงข้อมูล เนื่องจาก NodeJS เป็นแบบเธรดเดียว ฟังก์ชันการคำนวณ Merkle tree จำนวนมากจึงบล็อกฟังก์ชันเพิ่มธุรกรรม เราเห็นสองตัวเลือกในการแก้ปัญหานี้:
1. เรียกใช้กระบวนการ NodeJS หลายกระบวนการ ซึ่งแต่ละกระบวนการทำหน้าที่เฉพาะ
2. ใช้ worker_threads และย้ายการดำเนินการของโค้ดบางส่วนไปไว้ในเธรด
เป็นผลให้เราใช้ทั้งสองตัวเลือกในเวลาเดียวกัน: เราแบ่งโหนดหนึ่งอย่างมีเหตุผลออกเป็น 3 ส่วนที่สามารถทำงานแยกกัน แต่ในเวลาเดียวกันพร้อมกัน
1. โหนดการส่งซึ่งรับธุรกรรมลงพูลและสร้างบล็อก
2. โหนดตรวจสอบความถูกต้องของโหนด
3. โหนด API - จัดเตรียม API สำหรับการเข้าถึงข้อมูล
ในกรณีนี้ คุณสามารถเชื่อมต่อกับแต่ละโหนดผ่านซ็อกเก็ตยูนิกซ์โดยใช้ cli
เราย้ายการดำเนินการหนัก เช่น การคำนวณต้นไม้ Merkle ไปยังเธรดที่แยกจากกัน
ดังนั้นเราจึงบรรลุการทำงานปกติของฟังก์ชัน Plasma Cash ทั้งหมดพร้อมกันและไม่มีข้อผิดพลาด
เมื่อระบบทำงานได้ เราก็เริ่มทดสอบความเร็วและน่าเสียดายที่เราได้รับผลลัพธ์ที่ไม่น่าพอใจ: ธุรกรรม 5 รายการต่อวินาที และธุรกรรมสูงสุด 000 รายการต่อบล็อก ฉันต้องหาว่าอะไรถูกนำมาใช้อย่างไม่ถูกต้อง
ขั้นแรก เราได้เริ่มทดสอบกลไกการสื่อสารกับ Plasma Cash เพื่อค้นหาขีดความสามารถสูงสุดของระบบ เราได้เขียนไว้ก่อนหน้านี้ว่าโหนด Plasma Cash มีอินเทอร์เฟซซ็อกเก็ตยูนิกซ์ ในตอนแรกมันเป็นแบบข้อความ ออบเจ็กต์ json ถูกส่งโดยใช้ `JSON.parse()` และ `JSON.stringify()`
```json
{
"action": "sendTransaction",
"payload":{
"prevHash": "0x8a88cc4217745fd0b4eb161f6923235da10593be66b841d47da86b9cd95d93e0",
"prevBlock": 41,
"tokenId": "57570139642005649136210751546585740989890521125187435281313126554130572876445",
"newOwner": "0x200eabe5b26e547446ae5821622892291632d4f4",
"type": "pay",
"data": "",
"signature": "0xd1107d0c6df15e01e168e631a386363c72206cb75b233f8f3cf883134854967e1cd9b3306cc5c0ce58f0a7397ae9b2487501b56695fe3a3c90ec0f61c7ea4a721c"
}
}
```
เราวัดความเร็วการถ่ายโอนของวัตถุดังกล่าวและพบว่า ~ 130k ต่อวินาที เราพยายามแทนที่ฟังก์ชันมาตรฐานสำหรับการทำงานกับ json แต่ประสิทธิภาพไม่ดีขึ้น เครื่องยนต์ V8 จะต้องได้รับการปรับให้เหมาะสมที่สุดสำหรับการทำงานเหล่านี้
เราทำงานกับธุรกรรม โทเค็น และการบล็อกผ่านคลาสต่างๆ เมื่อสร้างคลาสดังกล่าวประสิทธิภาพลดลง 2 เท่าซึ่งบ่งชี้ว่า OOP ไม่เหมาะกับเรา ฉันต้องเขียนทุกอย่างใหม่ให้เป็นแนวทางที่ใช้งานได้จริง
การบันทึกลงในฐานข้อมูล
ในตอนแรก Redis ได้รับเลือกสำหรับการจัดเก็บข้อมูลให้เป็นหนึ่งในโซลูชันที่มีประสิทธิผลมากที่สุดที่ตรงกับความต้องการของเรา: พื้นที่จัดเก็บคีย์-ค่า การทำงานกับตารางแฮช ชุดต่างๆ เราเปิดตัว Redis-benchmark และมีการดำเนินการ ~80 ต่อวินาทีใน 1 โหมดไปป์ไลน์
เพื่อประสิทธิภาพสูง เราได้ปรับแต่ง Redis ให้ละเอียดยิ่งขึ้น:
- สร้างการเชื่อมต่อซ็อกเก็ตยูนิกซ์แล้ว
- เราปิดใช้งานการบันทึกสถานะลงในดิสก์ (เพื่อความน่าเชื่อถือ คุณสามารถตั้งค่าแบบจำลองและบันทึกลงดิสก์ใน Redis แยกต่างหากได้)
ใน Redis พูลคือตารางแฮชเพราะเราจำเป็นต้องดึงธุรกรรมทั้งหมดในแบบสอบถามเดียวและลบธุรกรรมทีละรายการ เราลองใช้รายการปกติ แต่จะช้ากว่าเมื่อยกเลิกการโหลดรายการทั้งหมด
เมื่อใช้ NodeJS มาตรฐาน ไลบรารี Redis จะได้รับประสิทธิภาพธุรกรรม 18 รายการต่อวินาที ความเร็วลดลง 9 เท่า
เนื่องจากเกณฑ์มาตรฐานแสดงให้เราเห็นว่าความเป็นไปได้นั้นยิ่งใหญ่กว่าถึง 5 เท่าอย่างชัดเจน เราจึงเริ่มเพิ่มประสิทธิภาพ เราเปลี่ยนไลบรารีเป็น ioredis และได้รับประสิทธิภาพ 25 ต่อวินาที เราได้เพิ่มธุรกรรมทีละรายการโดยใช้คำสั่ง `hset` ดังนั้นเราจึงสร้างข้อความค้นหาจำนวนมากใน Redis แนวคิดนี้เกิดขึ้นเพื่อรวมธุรกรรมเป็นชุดและส่งด้วยคำสั่งเดียว `hmset` ผลลัพธ์คือ 32k ต่อวินาที
ด้วยเหตุผลหลายประการซึ่งเราจะอธิบายด้านล่าง เราทำงานกับข้อมูลโดยใช้ `Buffer` และปรากฎว่า หากคุณแปลงเป็นข้อความ (`buffer.toString('hex')`) ก่อนเขียน คุณจะได้รับข้อมูลเพิ่มเติม ผลงาน. ดังนั้นความเร็วจึงเพิ่มขึ้นเป็น 35k ต่อวินาที ในขณะนี้ เราตัดสินใจระงับการเพิ่มประสิทธิภาพเพิ่มเติม
เราต้องเปลี่ยนไปใช้โปรโตคอลไบนารีเพราะ:
1. ระบบมักจะคำนวณแฮช ลายเซ็น ฯลฯ และด้วยเหตุนี้ ระบบจึงต้องการข้อมูลใน `Buffer
2. เมื่อส่งระหว่างบริการ ข้อมูลไบนารีจะมีน้ำหนักน้อยกว่าข้อความ ตัวอย่างเช่น เมื่อส่งบล็อกที่มีธุรกรรม 1 ล้านรายการ ข้อมูลในข้อความอาจกินพื้นที่มากกว่า 300 เมกะไบต์
3. การเปลี่ยนแปลงข้อมูลอย่างต่อเนื่องส่งผลต่อประสิทธิภาพ
ดังนั้นเราจึงใช้โปรโตคอลไบนารี่ของเราเองเป็นพื้นฐานในการจัดเก็บและส่งข้อมูล ซึ่งพัฒนาขึ้นบนพื้นฐานของไลบรารี 'ข้อมูลไบนารี' ที่ยอดเยี่ยม
เป็นผลให้เราได้รับโครงสร้างข้อมูลดังต่อไปนี้:
-ธุรกรรม
```json
{
prevHash: BD.types.buffer(20),
prevBlock: BD.types.uint24le,
tokenId: BD.types.string(null),
type: BD.types.uint8,
newOwner: BD.types.buffer(20),
dataLength: BD.types.uint24le,
data: BD.types.buffer(({current}) => current.dataLength),
signature: BD.types.buffer(65),
hash: BD.types.buffer(32),
blockNumber: BD.types.uint24le,
timestamp: BD.types.uint48le,
}
```
— โทเค็น
```json
{
id: BD.types.string(null),
owner: BD.types.buffer(20),
block: BD.types.uint24le,
amount: BD.types.string(null),
}
```
-ปิดกั้น
```json
{
number: BD.types.uint24le,
merkleRootHash: BD.types.buffer(32),
signature: BD.types.buffer(65),
countTx: BD.types.uint24le,
transactions: BD.types.array(Transaction.Protocol, ({current}) => current.countTx),
timestamp: BD.types.uint48le,
}
```
ด้วยคำสั่งปกติ `BD.encode(block, Protocol).slice();` และ `BD.decode(buffer, Protocol)` เราจะแปลงข้อมูลเป็น `Buffer` เพื่อบันทึกใน Redis หรือส่งต่อไปยังโหนดอื่นและดึงข้อมูล ข้อมูลกลับ
นอกจากนี้เรายังมี 2 โปรโตคอลไบนารี่สำหรับการถ่ายโอนข้อมูลระหว่างบริการ:
— โปรโตคอลสำหรับการโต้ตอบกับ Plasma Node ผ่านซ็อกเก็ตยูนิกซ์
```json
{
type: BD.types.uint8,
messageId: BD.types.uint24le,
error: BD.types.uint8,
length: BD.types.uint24le,
payload: BD.types.buffer(({node}) => node.length)
}
```
ที่ไหน:
- `พิมพ์` — การดำเนินการที่จะดำเนินการ เช่น 1 — sendTransaction, 2 — getTransaction;
- `น้ำหนักบรรทุก` — ข้อมูลที่ต้องส่งผ่านไปยังฟังก์ชันที่เหมาะสม
- `รหัสข้อความ` — รหัสข้อความเพื่อให้สามารถระบุการตอบกลับได้
— โปรโตคอลสำหรับการโต้ตอบระหว่างโหนด
```json
{
code: BD.types.uint8,
versionProtocol: BD.types.uint24le,
seq: BD.types.uint8,
countChunk: BD.types.uint24le,
chunkNumber: BD.types.uint24le,
length: BD.types.uint24le,
payload: BD.types.buffer(({node}) => node.length)
}
```
ที่ไหน:
- `รหัส` — รหัสข้อความ เช่น 6 — PREPARE_NEW_BLOCK, 7 — BLOCK_VALID, 8 — BLOCK_COMMIT;
- `เวอร์ชันโปรโตคอล` — เวอร์ชันโปรโตคอล เนื่องจากโหนดที่มีเวอร์ชันต่างกันสามารถถูกยกขึ้นบนเครือข่ายและสามารถทำงานได้แตกต่างกัน
- `ซีคิว` — ตัวระบุข้อความ
- `นับก้อน` и `chunkNumber` จำเป็นสำหรับการแยกข้อความขนาดใหญ่
- 'ความยาว' и `น้ำหนักบรรทุก` ความยาวและข้อมูลเอง
เนื่องจากเราพิมพ์ข้อมูลไว้ล่วงหน้า ระบบสุดท้ายจึงเร็วกว่าไลบรารี `rlp` ของ Ethereum มาก น่าเสียดายที่เรายังไม่สามารถปฏิเสธได้ เนื่องจากจำเป็นต้องสรุปสัญญาอัจฉริยะซึ่งเราวางแผนไว้ว่าจะทำในอนาคต
ถ้าเราบรรลุความเร็วได้ 35 000 ธุรกรรมต่อวินาที เรายังต้องประมวลผลในเวลาที่เหมาะสมอีกด้วย เนื่องจากเวลาในการสร้างบล็อกโดยประมาณจะใช้เวลา 30 วินาที เราจึงต้องรวมไว้ในบล็อกด้วย 1 000 000 ธุรกรรมซึ่งหมายถึงการส่งมากขึ้น 100 เมกะไบต์ของข้อมูล
ในตอนแรก เราใช้ไลบรารี `ethereumjs-devp2p` เพื่อสื่อสารระหว่างโหนด แต่ก็ไม่สามารถรองรับข้อมูลได้มากนัก ด้วยเหตุนี้ เราจึงใช้ไลบรารี `ws` และกำหนดค่าการส่งข้อมูลไบนารีผ่าน websocket แน่นอนว่าเรายังประสบปัญหาในการส่งแพ็กเก็ตข้อมูลขนาดใหญ่ แต่เราแบ่งมันออกเป็นชิ้น ๆ และตอนนี้ปัญหาเหล่านี้ก็หมดไป
สร้างต้นไม้ Merkle และคำนวณแฮชด้วย 1 000 000 การทำธุรกรรมต้องเกี่ยวกับ 10 วินาทีของการคำนวณต่อเนื่อง ในช่วงเวลานี้ การเชื่อมต่อกับโหนดทั้งหมดอาจขัดข้อง มีการตัดสินใจที่จะย้ายการคำนวณนี้ไปยังเธรดที่แยกจากกัน
สรุป:
อันที่จริงการค้นพบของเราไม่ใช่เรื่องใหม่ แต่ด้วยเหตุผลบางประการที่ผู้เชี่ยวชาญหลายคนลืมไปเมื่อทำการพัฒนา
- การใช้ Functional Programming แทน Object-Oriented Programming ช่วยเพิ่มประสิทธิภาพการทำงาน
- เสาหินนั้นแย่กว่าสถาปัตยกรรมบริการสำหรับระบบ NodeJS ที่มีประสิทธิผล
- การใช้ `worker_threads` สำหรับการคำนวณจำนวนมากช่วยเพิ่มการตอบสนองของระบบ โดยเฉพาะอย่างยิ่งเมื่อต้องจัดการกับการดำเนินการ i/o
- ซ็อกเก็ตยูนิกซ์มีเสถียรภาพและเร็วกว่าคำขอ http
- หากคุณต้องการถ่ายโอนข้อมูลขนาดใหญ่ผ่านเครือข่ายอย่างรวดเร็ว ควรใช้ websockets และส่งข้อมูลไบนารี่ โดยแบ่งออกเป็นชิ้นๆ ซึ่งสามารถส่งต่อได้หากมาไม่ถึง จากนั้นจึงรวมเป็นข้อความเดียว
เราขอเชิญคุณเยี่ยมชม GitHub โครงการ:
บทความนี้ร่วมเขียนโดย อเล็กซานเดอร์ นาชิวาน, นักพัฒนาอาวุโส
ที่มา: will.com