ماشین مجازی اتریوم EVM چیست؟
مقدمه
ماشین مجازی اتریوم (EVM) اصطلاحی است که وقتی در بلاک چین اتریوم (Ethereum) وقت میگذرانید و یا مشغول توسعه یک قرارداد هوشمند در آن بلاک چین هستید، با آن مواجه میشوید. اتریوم یک پلتفرم متن باز است که دارای کاربردهای زیادی است و افراد میتوانند برنامههای غیر متمرکز را بر روی این پلتفرم ایجاد کنند. اتریوم به مجموعهای از پروتکلها اشاره دارد که یک پلتفرم برای کاربردهای غیر متمرکز را تعریف میکنند. ماشین مجازی اتریوم در واقع قلب این شبکه است که در این نوشتار به توضیحاتی عمیق پیرامون آن پرداخته میشود.
ماشین مجازی اتریوم چیست؟
ماشینهای مجازی ماشینهایی هستند که پیچیدگی را بین دو عنصر ایجاد میکنند. این دو عنصر اجرای کد و اجرای ماشین هستند. این لایه خاص قابلیت حمل کلی نرم افزار را بهبود میبخشد. با استفاده از این ماشین، اطمینان حاصل میشود که برنامهها از یکدیگر و از میزبانشان جدا شدهاند.
ماشین مجازی اتریوم به عنوان یک ماشین کامل شبه تورینگ (Turing) دیده میشود. تکمیل تورینگ اصطلاحی است که به سیستمی از قوانین دستکاری شده اشاره دارد و نام خود را از آلان تورینگ (Alan Turing) گرفته است. تورینگ مردی است که ماشین نامگذاری (eponymous machine) را ایجاد کرده است. این ماشین یک مدل ریاضیاتی محاسبه است که ارائهدهنده یک ماشین انتزاعی است. آن اساسا نمادها را در یک باریکه نوار مطابق با یک مجموعه قوانین دستکاری میکند. این مدل معمولا خیلی ساده است و ماشین قادر است لوجیک (logic) الگوریتمی کامپیوتر را شبیهسازی کند.
زبانهای برنامهنویسی و واحدهای پردازشگر مرکزی دو نمونه برجسته از سیستمهایی هستند که به دادهها دسترسی داشته و آنها را تغییر میدهند. اگر این قوانین قادر به شبیهسازی محاسبه فرضی تورینگ باشند، این قوانین تورینگ کامل هستند. این سیستم میتواند هر نوع محاسبه یا برنامه کامپیوتری را اجرا کند.
پس اساسا یک ماشین تورینگ کامل از لحاظ ریاضیاتی قادر به حل هر مشکلی است که به آن معرفی شود. همان طور که قبلا اشاره شد، ماشین مجازی اتریوم شبیه این ماشین است. محاسباتی که ماشین مجازی اتریوم انجام میدهد به گس (gas) وابسته است. این اساسا به عنوان محدودهای برای تعداد کل محاسباتی که میتوان انجام داد، عملکرد دارد.
ایجاد قرارداد هوشمند
زبان برنامهنویسی اتریوم که معمولا قرارداد هوشمند توسط آن نوشته میشود سالیدیتی (Solidity) نام دارد. این زبان بسیار شبیه دو زبان دیگر جاوا اسکریپت (JavaScript) و سی پلاس پلاس (C++) است. از دیگر زبانهای برنامهنویسی که برای نوشتن قرارداد هوشمند مورد استفاده قرار میگیرند میتوان به Vyper و Bamboo اشاره کرد. ماشین مجازی اتریوم نمیتواند زبانهای مربوط به قرارداد هوشمند را که مستقیما به سالیدیتی شباهت دارند، اجرا نماید. در عوض، آنها در دستور العملهای ماشین سطح پایین تجمیع میشوند و به آنها کدهای عملکردی گفته میشود.
کدهای عملکردی
ماشین مجازی اتریوم از کدهای عملکردی برای انجام وظایفی خاص استفاده میکند. این کدها هستند که به ماشین مجازی اتریوم اجازه میدهند که رسما تورینگ کامل باشد. این یعنی که ماشین مجازی اتریوم در صورت داشتن منابع کافی میتواند هر چیزی را محاسبه کند. کدهای عملکردی یک بایتی (byte) هستند و این یعنی که حداکثر مقدار کدهای عملکردی ۲۵۶ است. به منظور سادگی میتوان همه این کدها را به انواع زیر تقسیمبندی کرد:
- کدهای عملکردی دستکاری پشته
- کدهای عملکردی bitwise/ مقایسهای/ حسابی
- کدهای عملکردی محیطی
- کدهای عملکردی دستکاری حافظه
- کدهای عملکردی دستکاری انبارش
- کدهای عملکردی مرتبط در جهت معکوس
- کدهای عملکردی توقفی
بایت کدها (Bytecodes)
بایت کدها مولفهای اساسی برای انبار کردن کارآمدتر کدهای عملکردی هستند. بایت کدها اساسا کدهای عملکردی هستند که به این صورت کدگذاری شدهاند. به هر یک از این کدهای عملکردی یک بایت اختصاص داده میشود. به عنوان مثال، بایت کد برای STOP به صورت 0x00 است. در اینجا و برای ایجاد یک تصویر روشن، از بایت کد 0x6001600101 استفاده میکنیم.
بایت کد طی فرآیند پیادهسازی به بایتهایی تقسیم میشود. بایتهایی که در دامنه 0x600x7f هستند، در معرض رفتار متفاوتی هستند زیرا آنها متشکل از دادههای انتقالی سرور هستند. این دادهها لازم است که به یک کد عملکردی الحاق شوند.
این دستور العمل در ابتدا 0x60 است و به PUSH1 ترجمه میشود. بعدا ما متوجه میشویم که دادههای انتقالی سرور یک بایت طول دارند و بنابراین بایت بعدی را به پشته وارد میکنیم. پشته حالا در مالکیت یک گزینه قرار دارد و ما میتوانیم به دستور العمل بعدی برویم. ما میدانیم که 0x01 بخشی از دستور العمل PUSH است. بنابراین دستور العمل بعدی که باید ایجاد کنیم (0x60 (PUSH1 است. اینک پشته دارای دو گزینه مشابه است.
آخرین دستور العمل 0x01 است که ترجمه آن ADD میشود. این دستور العمل قبل از انتقال گزینهها به پشته، ۲ گزینه را از پشته مورد استفاده قرار میدهد. اینک پشته دارای یک گزینه است و آن 0x02 است.
دستهبندی و ذخیره سازی
تعداد زیادی زبان برنامهنویسی سطح بالا وجود دارند که به کاربران اجازه میدهند که آرگومانها (arguments) را به شیوهای مستقیم به توابع انتقال دهند. از طرف دیگر، زبانهای برنامهنویسی سطح پایین از دسته بندی به عنوان ابزاری برای انتقال ارزش به توابع استفاده میکنند. ماشین مجازی اتریوم از یک پشته ثبت 256 بیتی (bit) استفاده میکند. در اینجاست که ۱۶ گزینه اخیر در دسترس قرار میگیرند و یا به صورت همزمان دستکاری میشوند. در نهایت، پشته قادر است تنها حدود ۱۰۲۴ گزینه را نگه دارد.
این محدودیتها منجر به کدهای عملکردی پیچیدهای میشود که از حافظه قرارداد استفاده میکنند. این استفاده همیشگی نیست و وقتی که اجرای قرارداد خاتمه مییابد، محتوای حافظه ذخیره نمیشود. پشته قابل مقایسه با آرگومانهای عملکردی است اما حافظه قابل مقایسه با متغیرهای تاییدکننده است.
تنها روش برای ذخیره سازی دائمی دادهها و دسترسی به اجرای قرارداد در آینده، استفاده از Storage است. ذخیره سازی قرارداد یا Contract Storage اساسا یک پایگاه داده عمومی است که در آنجا مقادیر برای خواندن خارجی موجود هستند. البته نوشتن در Storage به نسبت نوشتن در حافظه نسبتا گرانتر است.
هزینههای تعامل قرارداد هوشمند
اجرای قرارداد معمولا توسط آنهایی صورت میگیرد که به عنوان نود (node) اتریوم فعالیت دارند. به همین خاطر، یک مهاجم ممکن است اقدام به ساخت قرارداد کند. این نیازمند فعالیتهای محاسباتی زیاد و گرانی است که سرعت شبکه را به حداقل خواهد رساند. به منظور جلوگیری از این گونه حملات، هر کد عملکردی دارای یک هزینه گس مبنا است. کدهای عملکردی پیچیده مختلف همچنین هزینه گس پویایی نیز برای کاربران دارند.
اجازه دهید کد عملکردی KECCAK256 را به عنوان مثال به کار بریم. هزینه مبنای این کد عملکردی ۳۰ گس است و هزینه پویای آن ۶ گس برای هر کلمه است. دستور العملهای پیچیده معمولا به گس بیشتر و هزینه بیشتر نیاز دارند. علاوه بر این، هر تراکنش در ۲۱۰۰۰ گس شروع میشود.
وقتی که دستور العملها اجرا میشوند، گس قابل بازگشت خواهد بود. ایجاد یک ارزش انبارشی به عنوان صفر از غیر صفر حدود ۱۵۰۰۰ گس را جبران میکند. برعکس، برداشتن کامل یک قرارداد باعث تخفیف ۲۴۰۰۰ گس میشود. تکمیل اجرای قرارداد تنها زمانی است که بازگشت وجوه صورت میگیرد. بنابراین قراردادها قادر به پرداخت برای خودشان نیستند. علاوه بر این، بازگشت وجوه نباید از نصف گسی که قرارداد در حال جریان استفاده میکند، بیشتر شود.
استقرار قرارداد هوشمند
وقتی که قرارداد هوشمند مستقر شود، ایجاد یک تراکنش معمولی بدون حضور آدرس ممکن میشود. مقدار معینی بایت کد به عنوان داده ورودی داخل میشود. این بایت کد خاص به عنوان سازنده عمل میکند که متغیرهای اولیه را برای انبارش مینویسد. این نوشتن قبل از کپی کردن بایت کد آنی به کد قرارداد انجام میشود. در این میان، ایجاد بایت کد تنها یکبار انجام میشود، این در حالی است که بایت کد آنی در هر فراخوانی قرارداد خاصی اجرا میشود.
بایت کد بالا را میتوان به سه قسمت تجزیه کرد:
۱. سازنده: 60806040526001600055348015601457600080fd5b5060358060226000396000f3fe
۲. زمان اجرا: 6080604052600080fdfe
۳. متادیتا: A165627a7a723058204e048d6cab20eb0d9f95671510277b55a61a582250e04db7f6587a1bebc134d20029
در نهایت، سالیدیتی یک هش swarm از فایل متادیتا ایجاد میکند که در نهایت اضافه خواهد شد. Swarm یک پلتفرم ذخیره سازی توزیعی است که به عنوان سرویس انتشار محتوا عمل میکند. به زبان ساده میتوان گفت که آن یک سیستم ذخیره سازی فایل غیر متمرکز است. اگرچه هش Swarm در بایت کد زمان اجرا است، اما ماشین مجازی اتریوم آن را به عنوان کد عملکردی تشخیص نمیدهد. دلیل این امر این است که موقعیت آن کاملا غیر قابل دسترس است. سالیدیتی معمولا از این چیدمان خاص استفاده میکند:
0xa1 0x65 ‘b’ ‘z’ ‘z’ ‘r’ ‘0’ 0x58 0x20 [32 bytes swarm hash] 0x00 0x29
در این مورد خاص، میتوان هش Swarm را به صورت زیر مشتق کرد:
4e048d6cab20eb0d9f95671510277b55a61a582250e04db7f6587a1bebc134d2
فایل متادیتا متشکل از اطلاعات متعددی در ارتباط با قرارداد است. متاسفانه آن چیزی بیش از یک شکست آزمایشی نیست. علاوه بر این، قراردادهای زیادی وجود ندارند که به طور عمومی متاداده خود را به داخل شبکه Swarm آپلود کنند.
تجزیه بایت کدها
تعدادی از پروژههای خوب اقدام به ایجاد ابزارهایی کردهاند که با استفاده از آنها بتوان خواندن بایت کدها را راحتتر کرد. دو نمونه از این سرویسها که به تجزیه قرارداد در شبکه اصلی کمک میکنند eveem.org و ethervm.io هستند.
متاسفانه چندین بخش از منبع قرارداد اولیه مرتبا از دست میرود. این به دلیل بهینهسازی انجام شده توسط کامپایلر (compiler) است. علیرغم این، بخش عظیمی از نامهای عملکردی را میتوان با آزمون و خطا نشان داد. روشی که در اینجا انجام میگیرد، مرتبط کردن امضاهای عملکردی با مجموعه دادههای گستردهای است که شامل نام عملکردها و رویدادهای محبوب است.
فراخوانیهای قرارداد گاهی نیازمند یک محیط باینری کاربردی هستند. این شامل ورودی و خروجی مورد نیاز است. وقتی که نوبت به فراخوانی عملکرد قرارداد میرسد، تعیین امضای قرارداد توسط نام عملکرد هشینگ صورت میگیرد. این همچنین شامل ورودیهای خود است و علاوه بر این، هر چیزی را به جز چهار بایت اول کوتاه میکند.
در شکل بالا، تابع HelloWorld() به هش امضای 0x7fffb7bd پاسخ میدهد. هر آرگومانی که لازم است به تابع انتقال یابد را میتوان در بخشهای ۳۲ بایتی اضافه کرد. اینها کلمات هستند و به دنبال هش امضا در دادههای ورودی یک تراکنش میآیند.
اگر یک آرگومان حاوی دادههایی با ارزش بیش از ۳۲ بایت باشد، آرگومان به چند کلمه مختلف تقسیم میشود. این کلمات در ورودی دادهها و بعد از ورود آرگومانهای دیگر قرار میگیرند. علاوه بر این، اضافه کردن اندازه همه کلمات به عنوان کلمه دیگری موجود خواهد بود که قبل از همه کلمات آرایه قرار میگیرد. در مکان ورود آرگومان، موقعیت شروع کلمات آرایه در عوض اضافه خواهد شد.
سخن پایانی
ماشین مجازی اتریوم مولفه مهمی برای توسعه قراردادهای هوشمند است. درک آن برای افرادی که میخواهند در حوزه اتریوم و قرارداد هوشمند فعالیت داشته باشند، ضروری است. ساخت و توسعه قرارداد هوشمند نیازمند درک کامل و درست ماشین مجازی اتریوم است.
منبع : وال هالا کریپتو