چگونه در سالیدیتی، قراردادهای هوشمند (نسخه‌‌بندی‌‌شده) قابل بروزرسانی بنویسیم؟

تغییرناپذیری یک ویژگی فوق‌‌العاده در بلاکچین است، اما همانند تمامی مسائل این دنیا، معایبی را هم به همراه دارد. این مقاله بر روی قابلیت کاربردپذیری دوباره و بروزرسانی قراردادهای هوشمند در پلتفرم‌‌های بلاکچین فعلی تمرکز دارد. این مقاله به طور کلی در درباره اتریوم که رایج‌‌ترین پلتفرم قرارداد هوشمند فعلی محسوب می‌‌شود، صحبت خواهد کرد. ابتدا به بررسی دلایل پشت محدودیت‌‌ها یا ناکارآمدی‌‌های قراردادهای هوشمند فعلی پرداخته، سپس درباره راه‌‌حل‌‌های جایگزین قابلیت بروزرسانی‌‌‌‌ یا نسخه‌‌بندی که امروزه در اکثر پلتفرم‌‌های متمرکز وجود دارد، صحبت خواهد شد.

0 21

مقدمه

سالیدیتی (با نگاه وسیع‌‌تر، EVM) هنوز راه بسیار زیادی در زمینه قدرت زبانی و کیفیت برنامه‌‌نویسی پیش روی خود دارد. اگر با اتریوم کار کرده باشید، قطعا از چنین موضوعی مطلع شده‌‌اید.

سالیدیتی یک زبان محدود است.

مخصوصا هنگامی که از سرزمین سوییفت و جاوا اسکریپت می‌‌آیید، از نظر میزان قدرت برنامه‌‌نویس از طرف زبان و قدرت خود زبان، توسعه در سالیدیتی قطعا رو به عقب حرکت کرده ‌‌است.

این مسئله گاهی باعث خرد شدن اعصاب شما می‌‌شود!

اما چرا محدود است؟

  • سالیدیتی و به طور کلی زبان‌‌هایی که با بایت‌‌کد سازگار هستند و باید در EVM اجرا شوند (که یک جعبه شن یا مکانیزم محافظتی است)، به این خاطر محدود هستند:
  • هنگام اجرا، کد شما در تمامی نودهای شبکه اجرا خواهد شد. وقتی که یک نود، بلاک جدید دریافت می‌‌کند، صحت و درستی آن را تایید خواهد کرد. در اتریوم این مسئله به معنای تایید تمام محاسباتی که در بلاک صورت گرفته و همچنین وضعیت قراردادهایی که باید صحیح باشند، خواهد بود.
  • با وجود اینکه EVM به صورت تورینگ کامل (Turing-complete) بوده، این مسئله سبب می‌‌شود تا محاسبات سنگین، گران شوند (یا به صورت مستقیم تحت تاثیر محدودیت Gas فعلی قرار بگیرند)، زیرا که تمامی نودها باید آن را اجرا کنند و به همین خاطر است که شبکه کند می‌‌شود.
  • هنوز کتابخانه استانداردی توسعه نیافته است. به‌‌خصوص چینش‌‌ها و مجموعه‌‌ها سخت هستند و من به شخصه برای انجام کارهای ابتدایی‌‌ام، باید مجموعه کتابخانه دست‌‌ساز خود را می‌‌ساختم.
  • نمی‌‌توانید داده‌‌ای را خارج از دنیا به دست آورید (خارج از EVM) مگر این که درون یک تراکنش (اوراکل) قرار بگیرد و وقتی که یک قرارداد به کار گرفته‌‌شد، غیرقابل بروزرسانی‌‌ خواهد بود (اما می‌‌توانید به فکر انتقال یا قراردادهای مطلق ذخیره باشید).

برخی از این محدودیت‌‌ها برای بقای پلتفرم رایانشی اتریوم ضروری هستند (هرگز نمی‌‌توانید یک پشتیبان برای تصاویر گوگل خود داشته باشید و شناخت تصویری مطلق را بر روی زنجیره انجام دهید و مشکلی بابت این موضوع وجود ندارد). دیگر محدودیت‌‌ها به این خاطر هستند که این فناوری هنوز بسیار جوان می‌‌باشد (اگرچه با سرعت خیره‌‌کننده‌‌ای رشد می‌‌کند) و با گذر زمان بهبود خواهد یافت.

بسیار خوب. حالا من برای حل این مشکل، چه کار باید بکنم؟!

هنگامی که بر روی یک پروژه کار می‌‌کردم و این پروژه در قراردادهای آتی نیازمند تغییر است، با کتابخانه رو به رو شدم. کتابخانه یک ویژگی سالیدیتی است که به ما در حل غیرمستقیم این مشکل کمک می‌‌کند. پیش از این که وارد پیاده‌‌سازی قرارداد قابل ‌‌بروزرسانی‌‌ شویم، بگذارید ببینیم کتابخانه چیست و چه محدودیت‌‌هایی دارد.

کتابخانه چیست و چرا به آن نیاز داریم؟

در سالیدیتی، کتابخانه نوع متفاوتی از قرارداد است که هیچ ذخیره‌‌ای نداشته و نمی‌‌تواند اتر نگهداری کند. گاهی می‌‌توان در EVM به کتابخانه همانند یک تک‌‌ورق (singleton)، کدی که می‌‌توان آن را از هر قراردادی بدون نیاز به پیاده‌‌سازی دوباره آن فرا خواند، نگاه کرد. این مسئله برخی از مشکلات بزرگ را حل می‌‌کند:

کاهش هزینه‌‌های Gas: این موضوع به وضوح باعث کاهش چشمگیر مقادیر Gas می‌‌شود، زیرا دیگر نیازی نیست که یک کد تکراری را بارها و بارها به کار گرفت و قراردادهای هوشمند مختلف را می‌‌توان با یک کتابخانه انجام داد.

تکرار کد در بلاکچین: این مسئله هم با توجیه بالا کاملا قابل درک است.

بروزرسانی‌‌‌‌های کد: اصلاحات ایرادهای قبلی و بروزرسانی‌‌‌‌ها باید به صورت مجزا از هم در هر پروژه صورت بگیرد (یا حتی بدتر، اتریوم باید هارد فورک پیدا کند تا مشکل قرارداد حل شود). حالا این مسئله حل شده‌‌است.

کتابخانه‌‌ها عالی به نظر می‌‌رسند، مگر نه؟ متاسفانه آن‌‌ها هم محدودیت‌‌هایی دارند. در بخش زیر برخی از مسائل مهم درباره کتابخانه‌‌ها آمده ‌‌است:

  • کتابخانه‌‌ها قابلیت ذخیره‌‌سازی ندارند.
  • کتابخانه‌‌ها می‌‌توانند ذخیره دیگر قراردادها را دستکاری کنند.
  • کتابخانه‌‌ها قابلیت‌‌های پرداختی ندارند.
  • کتابخانه‌‌ها نمی‌‌توانند یک عملکرد عقب‌‌نشینی انجام دهند.
  • کتابخانه‌‌ها لاگ سیستم عامل ندارند.
  • کتابخانه‌‌ها را می‌‌توان برای حذف لاگ‌‌های سیستم عامل قرارداد مورد استفاده آن به کار برد.
  • کتابخانه‌‌ها اجازه مالکیت ندارند.
  • با وجود این که کتابخانه‌‌ها نمی‌‌توانند به صورت مستقیم مالکیت داشته باشند، می‌‌توانند با دیگر کتابخانه‌‌ها متصل شده و از آن‌‌ها همانند یک قرارداد استفاده کنند، اما محدودیت‌‌های همیشگی کتابخانه‌‌ها را دارند.

این نکات در ابتدا کمی گیج‌‌کننده به نظر می‌‌رسند. اما نگران نباشید! در ادامه مطلب می‌‌توانید اطلاعات بیشتری درباره کتابخانه‌‌ها داشته باشید.

اما فعلا، ما فقط درباره بخش‌‌هایی صحبت خواهیم کرد که برای فهم و به کارگیری قراردادهای هوشمند قابل بروزرسانی‌‌ به آن‌‌ها نیاز داریم.

کتابخانه چگونه عمل می‌‌کند؟

کتابخانه نوعی از قرارداد است که امکان قابلیت‌‌های پرداختی و عملکرد عقب‌‌نشینی ندارد (این محدودیت‌‌ها در هنگام گردآوری اعمال می‌‌شود و به همین خاطر، نگهداری سرمایه کتابخانه‌‌ها را غیرممکن می‌‌کند). کتابخانه همانند قرارداد ({}C) با حرف اول کتابخانه ({}L) مشخص می‌‌شود.

فراخوانی یک عملکرد کتابخانه از دستورالعمل ویژه‌‌ای با عنوان DELEGATECALL استفاده خواهد کرد و سبب ایجاد مقوله‌‌ای در کتابخانه خواهد شد که در واقع خود قرارداد کد را اجرا کرده. من واقعا این زاویه اسناد سالیدیتی را دوست دارم:

کتابخانه‌‌ها را می‌‌توان به عنوان قراردادهای تلویحی پایه قراردادهای استفاده‌‌کننده از آن‌‌ها در نظر گرفت.

در اسنیپت بالا، هنگامی که عملکرد یک () قرارداد C فراخوانده شود، آدرس قرارداد و نه کتابخانه، بازگردانده خواهد شد. این مسئله برای تمام مشخصات msg یکسان خواهد بود: msg.sender, msg.value, msg.sig, msg.data and msg.gas. (سندسازی سالیدیتی مرتبط با این مسئله به شکل دیگری مرتبط است، اما بعد از انجام کمی آزمایش، به نظر می‌‌رسد که مقوله msg حفظ شود.)

نکته‌‌ای که می‌‌توانیم اینجا به آن توجه کنیم این است که نحوه ارتباط رده C و کتابخانه L مشخص نیست. پس بیایید آن را با هم بررسی کنیم.

کتابخانه‌‌ها چگونه به هم مرتبط هستند؟

( {}contract C is B) در قراردادی که به یک کتابخانه بستگی دارد، جدا از بحث انتقال صریح قرارداد، نحوه ارتباط قرارداد با کتابخانه چندان واضح نیست. در مثال بالا، قرارداد C از کتابخانه L در عملکرد ()a خود استفاده می‌‌کند، اما هیچ چیزی از کتابخانه مورد استفاده مشخص نیست و L در بایت‌‌کد C جمع نخواهد شد.

ارتباط کتابخانه در سطح بایت‌‌کد صورت می‌‌گیرد. هنگامی که قرارداد C جمع‌‌آوری می‌‌شود، این حالت، دارندگان برای آدرس کتابخانه، آماده می‌‌شود:

 

 

که به0dbe671f، امضای عملکرد برای ()a می‌‌گویند. اگر ما مجبور به ثابت نگه‌‌داشتن قرارداد C باشیم، پیاده‌‌سازی با شکست مواجه می‌‌شود، زیرا که بایت‌‌کد نامعتبر است.

به بیان ساده، ارتباط کتابخانه به سادگی جایگذاری تمام رخدادهای دارندگان کتابخانه در بایت‌‌کد قرارداد همراه با آدرس کتابخانه به کار گرفته‌‌شده در بلاکچین است. وقتی که قرارداد با کتابخانه مرتبط شد، می‌‌توان آن را به کار گرفت.

حالا که مباحث اساسی کتابخانه را بررسی کردیم، بگذارید ببینیم چگونه می‌‌توانیم از آن‌‌ها کمک بگیریم تا قراردادهای قابل بروزرسانی‌‌ ایجاد کنیم.

کتابخانه‌‌ها قابل بروزرسانی‌‌ نیستند

همانند قراردادها، کتابخانه‌‌ها هم قابل بروزرسانی‌‌ نیستند. همانطور که در بخش قبلی بیان شد، منبع کتابخانه در سطح بایت‌‌کد و نه در سطح ذخیره‌‌سازی ایجاد می‌‌شود. تغییر بایت‌‌کد یک قرارداد به محض به کارگیری آن ممکن نیست، به همین خاطر منبع کتابخانه تا هنگام بقای قرارداد، باقی خواهد ماند.

حتما با خود می‌‌گویید، چه کسی این ویژگی قابل بروزرسانی‌‌ مورد بحث ما را معرفی می‌‌کند؟

سرانجام، این موضوع چگونه کار می‌‌کند؟

در این قسمت کمی پیچیدگی وجود دارد. بیایید آن را با جزییات مشاهده کنیم:

Contracts

مدل قرارداد قابل بروزرسانی‌‌

به جای ارتباط مستقیم قرارداد اصلی کاربر با آدرس کتابخانه به کارگرفته‌‌شده، این قرارداد با یک قرارداد فرستنده مرتبط خواهد شد. در هنگام جمع‌‌آوری و به‌‌کارگیری، این مسئله مشکلی ندارد زیرا که فرستنده هیچکدام از رو‌‌های کتابخانه را پیاده نمی‌‌کند. این به آن معنا است که قرارداد فرستنده از هیچ کد کتابخانه در خود قرارداد استفاده نمی‌‌کند و بایت‌‌کد (مثل بایت‌‌کد قرارداد C که بالاتر دیدیم) مجبور به قرار دادن آدرس کتابخانه در بایت‌‌کد خود نیست. پس ما هیچ آدرسی را در سطح بایت‌‌کد، هارد کد (hardcoding) نمی‌‌کنیم و می‌‌توانیم هر زمان که خواستیم کتابخانه را با یک گزینه دیگر جابجا کنیم.

اما ما که از هیچ کد کتابخانه در قرارداد فرستنده استفاده نمی‌‌کنیم، چگونه عملکردهای کتابخانه را اجرا می‌‌کنیم؟

هنگامی که یک تراکنش وارد می‌‌شود، قرارداد اصلی (قرارداد توکنی) فکر می‌‌کند یک فراخوان تحویلی (delegatecall ) به کتابخانه آمده (TokenLib1) و به آن متصل شده ‌‌است. اما در عوض این فراخوان تحویلی برای قرارداد فرستنده ایجاد خواهد شد. اینجا نکته جذاب ماجرا وجود دارد. به محض این که فرستنده، فراخوان تحویلی را در عملکرد عقب‌‌نشینی خود دریافت کند، نسخه مناسب برای کد کتابخانه را تشخیص می‌‌دهد و فراخوان را دوباره با یک فراخوان تحویلی باز می‌‌گرداند. وقتی که کتابخانه بازگشت را انجام داد، این بازگشت به سمت قرارداد اصلی حرکت خواهد کرد.

این راه‌‌حل، فوق‌‌العاده عمل می‌‌کند اما محدودیت‌‌های جزیی هم دارد.

محدودیت‌‌ها

  • فرستنده باید اندازه حافظه برای بازگشت فراخوان کتابخانه را بداند. در حال حاضر، این مسئله با استفاده از مسیریابی امضاهای عملکردی به اندازه بازگشت آن‌‌ها حل می‌‌شود. این موضوع به صورت عمدی و به منظور حفظ سادگی در خارج از بحث حفظ می‌‌شود.
  • با توجه به نحوه کار فراخوان تحویلی در سطح EVM، می‌‌توانید فقط از قراردادی برای قرارداد دیگری استفاده کنید که اثر انگشت ذخیره‌‌سازی مشابهی داشته باشد. کتابخانه‌‌ها هیچ فضای ذخیره‌‌ای ندارند و ما فرستنده را بدون ذخیره نگه می‌‌داریم. به همین خاطر است که یک ذخیره فرستنده مجزا برای حفظ تمام داده‌‌های مورد نیاز وجود دارد. همچنین آدرس ذخیره فرستنده باید در بایت‌‌کد قرارداد فرستنده هارد کد شود.
  • توجه داشته باشید که برای قرارداد کاربری (قرارداد توکنی)، به هیچ چیز خاصی نیاز نیست، فقط به جای ارتباط با نسخه مستحکم کتابخانه، ارتباط با فرستنده ضروری خواهد بود.

شاید از این مطالب هم خوشتان بیاید.

ارسال پاسخ

آدرس ایمیل شما منتشر نخواهد شد.