การทดสอบสาธารณะ: โซลูชันเพื่อความเป็นส่วนตัวและความสามารถในการปรับขนาดบน Ethereum

บล็อกเชน เป็นเทคโนโลยีที่เป็นนวัตกรรมใหม่ที่สัญญาว่าจะปรับปรุงชีวิตมนุษย์ในด้านต่างๆ โดยจะถ่ายโอนกระบวนการและผลิตภัณฑ์จริงไปยังพื้นที่ดิจิทัล รับประกันความเร็วและความน่าเชื่อถือของธุรกรรมทางการเงิน ลดต้นทุน และยังช่วยให้คุณสร้างแอปพลิเคชัน DAPP สมัยใหม่โดยใช้สัญญาอัจฉริยะในเครือข่ายแบบกระจายอำนาจ

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

เพื่อให้มั่นใจถึงการกระจายอำนาจ ความปลอดภัย และความสามารถในการปรับขนาดในบล็อกเชน ซึ่งจะช่วยแก้ปัญหา Scalability Trilemma ซึ่งเป็นทีมพัฒนา โอกาส สร้าง Plasma Cash ซึ่งเป็นเครือข่ายย่อยที่ประกอบด้วยสัญญาอัจฉริยะและเครือข่ายส่วนตัวที่ใช้ Node.js ซึ่งจะถ่ายโอนสถานะไปยังเครือข่ายรูท (Ethereum) เป็นระยะ ๆ

การทดสอบสาธารณะ: โซลูชันเพื่อความเป็นส่วนตัวและความสามารถในการปรับขนาดบน Ethereum

กระบวนการสำคัญใน 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. หากการดำเนินการถอนโทเค็นเกิดขึ้นโดยมีการละเมิด (โทเค็นถูกใช้ไปหลังจากขั้นตอนการถอนเริ่มต้น หรือโทเค็นนั้นเป็นของคนอื่นอยู่แล้วก่อนการถอน) เจ้าของโทเค็นสามารถหักล้างการถอนได้ภายในสองสัปดาห์

การทดสอบสาธารณะ: โซลูชันเพื่อความเป็นส่วนตัวและความสามารถในการปรับขนาดบน Ethereum

ความเป็นส่วนตัวเกิดขึ้นได้สองวิธี

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 โครงการ: https://github.com/opporty-com/Plasma-Cash/tree/new-version

บทความนี้ร่วมเขียนโดย อเล็กซานเดอร์ นาชิวาน, นักพัฒนาอาวุโส เคลฟเวอร์ โซลูชั่น อิงค์.

ที่มา: will.com

เพิ่มความคิดเห็น