خلاصهی کتاب
معرفی کتاب
Mastering Ethereum: Building Smart Contracts and DApps کتابی است از آندریاس آنتونوپولوس (Andreas M. Antonopoulos)، نویسندهی Mastering Bitcoin، و گَوین وود (Gavin Wood)، همبنیانگذار اتریوم و نویسندهی Yellow Paper، که انتشارات O’Reilly آن را منتشر کرده است. وود میخواست توضیحی در دسترستر از مقالهی ریاضیوار Yellow Paper بنویسد؛ حاصل کار کتابی شده که از «اتریوم چیست؟» شروع میکند و تا نوشتن قرارداد هوشمند، امنیت، توکنها، اوراکلها، ماشین مجازی اتریوم و اجماع پیش میرود.
این کتاب برای برنامهنویسی است که میخواهد اتریوم را از پایه و در یک مسیر پیوسته بفهمد، نه فقط چند نمونهکد کپی کند. مباحث رمزنگاری، کیفپول و تراکنشها هم برای کسی که صرفاً کاربر است مفیدند. فصلهای Solidity و امنیت قراردادها بهویژه برای کسی ارزشمندند که میخواهد وارد توسعهی قرارداد هوشمند یا ممیزی امنیتی شود.
چاپها و دسترسی
- چاپ اول (۲۰۱۸): خلاصهی این صفحه بر پایهی همین چاپ نوشته شده است. متن آن در مخزن عمومی github.com/ethereumbook/ethereumbook با مجوز Creative Commons (CC BY-NC-ND 4.0) در دسترس است.
- چاپ دوم (اکتبر ۲۰۲۵): با مشارکت کارلو پاریزی، الساندرو مازا و نیکولو پوتسولینی منتشر شده و بهروزتر است.
پیش از خواندن: این صفحه ترجمهی کتاب نیست؛ خلاصهای مستقل و مفصل به زبان فارسی است و جای متن اصلی را نمیگیرد. چون بر پایهی چاپ اول (۲۰۱۸) است، بعضی بخشها (مثل توضیح اجماع اثبات کار، ابزارها و نسخههای Solidity که نمونهکدها برای ۰٫۴ تا ۰٫۵ نوشته شدهاند) با وضعیت امروز اتریوم فرق دارند. برای جزئیات دقیق، کدها و وضعیت روز، به چاپ دوم یا مستندات رسمی مراجعه کنید.
این خلاصه به چه درسهایی کمک میکند؟
فصل ۷ (Solidity) و فصل ۹ (امنیت قراردادهای هوشمند) به مباحث دورههای «مبانی سالیدیتی» (B1) و «مبانی امنیت قرارداد هوشمند» (A1) نزدیکاند؛ فصل ۱۰ (توکنها) و ۱۱ (اوراکلها) به مباحث توسعهی dApp و یکپارچهسازی. این دورهها در فهرست انتظار هستند.
فهرست خلاصهی فصلها
- پیشگفتار — خلاصه
- فصل ۱ — اتریوم چیست؟ (What Is Ethereum?)
- فصل ۲ — مبانی اتریوم (Ethereum Basics)
- فصل ۳ — کلاینتهای اتریوم (Ethereum Clients)
- فصل ۴ — رمزنگاری (Cryptography)
- فصل ۵ — کیفپولها (Wallets)
- فصل ۶ — تراکنشها (Transactions)
- فصل ۷ — قراردادهای هوشمند و Solidity
- فصل ۸ — قراردادهای هوشمند و Vyper
- فصل ۹ — امنیت قراردادهای هوشمند
- فصل ۱۰ — توکنها (Tokens)
- فصل ۱۱ — اوراکلها (Oracles)
- فصل ۱۲ — برنامههای غیرمتمرکز (DApps)
- فصل ۱۳ — ماشین مجازی اتریوم (EVM)
- فصل ۱۴ — اجماع (Consensus)
- پیوست A — تاریخچهٔ فورکهای اتریوم
- پیوست B — استانداردهای اتریوم (EIP و ERC)
- پیوست C — Opcodeهای EVM و مصرف گس
- پیوست D — ابزارها، فریمورکها و کتابخانههای توسعه
- پیوست E — آموزش web3.js
پیشگفتار — خلاصه
توجه: این فایل ترجمهٔ کتاب نیست؛ خلاصهای مستقل و به قلم من از محتوای کتاب Mastering Ethereum (نوشتهٔ آندریاس آنتونوپولوس و دکتر گَوین وود، انتشارات اُرایلی، ۲۰۱۸) است. کتاب تحت مجوز CC BY-NC-ND 4.0 منتشر شده است. برای مطالعهٔ متن کامل به نسخهٔ اصلی یا https://github.com/ethereumbook/ethereumbook مراجعه کنید.
پیشینهٔ کتاب
این کتاب حاصل همکاری دو نفر است: گَوین وود، همبنیانگذار و مدیر فنی نخستین اتریوم و نویسندهٔ «Yellow Paper»، و آندریاس آنتونوپولوس، نویسندهٔ کتاب پرفروش Mastering Bitcoin. وود میخواست توضیحی در دسترستر از مقالهٔ پرنماد و ریاضیوار Yellow Paper بنویسد. خوانندگان آنتونوپولوس هم پس از انتشار Mastering Bitcoin مدام میپرسیدند نسخهٔ اتریومی آن کِی میآید. در مه ۲۰۱۶ این دو در یک شهر دیدار کردند و تصمیم گرفتند کتاب را بهصورت همکاری جمعی و با مجوز متنباز (Creative Commons) بنویسند. ناشر هم با این شیوه موافقت کرد و صدها مشارکتکننده در تولید آن سهم داشتند.
نحوهٔ استفاده از کتاب
- کتاب هم بهعنوان مرجع و هم بهعنوان روایت پیوسته از ابتدا تا انتها طراحی شده است. به همین دلیل برخی مفاهیم (مثلاً گس) عمداً چند بار تکرار میشوند: یک بار در ابتدا برای فهمیدن مطالب بعدی و یک بار در بخشی مستقل با عمق بیشتر.
- دو فصل اول مقدمهای ملایماند و برای مبتدیان هم قابل پیگیری هستند. از فصل سوم به بعد مخاطب اصلی برنامهنویساناند و بحثهای فنی و نمونهکد زیادی وجود دارد.
- نمایهٔ پایان کتاب برای یافتن سریع موضوعات بر اساس کلیدواژه است.
مخاطب
مخاطب اصلی کتاب برنامهنویسان هستند: کسانی که میخواهند بفهمند بلاکچینهای قرارداد هوشمند چگونه کار میکنند، چگونه از آنها استفاده کنند و چگونه قرارداد هوشمند و برنامهٔ غیرمتمرکز (DApp) بسازند. فصلهای ابتدایی برای غیربرنامهنویسها هم یک مقدمهٔ عمیق محسوب میشود.
قراردادهای نگارشی و کد
- نمونهکدها به Solidity، Vyper و JavaScript و همچنین دستورات خط فرمان سیستمهای یونیکسمانند نوشته شدهاند و همگی در مخزن گیتهاب کتاب (پوشهٔ
code) در دسترساند. - برخی خطوط کد برای چاپ شکسته شدهاند (با بکاسلش و خط جدید). هنگام تایپ مجدد باید این دو نویسه حذف و خطوط به هم متصل شوند.
- مقادیر در مثالها واقعیاند: کلیدهای خصوصی/عمومی، آدرسها، تراکنشها، قراردادها و بلاکها واقعاً روی بلاکچین اتریوم وجود دارند و میتوان آنها را بررسی کرد.
هشدار مهم دربارهٔ آدرسها و کلیدهای کتاب
کلیدهای خصوصی چاپشده در کتاب «سوخته» هستند، یعنی هر خوانندهای به آنها دسترسی دارد. هرگز به آدرسهای کتاب پول نفرستید؛ یا برای همیشه از دست میرود یا یکی از خوانندگان آن را برمیدارد.
مجوز و استفاده از کد
- نمونهکدها را معمولاً میتوان در برنامهها و مستندات خود به کار برد؛ فقط استفادهٔ حجیم نیاز به مجوز دارد.
- ذکر منبع (عنوان، نویسندگان، ناشر، ISBN) توصیه میشود اما اجباری نیست.
- خود کتاب تحت مجوز CC BY-NC-ND 4.0 (انتساب، غیرتجاری، بدون اثر مشتق) عرضه شده است.
- نسخهٔ باز کتاب و ترجمههای رسمی در https://ethereumbook.info/ معرفی میشوند.
قدردانیها
هر دو نویسنده در بخش قدردانی از مشارکتکنندگان، ویراستاران، بازبینهای فنی، پشتیبانان مالی (از جمله حامیان ماهانهٔ آندریاس در Patreon) و تیم ناشر تشکر کردهاند.
فصل ۱ — اتریوم چیست؟ (What Is Ethereum?)
نگاه کلی
اتریوم را اغلب «کامپیوتر جهانی» مینامند. فصل اول این عبارت را از دو منظر باز میکند:
- منظر علوم کامپیوتر: اتریوم یک ماشین حالت (state machine) قطعی (deterministic) اما عملاً نامحدود است. این ماشین از یک حالت سراسری واحد (singleton) تشکیل شده که همه میتوانند به آن دسترسی داشته باشند، بهعلاوهٔ یک ماشین مجازی که تغییرات را روی این حالت اعمال میکند.
- منظر عملی: اتریوم یک زیرساخت محاسباتی متنباز و غیرمتمرکز در سطح جهان است که برنامههایی به نام قرارداد هوشمند (smart contract) را اجرا میکند. تغییرات حالت سیستم با یک بلاکچین همگامسازی و ذخیره میشود و ارز رمزنگاریِ اتر (ether) هزینهٔ منابع اجرایی را اندازهگیری و محدود میکند.
حاصل این طراحی، امکان ساخت برنامههای غیرمتمرکزی با کارکردهای اقتصادی درونی است که دسترسپذیری بالا، قابلیت حسابرسی، شفافیت و بیطرفی دارند و سانسور و برخی ریسکهای طرفمقابل (counterparty risk) را کاهش یا حذف میکنند.
مقایسه با بیتکوین
اتریوم عناصر مشترک زیادی با بلاکچینهای باز دیگر دارد: شبکهٔ همتابههمتا، الگوریتم اجماع مقاوم در برابر خطای بیزانسی (در آن زمان اثبات کار)، ابزارهای رمزنگاری مانند امضای دیجیتال و هش، و یک ارز دیجیتال. اما هدف و ساختار آن تفاوتهای بنیادی دارد:
- هدف اصلی اتریوم شبکهٔ پرداخت نیست. اتر یک «ارز کاربردی» (utility) برای پرداخت هزینهٔ استفاده از پلتفرم است.
- زبان اسکریپت بیتکوین عمداً محدود است و فقط شرایط خرجکردن را بهصورت درست/نادرست ارزیابی میکند. اتریوم یک بلاکچین همهمنظوره و برنامهپذیر است که زبان آن تورینگکامل است و کدی با پیچیدگی دلخواه را اجرا میکند.
اجزای یک بلاکچین
یک بلاکچین باز و عمومی معمولاً این اجزا را دارد:
- شبکهٔ همتابههمتا (P2P) با پروتکل شایعهپراکنی (gossip) استاندارد برای انتشار تراکنشها و بلاکها؛
- پیامها در قالب تراکنش که نمایندهٔ گذار حالت (state transition) هستند؛
- مجموعه قواعد اجماع برای تعریف تراکنش و گذار معتبر؛
- ماشین حالت که تراکنشها را طبق قواعد پردازش میکند؛
- زنجیرهای از بلاکهای امنشده با رمزنگاری بهعنوان دفتر روزنامهٔ گذارهای تأییدشده؛
- الگوریتم اجماع که کنترل را غیرمتمرکز میکند و شرکتکنندگان را به همکاری در اجرای قواعد وا میدارد؛
- نظام مشوقهای مبتنی بر نظریهٔ بازی (مثلاً هزینهٔ اثبات کار بهعلاوهٔ پاداش بلاک) برای امنیت اقتصادی؛
- یک یا چند پیادهسازی متنباز (کلاینت).
تفاوت مهمی با بیتکوین: در بیتکوین یک پیادهسازی مرجع (Bitcoin Core) وجود دارد، ولی در اتریوم یک مشخصات مرجع (Yellow Paper، توصیف ریاضی سیستم) هست و چند کلاینت مطابق آن ساخته شدهاند. نویسندگان تأکید میکنند که واژهٔ «بلاکچین» امروز مبهم است؛ وقتی کسی میگوید «این یک بلاکچین است»، هنوز پاسخی نداده است. باید پرسید کدام اجزا را دارد و آیا باز، عمومی، جهانی، غیرمتمرکز، بیطرف و مقاوم در برابر سانسور است یا نه.
تولد اتریوم
- توسعهدهندگان در آن زمان دو راه داشتند: ساختن روی بیتکوین (با محدودیتهای عمدی نوع تراکنش، نوع داده و اندازهٔ ذخیرهسازی که غالباً به لایههای برونزنجیرهای نیاز دارد و مزایای بلاکچین عمومی را تضعیف میکند) یا ساختن یک بلاکچین کاملاً جدید (که کار زیرساختی و آزمایش فراوان میخواست).
- پایان ۲۰۱۳: ویتالیک بوترین که به گسترش قابلیتهای بیتکوین و Mastercoin (پروتکلی رویِ بیتکوین برای قراردادهای ابتدایی) فکر میکرد، در اکتبر پیشنهادی انعطافپذیرتر و اسکریپتپذیر (غیرتورینگکامل) به تیم Mastercoin داد که برای نقشهٔ راه آنها بیش از حد رادیکال بود.
- دسامبر ۲۰۱۳: او وایتپیپر اتریوم را منتشر کرد: یک بلاکچین تورینگکامل و همهمنظوره. هر دو نویسندهٔ کتاب پیشنویس را دیدند؛ آنتونوپولوس سؤالهای زیادی دربارهٔ اجرای قرارداد و زبان تورینگکامل پرسید ولی بعداً وارد پروژه شد، و گَوین وود از نخستین کسانی بود که با مهارت C++ پیشنهاد کمک داد و همبنیانگذار، همطراح و مدیر فنی شد.
- وود نقش بزرگی در تغییر نگاه از «پول برنامهپذیر» به «پلتفرم محاسبات همهمنظوره» و در ایدهٔ «Web3» داشت (اتریوم همراه Whisper و Swarm).
- هدف بنیانگذاران بلاکچینی بدون هدف خاص بود تا توسعهدهندگان بتوانند برنامهٔ خود را بدون بازساخت P2P، بلاکچین و اجماع بنویسند و فقط یک محیط برنامهنویسی امن و قطعی تحویل بگیرند.
- نخستین بلاک اتریوم در ۳۰ ژوئیهٔ ۲۰۱۵ استخراج شد.
چهار مرحلهٔ توسعه
توسعه در چهار مرحله با نام رمز برنامهریزی شده است و هر مرحله ممکن است شامل هاردفورک (تغییر ناسازگار با گذشته) باشد:
| مرحله / فورک | توضیح مختصر |
|---|---|
| Frontier (بلاک ۰) | مرحلهٔ آغازین، از ژوئیهٔ ۲۰۱۵ تا مارس ۲۰۱۶ |
| Ice Age (بلاک ۲۰۰٬۰۰۰) | افزایش نمایی دشواری برای تشویق به گذار به اثبات سهام |
| Homestead (بلاک ۱٬۱۵۰٬۰۰۰) | مرحلهٔ دوم، مارس ۲۰۱۶ |
| DAO (بلاک ۱٬۱۹۲٬۰۰۰) | بازپرداخت قربانیان هک قرارداد DAO؛ منجر به انشعاب اتریوم و اتریوم کلاسیک شد |
| Tangerine Whistle (۲٬۴۶۳٬۰۰۰) | تغییر هزینهٔ گس برای عملیات پرخرج I/O و پاکسازی حالت پس از حملهٔ DoS |
| Spurious Dragon (۲٬۶۷۵٬۰۰۰) | رفع بردارهای حملهٔ DoS بیشتر، پاکسازی دوباره و محافظت در برابر حملهٔ بازپخش |
| Metropolis / Byzantium (۴٬۳۷۰٬۰۰۰) | مرحلهٔ سوم (اکتبر ۲۰۱۷)، شامل Byzantium و سپس Constantinople |
| Serenity | مرحلهٔ نهایی (برنامهریزیشده) |
اتریوم: بلاکچین همهمنظوره
بلاکچین بیتکوین مالکیت واحدهای بیتکوین را ردیابی میکند؛ یک ماشین حالت اجماعی توزیعشده که تراکنشها مالکیت سکهها را تغییر میدهند. اتریوم نیز ماشین حالت توزیعشده است اما بهجای ردیابی فقط مالکیت ارز، گذارهای حالت یک ذخیرهگاه دادهٔ همهمنظوره از جفتهای کلید–مقدار را دنبال میکند. شبیه حافظهٔ RAM کامپیوتر، با این فرق که هم کد و هم داده را نگه میدارد و تغییرات آن با بلاکچین ردیابی میشود. دو تفاوت کلیدی با کامپیوترهای معمولی: تغییرات حالت تابع قواعد اجماع است و حالت بهصورت سراسری توزیع شده است.
اجزای اتریوم (نگاشت به اجزای بلاکچین)
- شبکهٔ P2P: شبکهٔ اصلی روی درگاه TCP شمارهٔ ۳۰۳۰۳ و با پروتکل ÐΞVp2p.
- قواعد اجماع: در مشخصات مرجع (Yellow Paper).
- تراکنشها: پیامهای شبکه با فرستنده، گیرنده، مقدار و بارِ داده.
- ماشین حالت: ماشین مجازی اتریوم (EVM)، ماشین پشتهای که بایتکد را اجرا میکند؛ قراردادها به زبانهای سطح بالا مانند Solidity نوشته و به بایتکد کامپایل میشوند.
- ساختار داده: پایگاه داده محلی (معمولاً LevelDB) شامل ساختار هششدهٔ درخت مرکل پاتریشیا.
- الگوریتم اجماع: اجماع ناکاموتو (زنجیرهٔ طولانیتر با وزن اثبات کار)، با برنامهٔ حرکت به اثبات سهام (Casper).
- امنیت اقتصادی: الگوریتم اثبات کار Ethash (که قرار است کنار گذاشته شود).
- کلاینتها: Geth (نسخهٔ Go) و Parity از شناختهشدهترینها.
اتریوم و تورینگکامل بودن
- آلن تورینگ در ۱۹۳۶ مدلی ریاضی از کامپیوتر ارائه داد (ماشین حالتی که روی نوار نامحدود میخواند و مینویسد) و ثابت کرد که مسئلهٔ توقف (halting problem) حلناپذیر است: نمیتوان برای برنامهای دلخواه از پیش گفت که متوقف میشود یا نه.
- سیستمی تورینگکامل است که بتواند هر ماشین تورینگ را شبیهسازی کند (ماشین تورینگ جهانی). EVM با توانایی اجرای برنامهٔ ذخیرهشده و خواندن/نوشتن حافظه چنین است (با محدودیت حافظهٔ متناهی).
- نوآوری اتریوم: ترکیب معماری رایانهٔ برنامهٔ ذخیرهشده با بلاکچین غیرمتمرکز؛ برنامهها «همهجا» اجرا میشوند ولی حالتی مشترک و تحت اجماع میسازند.
تورینگکامل بودن بهعنوان «ویژگی»
دستیابی به تورینگکامل بودن بسیار ساده است (سادهترین ماشین شناختهشده فقط ۴ حالت و ۶ نماد دارد و حتی گاهی سیستمها «تصادفاً» تورینگکامل میشوند). اما در سامانههای با دسترسی آزاد خطرناک است؛ مثل چاپگری که با فایل مخرب قفل میکند، با این تفاوت که بلاکچین را نمیتوان خاموش و روشن کرد.
پیامدها و راهحل گس
چون نمیتوان پیش از اجرا دانست که قرارداد متوقف میشود و چهقدر منابع میخورد، هر گرهای که تراکنش را اعتبارسنجی کند میتواند قربانی حلقهٔ بیپایان (حملهٔ DoS) شود. اتریوم برای حل این مشکل گس (gas) را معرفی کرد:
- هر دستور EVM هزینهٔ ثابتی به واحد گس دارد.
- فرستندهٔ تراکنش سقفی برای گس تعیین میکند؛ اگر مصرف از آن بگذرد، اجرا متوقف میشود.
- گس را فقط با اتر و بهعنوان بخشی از تراکنش میتوان خرید و قیمت آن ثابت نیست.
- گس مصرفنشده به فرستنده بازگردانده میشود.
از بلاکچین همهمنظوره به DApp
یک DApp دستکم از «قرارداد هوشمند روی بلاکچین» و «رابط کاربری وب» تشکیل میشود و اغلب اجزای غیرمتمرکز دیگری مثل ذخیرهسازی P2P (Swarm) و پیامرسانی P2P (Whisper) را هم دارد. (نماد Ð مخفف «ETH» است و گاهی DApp را «ÐApp» مینویسند.)
نسل سوم اینترنت (Web3)
اصطلاح Web 2.0 در ۲۰۰۴ رایج شد و به محتوای تولیدشده توسط کاربر و تعاملپذیری اشاره داشت. Web3 که گَوین وود مطرح کرد، یعنی گذار از برنامههای متمرکز به برنامههای مبتنی بر پروتکلهای غیرمتمرکز. کتابخانهٔ web3.js پلی میان جاوااسکریپت در مرورگر و بلاکچین است و رابطی برای Swarm و Whisper دارد.
فرهنگ توسعه در اتریوم
- بیتکوین محافظهکار است و تغییرات اغلب باید سازگار با گذشته باشند.
- فرهنگ اتریوم رو به آینده است («سریع حرکت کن و خراب کن» با لحنی نیمهشوخی): تغییرات لازم حتی با شکستن سازگاری اعمال میشود.
- پیامد برای توسعهدهنده: باید انعطافپذیر بود. قراردادها تغییرناپذیرند و «ارتقا» ندارند؛ باید قرارداد جدید را مستقر کرد و کاربران و داراییها را مهاجرت داد. در عمل این یعنی اهداف خودمختاری و عدم تمرکز کامل هنوز محقق نشده و باید مقداری کنترل بر قراردادها نگه داشت.
- سرعت بالا یعنی فرصتی برای «bike-shedding» (بحث بر سر جزئیات بیاهمیت) نیست.
چرا اتریوم را بیاموزیم؟
یادگیری بلاکچین سخت است چون رشتههای مختلفی (برنامهنویسی، امنیت اطلاعات، رمزنگاری، اقتصاد، سیستمهای توزیعشده، شبکههای P2P) را ترکیب میکند. اتریوم شیب این یادگیری را ملایمتر میکند و برای توسعهدهندگان جاوااسکریپت راه ورود سریعی دارد؛ جامعهای که از هر بلاکچین دیگری سریعتر رشد میکند. اما این یک شمشیر دو لبه است: نوشتن کد ساده است، نوشتن کد خوب و امن دشوار است.
کتاب چه چیزی میآموزد
با یک تراکنش ساده شروع میکنیم، آن را کالبدشکافی میکنیم، قرارداد سادهای میسازیم، بهترش میکنیم و مسیر آن را در سیستم اتریوم دنبال میکنیم؛ هم «چگونه» و هم «چرا» طراحیها چنیناند.
منابع پیشنهادی فصل
Yellow Paper، Beige Paper (بازنویسی سادهتر)، پروتکل ÐΞVp2p، مستندات EVM، LevelDB، درختهای مرکل پاتریشیا، Ethash، راهنمای Casper، و کلاینتهای Geth و Parity.
فصل ۲ — مبانی اتریوم (Ethereum Basics)
این فصل یک مسیر عملی و گامبهگام است: ساخت کیف پول، دریافت و ارسال اتر آزمایشی، نوشتن و استقرار نخستین قرارداد هوشمند و تعامل با آن.
واحدهای پول اتر
- ارز اتریوم اتر (ether) با نماد ETH یا Ξ است. «اتریوم» نام سیستم است، نه ارز (اشتباه رایج مبتدیها).
- کوچکترین واحد wei است و ۱ اتر = ۱۰¹۸ wei. مقدار پول در پروتکل همیشه یک عدد صحیح بدون علامت بر حسب wei است.
- واحدها نام علمی (SI) و نام رایج دارند که بزرگان رایانه و رمزنگاری را گرامی میدارد: Babbage (۱۰³)، Lovelace (۱۰⁶)، Shannon (۱۰⁹ که همان gwei است)، Szabo (۱۰¹²)، Finney (۱۰¹⁵) و Ether (۱۰¹⁸).
انتخاب کیف پول
«کیف پول» در این کتاب یعنی نرمافزاری که حساب اتریوم شما را مدیریت میکند: کلیدها را نگه میدارد و تراکنش میسازد و پخش میکند. چهار گزینهٔ معرفیشده:
- MetaMask: افزونهٔ مرورگر (Chrome/Firefox/Opera/Brave)، مناسب آزمایش و اتصال به شبکهها و گرههای مختلف.
- Jaxx: چندسکهای و چندپلتفرمی (موبایل و دسکتاپ)، ساده برای کاربر تازهکار.
- MyEtherWallet (MEW): کیف پول تحت وب با امکانات پیشرفته.
- Emerald Wallet: متنباز و دسکتاپ، طراحیشده برای اتریوم کلاسیک ولی سازگار با زنجیرههای مبتنی بر اتریوم؛ میتواند گره کامل یا حالت سبک داشته باشد.
تغییر کیف پول آسان است (انتقال وجه یا صادرکردن کلید). انتخاب اینها تأیید کیفیت یا امنیت نیست. چون کیف پول به کلید خصوصی دسترسی دارد، فقط از منابع معتبر دانلود کنید و دارایی را بین چند کیف پول پخش کنید.
کنترل و مسئولیت
در سامانهٔ غیرمتمرکز هر کاربر میتواند و باید کلید خصوصی خود را کنترل کند. اصل ساده: یک کلید خصوصی = یک «حساب». هر که کلید را ندارد، دسترسی هم ندارد، و گمکردن کلید یعنی از دست رفتن دائمی دارایی. توصیههای امنیتی فصل:
- امنیت را بداهه نسازید؛ از روشهای استاندارد و آزموده استفاده کنید.
- هرچه حساب مهمتر، تدابیر امنیتی قویتر (بالاترین سطح: دستگاه جداشده از شبکه یا air-gapped).
- کلید خصوصی را هرگز بهصورت متن ساده و دیجیتال ذخیره نکنید؛ فایل رمزگذاریشدهٔ keystore با رمز قوی استفاده کنید.
- رمز عبور را در سند، عکس، اسکرینشات یا درایو آنلاین نگه ندارید؛ از مدیر رمز یا کاغذ استفاده کنید.
- عبارت یادآور (mnemonic) را روی کاغذ پشتیبان بگیرید و جای امن نگه دارید، نه دیجیتال.
- پیش از انتقال مبلغ بزرگ یا به آدرس تازه، یک تراکنش آزمایشی کوچک انجام دهید.
- مرورگرهای بلاک (block explorer) راحتاند ولی با آشکارکردن آدرسها به حریم خصوصی لطمه میزنند.
- به آدرسهای داخل کتاب پول نفرستید.
شروع با MetaMask
نصب از فروشگاه افزونههای Chrome؛ لازم است اصالت افزونه (شناسه، ارائهدهنده، تعداد بازبینی و کاربر) بررسی شود چون افزونههای جعلی وجود دارد. پس از نصب، کاربر رمز عبور (که فقط دسترسی به خود MetaMask را کنترل میکند) میگذارد و یک عبارت بازیابی ۱۲ کلمهای میگیرد که برای بازیابی در هر کیف پول سازگار کافی است و باید دو بار روی کاغذ در دو مکان امن نگهداری شود. سپس آدرس حساب و شبکهٔ فعلی نمایش داده میشود.
شبکهها
- Main Network: اتر واقعی با ارزش واقعی.
- Ropsten: شبکهٔ آزمایشی عمومی (اتر بیارزش).
- Kovan: شبکهٔ آزمایشی با اجماع Aura و اثبات اختیار (فقط Parity).
- Rinkeby: شبکهٔ آزمایشی با اجماع Clique و اثبات اختیار.
- Localhost 8545: گره محلی (شبکهٔ عمومی یا خصوصی).
- Custom RPC: هر گرهٔ سازگار با RPC کلاینت Geth.
کلید و آدرس در همهٔ شبکهها یکسان است ولی موجودی هر شبکه مستقل است.
دریافت اتر آزمایشی
با رفتن به شبکهٔ Ropsten و استفاده از «شیر آب» (faucet) یک تراکنش پرداخت به آدرس شما ساخته میشود؛ شناسهٔ تراکنش (هش) را در block explorer (مثل Etherscan) میتوان دید.
ارسال اتر و کارمزد گس
تلاش برای ارسال ۱ ETH از موجودی دقیق ۱ ETH ناموفق است چون هر تراکنش کارمزد گس دارد. مثال کتاب: قیمت گس ۳ gwei و سقف گس ۲۱٬۰۰۰ برای انتقال ساده، پس بیشترین هزینه ۰٫۰۰۰۰۶۳ ETH است. شبکههای آزمایشی هم کارمزد دارند تا رفتارشان مثل شبکهٔ اصلی باشد و از حملات DoS و قراردادهای معیوب محافظت شوند. پس از دریافت اتر بیشتر، انتقال موفق میشود و موجودی نهایی ۰٫۹۹۹۹۳۷ ETH میشود. تاریخچهٔ تراکنشهای هر آدرس را میتوان در Etherscan دید و از آنجا به آدرسها و تراکنشهای دیگر رسید.
معرفی کامپیوتر جهانی
تا اینجا اتریوم را ارز دیجیتال دیدهایم، اما در اصل ارز در خدمت اجرای قراردادهای هوشمند روی EVM است. EVM یک singleton جهانی است؛ هر گره یک نسخهٔ محلی آن را برای اعتبارسنجی اجرا میکند و بلاکچین وضعیت در حال تغییر آن را ثبت میکند (جزئیات در فصل ۱۳).
حسابهای خارجی (EOA) و قراردادها
- EOA: حسابی که کلید خصوصی دارد و کنترل آن دست صاحب کلید است.
- حساب قرارداد: کد قرارداد هوشمند دارد و کلید خصوصی ندارد؛ منطق کدش آن را کنترل میکند.
- هر دو آدرس دارند و میتوانند اتر دریافت و ارسال کنند. وقتی مقصد تراکنش یک قرارداد باشد، قرارداد با ورودی تراکنش (اتر و/یا داده شامل نام تابع و پارامترها) در EVM اجرا میشود.
- چون قرارداد کلید ندارد، نمیتواند تراکنش را آغاز کند؛ فقط EOA آغازگر است ولی قراردادها میتوانند در واکنش، قراردادهای دیگر را فراخوانی کنند (مثلاً کیف پول چندامضایی یا الگوی DApp).
نخستین قرارداد: شیر آب اتر آزمایشی (Faucet)
قرارداد سادهای به زبان Solidity (ساختهٔ گَوین وود) نوشته میشود که عمداً ساده و معیوب است (ایرادهایش در فصلهای بعد بررسی میشود). ساختار آن:
contract Faucetشبیه تعریف کلاس در زبانهای شیءگرا است.- تابع عمومی
withdraw(uint withdraw_amount)باrequireسقف برداشت را ۰٫۱ اتر (۱۰¹⁷ wei) میکند و باmsg.sender.transfer(...)اتر را به فرستندهٔ تراکنش میفرستد.msgهمان تراکنشی است که قرارداد را فعال کرده است. - تابع پیشفرض (fallback) بدون نام، با
public payable، برای پذیرش اتر وقتی تراکنش تابع خاصی را نام نبرده یا داده ندارد.
کامپایل و استقرار
- کامپایل در محیط Remix IDE (remix.ethereum.org)، با توجه به نسخهٔ کامپایلر (۰٫۴٫۱۹ در کتاب) و دستور
pragma، خروجی بایتکد EVM است. - برای ثبت قرارداد روی بلاکچین، یک تراکنش ویژه «ایجاد قرارداد» ساخته میشود (مقصد: آدرس صفر). در Remix، محیط «Injected Web3» را انتخاب میکنیم تا از طریق MetaMask به Ropsten وصل شود. تراکنش ایجاد اتری ندارد ولی داده (بایتکد) دارد و گس مصرف میکند؛ بعد از ۱۵ تا ۳۰ ثانیه استخراج میشود و قرارداد آدرس خودش را میگیرد.
تعامل با قرارداد
- مشاهده در Etherscan: تاریخچهٔ آدرس قرارداد.
- تأمین مالی: ارسال ۱ اتر به آدرس قرارداد مثل هر آدرس دیگر؛ چون دادهای نیست، تابع پیشفرض
payableاجرا میشود و موجودی بالا میرود. - برداشت: در Remix تابع
withdrawرا با مقدار"100000000000000000"(داخل کوتیشن بهدلیل محدودیت اعداد بزرگ در جاوااسکریپت) فراخوانی میکنیم. تراکنش مقدار ۰ دارد ولی موجودی قرارداد به ۰٫۹ اتر میرسد. خروج پول بهصورت تراکنش داخلی (internal transaction/message) در تب جداگانه نمایش داده میشود چون از کد قرارداد برخاسته است.
جمعبندی
شما کیف پول ساختید، اتر آزمایشی گرفتید و فرستادید، یک قرارداد Solidity نوشتید، با Remix کامپایل و روی Ropsten مستقر کردید، آن را شارژ کردید و با فراخوانی withdraw ۰٫۱ اتر برداشتید؛ یعنی با نرمافزاری که پول را روی کامپیوتر جهانی غیرمتمرکز کنترل میکند تعامل کردید. برنامهنویسی قرارداد در فصل ۷ و بهترین روشها و امنیت در فصل ۹ دنبال میشود.
فصل ۳ — کلاینتهای اتریوم (Ethereum Clients)
کلاینت چیست؟
کلاینت اتریوم نرمافزاری است که مشخصات اتریوم را پیاده میکند و روی شبکهٔ همتابههمتا با کلاینتهای دیگر ارتباط برقرار میکند. تا زمانی که کلاینتها از مشخصات مرجع و پروتکلهای استاندارد پیروی کنند، با هم سازگارند، حتی اگر تیمها و زبانهای برنامهنویسی متفاوتی داشته باشند.
نکات کلیدی:
- کلاینتهای اصلی متنباز هستند (مثلاً با مجوز LGPL v3). متنباز بودن یعنی توسعه توسط جامعهای باز و چشمهای بیشتر روی کد.
- برخلاف بیتکوین که «مشخصات» آن همان پیادهسازی مرجع (Bitcoin Core) است، اتریوم با یک مشخصات رسمی (Yellow Paper، ترکیب متن انگلیسی و ریاضی) و پیشنهادهای بهبود اتریوم (EIP) تعریف میشود.
- تنوع پیادهسازیها در اتریوم بیش از هر بلاکچین دیگر است و این یک سپر دفاعی خوب است: اگر آسیبپذیریای در یک کلاینت کشف شود، بقیه کلاینتها شبکه را تقریباً بدون اختلال زنده نگه میدارند.
شبکههای مبتنی بر اتریوم
شبکههایی مثل Ethereum Classic، Ella، Expanse، Ubiq و Musicoin عمدتاً با مشخصات سازگارند اما برای پشتیبانی از هر کدام، کلاینتها به تغییرات کوچکی نیاز دارند؛ بنابراین هر نسخهٔ کلاینت لزوماً همهٔ این زنجیرهها را اجرا نمیکند.
شش پیادهسازی اصلی پروتکل (در زمان نگارش) به شش زبان:
| کلاینت | زبان |
|---|---|
| Parity | Rust |
| Geth | Go |
| cpp-ethereum | C++ |
| pyethereum | Python |
| Mantis | Scala |
| Harmony | Java |
فصل روی دو کلاینت رایجتر، Parity و Geth، تمرکز دارد.
آیا باید گره کامل اجرا کنم؟
سلامت، تابآوری و مقاومت در برابر سانسور بلاکچین به وجود گرههای کامل زیاد، مستقل و پراکنده در سطح جهان بستگی دارد. هر گره کامل به گرههای جدید کمک میکند داده را بگیرند و به اپراتور هم راستیآزمایی مستقل و معتبر همهٔ تراکنشها و قراردادها را میدهد.
هزینهها: دانلود و ذخیرهٔ حدود ۸۰ تا ۱۰۰ گیگابایت (تا سپتامبر ۲۰۱۸) که روزبهروز رشد میکند، پهنای باند و نگهداری.
برای توسعه، گره کامل شبکهٔ اصلی ضروری نیست. میتوان از گره شبکهٔ آزمایشی، بلاکچین خصوصی محلی مثل Ganache یا سرویس ابری مثل Infura استفاده کرد. همچنین کلاینت راه دور (remote client) گزینهای است که بلاکچین را ذخیره یا اعتبارسنجی نمیکند و فقط عملکرد کیف پول دارد (MetaMask، Emerald Wallet، MyEtherWallet، MyCrypto).
تفاوت کلاینت راه دور با کلاینت سبک
- کلاینت سبک (light client) (مشابه SPV در بیتکوین) سرآیند بلاکها را اعتبارسنجی و با اثباتهای مرکل ورود تراکنشها را بررسی میکند و امنیتی نزدیک به گره کامل دارد.
- کلاینت راه دور هیچ اعتبارسنجی انجام نمیدهد و کاملاً به گره کامل اعتماد میکند؛ پس امنیت و ناشناسی کمتری دارد، مگر آنکه به گره کامل خود شما وصل شود.
مزایا و معایب سه گزینهٔ اجرا
گره کامل شبکهٔ اصلی
- مزایا: پشتیبانی از تابآوری شبکه، اعتبارسنجی مستقل، تعامل و استقرار مستقیم قرارداد بدون واسطه، پرسوجو بدون فاشکردن اطلاعات به شخص ثالث.
- معایب: نیاز به منابع رو به رشد، چند روز همگامسازی اولیه، نگهداری و آنلاینماندن.
گره شبکهٔ آزمایشی عمومی
- مزایا: حدود ۱۰ گیگابایت داده، همگامسازی در چند ساعت، اتر آزمایشی رایگان از faucet، شبکهٔ زنده با کاربران و قراردادهای دیگر.
- معایب: چون ارزش واقعی ندارد، امنیت در برابر مهاجم واقعی قابل آزمون نیست؛ کارمزد گس و ازدحام شبکه بهطور واقعی شبیهسازی نمیشود.
شبیهسازی محلی (مثل Ganache، نام قبلی testrpc)
- مزایا: بدون همگامسازی، بدون نیاز به اتر آزمایشی، بدون کاربر یا قرارداد دیگر.
- معایب: رفتار با بلاکچین عمومی متفاوت است (بدون رقابت بر سر فضای بلاک)، استخراج قابل پیشبینی، و باید همهٔ وابستگیها را خودتان مستقر کنید؛ بازسازی قراردادهای عمومی مثل DAO ممکن نیست.
اجرای کلاینت
نیازمندیهای سختافزاری
- حداقل: پردازندهٔ ۲ هستهای، ۸۰ گیگابایت فضای آزاد، ۴ گیگابایت RAM با SSD (یا ۸ گیگابایت با HDD)، اینترنت ۸ مگابیت/ثانیه.
- توصیهشده: ۴ هستهٔ سریع، ۱۶ گیگابایت RAM یا بیشتر، SSD با حداقل ۵۰۰ گیگابایت فضای آزاد، اینترنت ۲۵ مگابیت/ثانیه یا بیشتر.
- همگامسازی بسیار I/O-محور است، پس SSD مهم است. ارقام برای گرهٔ «هرسشده» (pruned) است؛ گرهٔ آرشیوی (archival) که کل حالتهای تاریخی را نگه میدارد بیش از ۱ ترابایت میخواهد. در زمان کتاب، Parity منابع کمتری مصرف میکرد.
نرمافزارهای لازم
نمونهها روی اوبونتو و پوستهٔ bash هستند. به git، زبان Go (نسخهٔ ۱٫۱۰ به بالا برای Geth) و Rust (نسخهٔ ۱٫۲۷ به بالا برای Parity) نیاز است؛ Parity همچنین به کتابخانههایی مانند OpenSSL و libudev نیاز دارد. هر زنجیرهٔ مبتنی بر اتریوم معمولاً نسخهٔ Geth خودش را دارد، ولی Parity با یک دانلود از چند زنجیره (Ethereum، Ethereum Classic، Ellaism، Expanse، Musicoin) پشتیبانی میکند.
Parity
پیادهسازی گره کامل و مرورگر DApp، نوشتهشده «از صفر» به زبان Rust با هدف ماژولار، امن و مقیاسپذیر، توسط شرکت Parity Tech (بریتانیا) و با مجوز GPLv3. (یادآوری: گَوین وود بنیانگذار این شرکت و از نویسندگان کد آن است؛ حدود ۲۵٪ کلاینتهای نصبشده.) نصب با کلون مخزن گیت و cargo install و بررسی با parity --version.
Geth (Go-Ethereum)
پیادهسازی Go که بنیاد اتریوم فعالانه توسعه میدهد و پیادهسازی «رسمی» محسوب میشود. مراحل: کلون مخزن مربوط به زنجیرهٔ مورد نظر، make geth و بررسی با geth version. نسخههای پیشساخته هم موجودند.
همگامسازی اولیه
همگامسازی کامل از بلاک ژنسیس همهٔ بلاکها و تراکنشها را اعتبارسنجی میکند و بسیار کند است. بخشی از دلیل: حملات DoS اواخر ۲۰۱۶ (از بلاک ۲٬۲۸۳٬۳۹۷ در ۱۸ سپتامبر ۲۰۱۶ تا بلاک ۲٬۷۰۰٬۰۳۱ در ۲۶ نوامبر ۲۰۱۶) که اعتبارسنجی را بسیار کند و حافظهبر و I/Oبر کرد (بیش از یک دقیقه برای هر بلاک). هاردفورکهای بعدی آسیبپذیریها را رفع و حدود ۲۰ میلیون حساب خالی ناشی از هرزنامه را پاک کردند.
راهحل: همگامسازی سریع (fast sync) که اعتبارسنجی کامل را تا رسیدن به سر زنجیره به تعویق میاندازد. در Geth گزینهٔ --fast است (فقط با پایگاه داده بلاک خالی کار میکند) و Parity بهطور پیشفرض سریع همگام میشود. همگامسازی از نیمروز (سیستم بسیار سریع) تا چند روز (سیستم کندتر) طول میکشد.
رابط JSON-RPC
کلاینتها یک API مبتنی بر فراخوانی از راه دور با قالب JSON ارائه میدهند، معمولاً روی HTTP درگاه ۸۵۴۵ و بهصورت پیشفرض فقط برای localhost (۱۲۷٫۰٫۰٫۱). میتوان با کتابخانهٔ تخصصی یا ابزار عمومی مثل curl به آن درخواست داد.
ساختار درخواست (استاندارد JSON-RPC 2.0) چهار عنصر دارد: jsonrpc (همیشه “2.0”)، method (نام متد)، params (پارامترها، اختیاری) و id (شناسه برای تطبیق پاسخ؛ در batching که چند درخواست در یک اتصال ارسال میشود، کاربرد ویژه دارد).
مثالها: web3_clientVersion نسخهٔ کلاینت را برمیگرداند و eth_gasPrice قیمت گس را بهصورت عدد هگزادسیمال بر حسب wei (در مثال ۱۸ gwei). Parity حالت سازگاری با Geth دارد (--geth) که API یکسان ارائه میکند.
کلاینتهای راه دور
اینها زیرمجموعهای از کارکردهای گره کامل را بدون ذخیرهٔ بلاکچین ارائه میدهند: مدیریت کلید و آدرس، ساخت/امضا/پخش تراکنش، تعامل با قراردادها، مرور DApp، لینک به سرویسهای خارجی (مثل block explorer)، تبدیل واحد و نرخ ارز، تزریق نمونهٔ web3 به مرورگر و اتصال به RPC محلی یا راه دور.
کیفپولهای موبایلی
همگی remote هستند چون گوشی منابع کافی ندارد؛ کلاینتهای سبک هنوز در حال توسعهاند (در Parity گزینهٔ تجربی --light). نمونهها: Jaxx (چندارزی، بر پایهٔ BIP-39)، Status (کیف پول و مرورگر DApp)، Trust Wallet (پشتیبانی از توکنهای ERC20 و ERC223)، Cipher Browser.
کیفپولهای مرورگری
- MetaMask: کیف پول، کلاینت RPC و کاوشگر ساده قرارداد؛ نمونهٔ web3 را به محیط جاوااسکریپت مرورگر تزریق میکند و میتواند ارائهدهندهٔ web3 برای ابزارهای دیگر مثل MEW و MyCrypto باشد.
- Jaxx: افزونهٔ Chrome/Firefox و نسخهٔ دسکتاپ.
- MyEtherWallet (MEW): کیف پول نرمافزاری جاوااسکریپتی، پل به Trezor و Ledger، اتصال به web3 تزریقشده و RPC، و رابط ساده برای تعامل با قرارداد از طریق آدرس و ABI. برای آزمایش و واسط با کیفپول سختافزاری عالی است اما بهعنوان کیف پول اصلی توصیه نمیشود چون به تهدیدات محیط مرورگر در معرض است. هشدار فیشینگ: همیشه از بوکمارک استفاده کنید، نه موتور جستجو یا لینک.
- MyCrypto: انشعابی (fork) از MEW درست پیش از انتشار کتاب، با قابلیت مشابه که احتمالاً با گذر زمان فرق خواهد کرد.
- Mist: نخستین مرورگر اتریومی از بنیاد اتریوم؛ نخستین پیادهسازی استاندارد ERC20 را در کیف پول داشت و نخستین کیف پولی بود که چکسام camelCase (EIP-55) را معرفی کرد. گره کامل اجرا میکند و از ذخیرهسازی Swarm و آدرسهای ENS پشتیبانی میکند.
جمعبندی
در این فصل یک کلاینت را دانلود، نصب و همگام کردید؛ در نتیجه با تکثیر بلاکچین روی رایانهٔ خود در سلامت و پایداری شبکه سهیم میشوید.
فصل ۴ — رمزنگاری (Cryptography)
رمزنگاری در اتریوم: چه چیزی هست و چه چیزی نیست
رمزنگاری (cryptography) فقط «رمزگذاری» (encryption) نیست. میتواند اثبات دانستن یک راز بدون فاشکردن آن (امضای دیجیتال) و اثبات اصالت داده (اثر انگشت دیجیتال یا هش) را هم فراهم کند. هر دو ابزار برای کار اتریوم و هر بلاکچینی حیاتیاند.
نکتهٔ مهم: در زمان انتشار کتاب، هیچ بخشی از پروتکل اتریوم رمزگذاری نمیکند؛ تمام ارتباطات و دادههای تراکنش برای همه خواناست تا همه بتوانند درستی بهروزرسانیهای حالت را بسنجند و به اجماع برسند. ابزارهایی مانند اثباتهای دانش صفر (zero-knowledge) و رمزگذاری همومورفیک ممکن است در آینده محاسبات رمزگذاریشده را در عین اجماع ممکن کنند.
کلیدها و آدرسها
- مالکیت اتر در حسابهای EOA از طریق کلید خصوصی، آدرس و امضای دیجیتال برقرار میشود. آدرس مستقیماً از کلید خصوصی مشتق میشود: هر کلید خصوصی دقیقاً یک آدرس را مشخص میکند.
- کلید خصوصی هرگز در شبکه منتقل یا روی زنجیره ذخیره نمیشود؛ فقط آدرسها و امضاها.
- تشبیه: کلید عمومی مثل شمارهٔ حساب بانکی (شناسایی برای دیگران) و کلید خصوصی مثل رمز (PIN) است که کنترل حساب را میدهد.
- هر آدرسی کلید ندارد؛ آدرس قراردادها (فصل ۷) به کلید خصوصی وابسته نیست.
رمزنگاری کلید عمومی و ارزهای دیجیتال
رمزنگاری کلید عمومی (نامتقارن) از دههٔ ۱۹۷۰ (کارهای دیفی، هلمن و مرکل) پایهای بر توابعی است که محاسبهٔ آنها آسان اما معکوسکردنشان سخت است. مثال: ضرب دو عدد اول بزرگ آسان، اما تجزیهٔ حاصلضرب دشوار است (مثال کتاب: ۸٬۰۱۸٬۰۰۹ = ۲٬۰۰۳ × ۴٬۰۰۳). اگر یک رازِ میانبر دانسته شود، معکوسکردن آسان میشود؛ به اینها تابع دریچهدار (trapdoor) میگویند.
اتریوم از خانوادهٔ پیشرفتهتری استفاده میکند: رمزنگاری منحنی بیضوی که بر مسئلهٔ لگاریتم گسسته استوار است (ضرب ساده، «تقسیم» عملاً ناممکن و بدون دریچهٔ شناختهشده).
- کلید خصوصی و عمومی «جفت» هستند چون کلید عمومی از خصوصی مشتق میشود.
- امضای دیجیتال: جزئیات تراکنش (پیام) با کلید خصوصی ترکیب میشود تا کدی تولید شود که فقط صاحب کلید میتواند بسازد. هر کس میتواند با آدرس/کلید عمومی و جزئیات تراکنش صحت امضا را بسنجد، بدون دانستن کلید خصوصی.
- در اتریوم کلید خصوصی فقط برای امضا (احراز هویت تراکنش) استفاده میشود، نه رمزگذاری.
کلیدهای خصوصی
کلید خصوصی صرفاً یک عدد تصادفی است. کنترل آن یعنی کنترل همهٔ دارایی و قراردادهای مجاز برای آن آدرس؛ باید مخفی بماند و پشتیبانگیری شود، چون گمشدنش جبرانپذیر نیست.
تولید کلید از عدد تصادفی
- مهمترین گام، منبع آنتروپی امن است. کلید یک عدد بین ۱ و کمی کمتر از ۲²⁵⁶ است (حدود ۱٫۱۵۸×۱۰⁷⁷؛ دقیقاً مرتبهٔ منحنی بیضوی). معمولاً نرمافزار بیتهای تصادفی امن را به هش ۲۵۶ بیتی (Keccak-256 یا SHA-256) میدهد و در صورت خارجبودن از بازه تکرار میکند.
- فضای کلیدها آنقدر بزرگ است (۱۰⁷⁷؛ تعداد اتمهای جهان مرئی حدود ۱۰⁸⁰) که حدسزدن تصادفی ناممکن است.
- تولید کلید آفلاین است و به شبکه نیاز ندارد، پس باید واقعاً تصادفی باشد. انتخاب دستی یا استفاده از تابع تصادفی ساده (
rand) خطرناک است؛ باید از مولد اعداد شبهتصادفی امن رمزنگاری (CSPRNG) با منبع آنتروپی کافی استفاده کرد. - مثال کتاب یک کلید ۶۴ رقمی هگزادسیمال است (۲۵۶ بیت).
کلیدهای عمومی
کلید عمومی یک نقطه روی منحنی بیضوی است، یعنی دو عدد (x, y). از کلید خصوصی با ضرب نقطهای منحنی بیضوی حاصل میشود: K = k · G که k کلید خصوصی، G نقطهٔ مولد ثابت و K کلید عمومی است. این عمل یکطرفه است؛ معکوس آن (یافتن لگاریتم گسسته) در عمل با جستجوی فراگیر برابر است و بیش از عمر جهان طول میکشد.
منحنی بیضوی secp256k1
- اتریوم دقیقاً همان منحنی secp256k1 بیتکوین را (تعریفشده در استاندارد NIST/SECG) استفاده میکند؛ بنابراین کتابخانههای بیتکوین قابل استفادهاند.
- معادلهٔ منحنی
y² = x³ + 7روی میدان متناهی با مدول عدد اول بسیار بزرگp = 2²⁵⁶ − 2³² − 977است. روی میدان متناهی، منحنی شبیه الگوی نقاط پراکنده روی یک شبکه است (در کتاب با مثال p=17 تصویرسازی شده) ولی ریاضی همان است. - مثال پایتون در کتاب نشان میدهد نقطهای مانند
Qدر معادله صدق میکند:(x³ + 7 − y²) mod p = 0.
عملیات ریاضی روی منحنی
- جمع نقاط: برای دو نقطهٔ P1 و P2 یک نقطهٔ سوم P3 تعریف میشود: خطی بین دو نقطه میکشیم، منحنی را در یک نقطهٔ دیگر قطع میکند و آن را نسبت به محور x قرینه میکنیم. اگر دو نقطه یکی باشند، از مماس استفاده میشود.
- نقطهٔ بینهایت نقش صفر را بازی میکند (مثلاً وقتی خط عمودی است). جمع شرکتپذیر است.
- ضرب عدد صحیح در نقطه:
k·Pیعنی جمعکردن P با خودش k بار.
تولید کلید عمومی
با ضرب کلید خصوصی k در نقطهٔ مولد ثابت G (که برای همه یکسان است) به نقطهٔ منحصربهفرد K میرسیم. رابطه ثابت است ولی فقط از k به K محاسبهپذیر است؛ پس آدرس (مشتق از K) را میتوان بهراحتی به اشتراک گذاشت.
قالب سریالسازی
کلید عمومی اغلب بهصورت ۱۳۰ نویسهٔ هگزادسیمال (۶۵ بایت) دیده میشود (استاندارد SEC1 از SECG). پیشوندها: 00 نقطهٔ بینهایت، 04 غیرفشرده (۶۵ بایت)، 02/03 فشرده (با y زوج/فرد، ۳۳ بایت). اتریوم فقط از قالب غیرفشردهٔ 04 استفاده میکند: 04 + x(32 بایت) + y(32 بایت).
کتابخانههای منحنی بیضوی
- OpenSSL: مجموعهٔ کامل ابزارهای رمزنگاری شامل secp256k1 (مثلاً تابع
EC_POINT_mul). - libsecp256k1: پیادهسازی C از Bitcoin Core که از صفر نوشته شده تا جایگزین OpenSSL شود و از نظر کارایی و امنیت برتر شمرده میشود.
توابع هش رمزنگاریشده
برونو شنایر آنها را «اسبهای کاری» رمزنگاری مدرن مینامد. تابع هش دادهای با اندازهٔ دلخواه (پیشتصویر/ورودی) را به خروجی ثابت (هش) نگاشت میکند. هش رمزنگاریشده یکطرفه است و بازیابی ورودی از خروجی عملاً ناممکن است؛ تابع «چند-به-یک» است و پیداکردن دو ورودی با هش یکسان برخورد (collision) نام دارد که در اتریوم عملاً ناممکن است.
ویژگیهای اصلی: قطعیت (determinism)، قابلیت راستیآزمایی (محاسبهٔ کارا)، ناهمبستگی (تغییر کوچک ورودی = تغییر عظیم خروجی)، بازگشتناپذیری و مقاومت در برابر برخورد (مهم برای جلوگیری از جعل امضا).
کاربردها: اثر انگشت داده، یکپارچگی پیام، اثبات کار، احراز هویت (هش رمز عبور)، مولد اعداد شبهتصادفی، تعهد پیام (commit–reveal) و شناسههای یکتا.
Keccak-256؛ هش اتریوم
اتریوم در جاهای زیادی از Keccak-256 استفاده میکند. Keccak برندهٔ مسابقهٔ SHA-3 (NIST، ۲۰۰۷) بود و در ۲۰۱۵ با عنوان FIPS 202 استاندارد شد. اما NIST پس از پایان فرآیند برخی پارامترها را تغییر داد؛ همزمان با افشاگریهای اسنودن دربارهٔ احتمال نفوذ NSA در استانداردهای NIST (مثل Dual_EC_DRBG)، بنیاد اتریوم تصمیم گرفت نسخهٔ اصلی Keccak را پیاده کند، نه SHA-3 تغییریافته.
هشدار: در اسناد و کد اتریوم، «SHA-3» اغلب در واقع Keccak-256 است و خروجی آن با SHA-3 استاندارد برای ورودی یکسان فرق دارد (تفاوت در پارامتر padding). برای تشخیص از بردار آزمون استفاده کنید؛ هش رشتهٔ خالی در Keccak-256 با c5d246... و در SHA3 استاندارد با a7ffc6... شروع میشود. تلاشی (ERC59) برای تغییر نام sha3 به keccak256 در کد، opcode و کتابخانهها جریان دارد.
آدرسهای اتریوم
آدرس از کلید عمومی (بدون پیشوند 04) با Keccak-256 و نگهداشتن ۲۰ بایت آخر (کمارزشترین) بهدست میآید. معمولاً با پیشوند 0x (نشانهٔ هگزادسیمال) نوشته میشود. مثال کتاب: هش کلید عمومی 2a5bc3... ← آدرس 0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9.
قالبهای آدرس
بر خلاف بیتکوین، آدرسهای اتریوم بهطور پیشفرض بدون چکسام و بهصورت هگزادسیمال خام ارائه میشوند؛ چون فرض بر این بود که لایههای بالاتر (سرویسهای نام) آنها را پنهان میکنند. اما آن لایهها دیر توسعه یافتند و این باعث مشکلاتی مانند از دست رفتن وجوه بهدلیل خطای تایپی شد.
۱. ICAP (Inter exchange Client Address Protocol): رمزگذاریای تا حدی سازگار با IBAN (شمارهٔ حساب بانکی بینالمللی). از کد کشور غیراستاندارد «XE» استفاده میکند، با دو نویسهٔ چکسام و سه نوع شناسهٔ حساب:
- Direct: عدد پایه ۳۶ تا ۳۰ نویسه (۱۵۵ بیت کمارزش)؛ فقط برای آدرسهایی که با بایت صفر شروع میشوند و سازگار با IBAN (۳۳ نویسه).
- Basic: ۳۱ نویسه، برای هر آدرسی، ولی ناسازگار با اعتبارسنجی IBAN (۳۵ نویسه).
- Indirect: شناسهای که با ثبتکنندهٔ نام به آدرس نگاشت میشود (دارایی، سرویس نام و نام ۹ حرفی).
ابزار helpeth میتواند آدرس ICAP بسازد. احتمال شروع آدرس با بایت صفر ۱ از ۲۵۶ است. متأسفانه ICAP را کیفپولهای کمی پشتیبانی میکنند.
۲. چکسام با بزرگی و کوچکی حروف (EIP-55): چون آدرسها به حروف بزرگ/کوچک حساس نیستند، میتوان با تغییر حالت حروف هگز، چکسامی سازگار با گذشته جاسازی کرد. الگوریتم:
- هش Keccak-256 آدرس کوچکحرف (بدون
0x) را محاسبه کن. - هر کاراکتر الفبایی آدرس (a–f) را بزرگ کن اگر رقم هگز متناظر در هش ≥ ۸ باشد.
کیفپولهای بدون پشتیبانی EIP-55 حالت ترکیبی را نادیده میگیرند، اما کیفپولهای پشتیبان میتوانند خطا را با دقت ۹۹٫۹۸۶٪ شناسایی کنند. مثال کتاب: تغییر یک نویسه (F به E، یعنی تنها یک بیت) هش را کاملاً تغییر میدهد و بزرگی/کوچکی حروف دیگر با چکسام نمیخواند، پس خطا آشکار میشود.
جمعبندی
این فصل رمزنگاری کلید عمومی و کاربرد کلیدهای خصوصی/عمومی در اتریوم، نقش توابع هش (Keccak-256) در ساخت و اعتبارسنجی آدرس و ایدهٔ امضای دیجیتال را مرور کرد. فصل بعد این مفاهیم را در کیفپولها برای مدیریت مجموعهٔ کلیدها کنار هم میگذارد.
فصل ۵ — کیفپولها (Wallets)
«کیف پول» یعنی چه؟
واژهٔ «کیف پول» در اتریوم چند معنا دارد:
- در سطح بالا: نرمافزاری که رابط اصلی کاربر با اتریوم است؛ دسترسی به دارایی را کنترل میکند، کلیدها و آدرسها را مدیریت میکند، موجودی را دنبال میکند و تراکنش میسازد و امضا میکند. بعضی کیف پولها با قراردادها (مثل توکنهای ERC20) هم تعامل میکنند.
- از دید برنامهنویس: سامانهٔ ذخیره و مدیریت کلیدها. هر کیف پولی بخش مدیریت کلید دارد؛ برخی فقط همیناند و برخی بخشی از دستهٔ گستردهتر مرورگرهای DApp هستند (فصل ۱۲).
این فصل کیف پول را بهعنوان ظرف کلید خصوصی و سیستم مدیریت کلید بررسی میکند.
مرور فناوری کیف پول
تعادل بین راحتی و حریم خصوصی
راحتترین کیف پول یک کلید و یک آدرس برای همهچیز است، اما این برای حریم خصوصی فاجعه است چون همهٔ تراکنشها قابل ردیابی و همبستهسازی میشوند. استفاده از کلید تازه برای هر تراکنش بهترین حریم خصوصی را میدهد ولی مدیریتش دشوار است. طراحی خوب کیف پول باید تعادل مناسبی برقرار کند.
کیف پول فقط کلید دارد، نه اتر
کیف پول در واقع حلقهکلید (keychain) است: جفتهای کلید خصوصی و عمومی. اتر و توکنها روی بلاکچین ثبت شدهاند و کاربر با امضای تراکنشها با کلیدهای کیف پول آنها را کنترل میکند. این تمایز برای تغییر ذهنیت از بانکداری متمرکز (فقط شما و بانک موجودی را میبینید) به بلاکچین (همه موجودی حساب را میبینند ولی مالک را نمیشناسند) مهم است. پیامدها: میتوان موجودی را بدون کیف پول بررسی کرد و میتوان حساب را از یک کیف پول به کیف پول دیگر منتقل کرد.
دو نوع اصلی کیف پول
- غیرقطعی (nondeterministic): هر کلید با عدد تصادفی مستقل ساخته میشود و بین کلیدها رابطهای نیست. به آن JBOK («فقط یک مشت کلید») هم میگویند.
- قطعی (deterministic): همهٔ کلیدها از یک کلید اصلی/بذر (seed) مشتق میشوند و با داشتن بذر میتوان همه را دوباره ساخت. رایجترین روش مشتقگیری ساختار درختی است (HD).
بذرها را برای ایمنی در برابر از دست رفتن دستگاه معمولاً به صورت فهرستی از کلمات (کلمات یادآور / mnemonic) کدگذاری میکنند. هرکس این کلمات را داشته باشد میتواند کیف پول را بازسازی کند؛ پس هرگز بهصورت الکترونیکی ذخیره نشود و روی کاغذ در جای امن نگه داشته شود.
کیفپولهای غیرقطعی (تصادفی)
اولین کیف پول اتریوم (برای پیشفروش) در هر فایل یک کلید تصادفی داشت. این شیوه رو به منسوخشدن است چون:
- بهترین رویه برای حریم خصوصی عدم استفاده مجدد از آدرس است؛ یعنی با هر دریافت، آدرس (و کلید) جدید. این باعث میشود فهرست کلیدها مدام رشد کند و لازم باشد مرتب پشتیبانگیری شود، در غیر این صورت با از دست رفتن دیسک یا گوشی دسترسی از بین میرود.
- نوع «۰» که برای هر آدرس فایل جدید میسازد، دشوارترین حالت است.
با این حال بسیاری از کلاینتها (از جمله Geth) از فایل keystore استفاده میکنند: فایل JSON با یک کلید خصوصی که با رمز عبور رمزگذاری شده است. فیلدهای کلیدی نمونه: آدرس، رمز aes-128-ctr، ciphertext، iv، تابع مشتقگیری کلید scrypt با پارامترهایی مانند n=262144، salt، mac و version: 3. تابع مشتقگیری کلید (KDF) یا «کشش رمز» رمز را با تکرار هش (۲۶۲٬۱۴۴ دور) کش میدهد تا حملات brute-force، فرهنگ لغت و جدول رنگینکمان کند شوند. کتابخانههایی مثل keythereum این قالب را میخوانند و مینویسند.
توصیه: کیف پول غیرقطعی را فقط برای آزمونهای ساده به کار ببرید؛ برای کار واقعی از کیف پول HD استاندارد با بذر mnemonic استفاده کنید.
کیفپولهای قطعی (بذردار)
بذر یک عدد تصادفی است که با دادههای دیگر (مثل شماره ایندکس یا «chain code») ترکیب میشود تا کلیدهای خصوصی بیشماری تولید کند. یک بار پشتیبانگیری از بذر در زمان ساخت برای ایمنکردن تمام دارایی کافی است و انتقال کلیدها بین پیادهسازیهای مختلف را آسان میکند. اهمیت امنیت بذر بسیار بالاست، ولی تمرکز تلاش امنیتی روی یک داده میتواند مزیت هم باشد.
کیفپولهای قطعی سلسلهمراتبی (HD) — BIP-32 / BIP-44
پیشرفتهترین نوع کیف پول قطعی که با استاندارد BIP-32 بیتکوین تعریف شده است. کلیدها در یک درخت قرار دارند: کلید والد، فرزندان و نوهها را مشتق میکند. مزایا:
- ساختار درخت میتواند معنای سازمانی داشته باشد (مثلاً شاخه برای دریافت، شاخه برای باقیمانده؛ یا شاخههای مختلف برای بخشها و حوزههای سازمان).
- میتوان دنبالهای از کلیدهای عمومی را بدون دسترسی به کلیدهای خصوصی ساخت؛ مناسب برای سرور ناامن یا کیف پول فقطدریافت/فقطتماشا (watch-only).
بذر و کدهای یادآور (BIP-39)
روش ترجیحی برای کدگذاری کلید برای پشتیبانگیری، دنبالهای از کلمات است که به ترتیب درست، بذر را یکتا بازسازی میکند (استاندارد BIP-39). مثال کتاب نشان میدهد نوشتن رشتهٔ هگز خطای بسیار محتمل دارد، اما کلمات افزونگی زیادی دارند (مثلاً «inzect» بلافاصله نامعتبر شناخته میشود). این روش سادهترین راه برای صادر، رونویسی، ثبت روی کاغذ، خواندن بدون خطا و ورود به کیف پول دیگر است.
بهترین شیوههای کیف پول
استانداردهای در همتنیدهای که کیفپولها را قابل همکاری میکنند:
- کلمات یادآور: BIP-39
- کیف پول HD: BIP-32
- ساختار چندمنظورهٔ HD: BIP-43
- چندارزی و چندحسابی: BIP-44
کیفپولهای نرمافزاری (Jaxx، MetaMask، MyCrypto، MEW) و سختافزاری (Keepkey، Ledger، Trezor) از آنها پشتیبانی میکنند. اگر کیف پول اتریوم میسازید، باید HD با بذر mnemonic و مطابق این چهار استاندارد باشد.
کلمات یادآور (BIP-39)
کیف پول هنگام ساخت ۱۲ تا ۲۴ کلمه نشان میدهد. مهم: اینها با brainwallet (کلمات انتخابی کاربر که ضعیفاند چون انسان منبع تصادفی بدی است و نیاز به حفظکردن دارد) فرق دارند، چون کلمات بهطور تصادفی توسط کیف پول تولید میشوند. BIP-39 توسط شرکت سازندهٔ Trezor پیشنهاد شد و با استاندارد پیشین Electrum ناسازگار است؛ اما حمایت گستردهٔ صنعتی دارد و برای کیف پول چندارزی (شامل اتریوم) قابل استفاده است.
تولید کلمات (مراحل ۱ تا ۶):
- توالی تصادفی امن S به طول ۱۲۸ تا ۲۵۶ بیت بساز.
- چکسام را از اولین (طول S ÷ ۳۲) بیت هش SHA-256 بساز.
- چکسام را به انتهای S اضافه کن.
- دنباله را به بخشهای ۱۱ بیتی تقسیم کن.
- هر مقدار ۱۱ بیتی را با فرهنگ لغت ۲۰۴۸ کلمهای به یک کلمه نگاشت کن.
- کلمات را به ترتیب کنار هم بگذار.
نگاشت آنتروپی به تعداد کلمات: ۱۲۸ بیت ← ۱۲ کلمه، ۱۶۰ ← ۱۵، ۱۹۲ ← ۱۸، ۲۲۴ ← ۲۱، ۲۵۶ ← ۲۴.
از کلمات تا بذر (مراحل ۷ تا ۹): کلمات و یک «نمک» (salt) متشکل از رشتهٔ ثابت "mnemonic" بهعلاوهٔ عبارت عبور اختیاری، با تابع کشش کلید PBKDF2 و ۲۰۴۸ دور HMAC-SHA512 ترکیب میشوند و بذر ۵۱۲ بیتی ساخته میشود. این روند جستجوی brute-force را پرهزینه میکند، در حالی که تعداد بذرهای ممکن (۲⁵¹² یا حدود ۱۰¹⁵⁴) بسیار بزرگ است.
عبارت عبور اختیاری: هر عبارت عبوری یک بذر متفاوت تولید میکند و «عبارت عبور اشتباه» وجود ندارد (کیف پول خالی دیگری ساخته میشود). دو کاربرد:
- عامل دوم (چیزی که حفظ میکنید) تا کلمات بهتنهایی بیفایده باشند؛
- انکارپذیری معقول یا «کیف پول تحت تهدید» که مبلغ اندکی دارد و مهاجم را منحرف میکند.
خطرها: در صورت فوت یا ناتوانی مالک و ندانستن عبارت عبور توسط دیگران، وجوه برای همیشه از دست میرود؛ و اگر عبارت عبور کنار بذر نگهداری شود، مزیت عامل دوم از بین میرود. استفاده از آن نیازمند طرح دقیق پشتیبانگیری و بازیابی است.
کتابخانههای پیادهسازی: python-mnemonic (مرجع)، eth-lightwallet، npm/bip39، و یک مبدل آنلاین/آفلاین برای تست.
ساخت کیف پول HD از بذر
بذر ریشه ۱۲۸، ۲۵۶ یا ۵۱۲ بیتی است، معمولاً از mnemonic بهدست میآید؛ و همهٔ کلیدها قطعی از آن مشتق میشوند؛ پس با انتقال فقط کلمات میتوان کیف پولی با هزاران یا میلیونها کلید را پشتیبانگیری و بازیابی کرد. (ابزار آزمایشی BIP-32 روی HTTP است و برای وجوه واقعی ایمن نیست.)
کلیدهای توسعهیافته (extended)
در BIP-32، کلید والد را میتوان با افزودن یک chain code (رشتهٔ ۲۵۶ بیتی) «توسعه» داد. کلید خصوصی توسعهیافته پیشوند xprv و کلید عمومی توسعهیافته پیشوند xpub دارد. ویژگی مهم: میتوان از کلید عمومی والد، کلیدهای عمومی فرزند را بدون کلید خصوصی مشتق کرد. کاربردها:
- نصب
xpubروی سرور فروشگاه آنلاین تا برای هر سفارش آدرس جدید بسازد بدون اینکه کلید خصوصی (قابل سرقت) داشته باشد؛ - ذخیرهسازی سرد و کیف پول سختافزاری: xprv آفلاین و xpub آنلاین برای تولید آدرس دریافت.
مشتقگیری سختشده (Hardened)
خطر: اگر یک کلید خصوصی فرزند فاش شود و chain code والد (که در xpub هست) معلوم باشد، همهٔ کلیدهای فرزندان و حتی کلید خصوصی والد قابل استنتاج است. راهحل مشتقگیری سختشده است که بهجای کلید عمومی والد از کلید خصوصی والد برای مشتق chain code فرزند استفاده میکند و یک «دیوار آتش» ایجاد میکند. بهترین شیوه: فرزندان سطح ۱ کلیدهای اصلی را همیشه سختشده مشتق کنید.
شماره ایندکس: عدد ۳۲ بیتی؛ بازهٔ 0 تا 2³¹−1 برای مشتقگیری عادی و 2³¹ تا 2³²−1 برای سختشده. ایندکس سختشده با پریم نمایش داده میشود (0' یعنی ۲³¹).
مسیر کلید (path)
هر سطح درخت با / جدا میشود؛ کلید خصوصی مشتق از کلید اصلی با m و کلید عمومی با M شروع میشود. مثلاً m/0 اولین فرزند و m/0/1 دومین نوهٔ آن؛ خواندن نسب از راست به چپ است.
پیمایش درخت: BIP-43 و BIP-44
هر والد میتواند ۴ میلیارد فرزند داشته باشد (۲ میلیارد عادی و ۲ میلیارد سختشده)، پس درخت بسیار پیچیده میشود. BIP-43 نخستین ایندکس سختشده را «هدف» ساختار میداند. BIP-44 ساختار چندارزی/چندحسابی را با هدف 44' تعریف میکند و پنج سطح دارد:
m / purpose' / coin_type' / account' / change / address_index
coin_type'از SLIP0044: اتریوم60'، اتریوم کلاسیک61'، بیتکوین0'، همهٔ testnetها1'.account'برای حسابهای منطقی جدا.change: به دلیل ریشهٔ بیتکوینی، دو زیردرخت دارد (دریافت و باقیمانده)؛ در اتریوم فقط مسیر «دریافت» استفاده میشود چون نیاز به آدرس باقیمانده نیست. این سطح مشتقگیری عادی دارد تا سطح حساب بتواند xpub را در محیط ناایمن صادر کند.address_index: آدرسهای قابل استفاده.
مثال: سومین آدرس دریافت اتریوم در حساب اصلی: M/44'/60'/0'/0/2.
جمعبندی
کیفپولها زیربنای هر برنامهٔ بلاکچینی کاربرمحورند: مجموعهٔ کلیدها و آدرسها را مدیریت میکنند و با امضای دیجیتال مالکیت اتر و مجوز تراکنش را اثبات میکنند (فصل ۶).
فصل ۶ — تراکنشها (Transactions)
تراکنش چیست؟
تراکنش یک پیام امضاشده است که یک حساب خارجی (EOA) میسازد، شبکهٔ اتریوم پخش میکند و روی بلاکچین ثبت میشود. تراکنشها تنها چیزی هستند که میتوانند حالت سیستم را تغییر دهند یا قرارداد را در EVM اجرا کنند؛ اتریوم خودش بهطور خودکار اجرا نمیشود و قراردادها خودبهخود کاری نمیکنند. همهچیز با یک تراکنش آغاز میشود. این فصل بیشتر برای کسانی است که تراکنش را در سطح پایین مدیریت میکنند (مثلاً سازندگان کیف پول).
ساختار تراکنش
تراکنش یک پیام باینری سریالشده با این فیلدهاست:
| فیلد | توضیح |
|---|---|
| Nonce | شمارندهای که EOA فرستنده صادر میکند و مانع بازپخش پیام میشود |
| Gas price | قیمت هر واحد گس (به wei) که فرستنده حاضر به پرداخت است |
| Gas limit | بیشترین مقدار گسی که فرستنده برای این تراکنش میخرد |
| Recipient | آدرس مقصد |
| Value | مقدار اتر برای ارسال به مقصد |
| Data | بارِ باینری با طول متغیر |
| v, r, s | سه مؤلفهٔ امضای دیجیتال ECDSA |
- سریالسازی با طرح RLP (Recursive Length Prefix) انجام میشود که برای سریالسازی دقیق بایتی در اتریوم ساخته شده است. اعداد به صورت صحیح big-endian با طول مضرب ۸ بیت کدگذاری میشوند. RLP نه جداکننده دارد و نه برچسب فیلد؛ پیشوند طول، مرز فیلدها را مشخص میکند.
- فیلد «from» در تراکنش وجود ندارد؛ کلید عمومی (و در نتیجه آدرس) از مؤلفههای
v, r, sامضا قابل استخراج است. «from»، شمارهٔ بلاک و شناسهٔ تراکنش (هش) را نرمافزارها برای نمایش اضافه میکنند و بخشی از پیام نیستند.
Nonce تراکنش
nonce یکی از مهمترین و کمشناختهترین مؤلفههای تراکنش است. طبق Yellow Paper برابر تعداد تراکنشهای ارسالی از آن آدرس (یا تعداد قرارداد ایجادشده برای حسابهای دارای کد) است. این مقدار صریحاً در حالت حساب ذخیره نمیشود و با شمارش تراکنشهای تأییدشده محاسبه میشود. دو کاربرد حیاتی:
- ترتیب پردازش: وقتی دو تراکنش (مثلاً ۶ و ۸ اتر از ۱۰ اتر موجودی) پشتسرهم ارسال شوند، گرهها ممکن است آنها را با ترتیب متفاوت دریافت کنند. nonce ترتیب را تضمین میکند: تراکنش با nonce بزرگتر تا پردازش تراکنشهای قبلی نادیده گرفته میشود.
- جلوگیری از بازپخش (replay): بدون nonce، دو پرداخت یکسان کاملاً شبیه هم میشوند و هرکس میتوانست تراکنش را بارها کپی و پخش کند. nonce هر تراکنش را یکتا میکند. این برای پروتکل مبتنی بر حساب (در مقابل UTXO بیتکوین) ضروری است.
پیگیری nonce
nonce از صفر شروع میشود؛ مثلاً اگر getTransactionCount عدد ۴۰ بدهد، nonceهای ۰ تا ۳۹ دیده شدهاند و nonce بعدی ۴۰ است. هشدار: وقتی چند تراکنش را پشتسرهم ارسال میکنید، getTransactionCount(..., "pending") همه را فوراً نمیشمارد. فقط وقتی شمار تأییدشده و در انتظار برابر باشند میتوان به آن اعتماد کرد؛ پس از آن برنامه باید خودش nonce را تا تأیید تراکنشها پیگیری کند. Parity تابع parity_nextNonce دارد که درست میشمارد.
حفره (gap)، nonce تکراری و تأیید
- شبکه تراکنشها را بر اساس nonce به ترتیب پردازش میکند. اگر تراکنش nonce 0 و سپس nonce 2 ارسال شود، دومی در mempool میماند تا nonce 1 برسد. اگر یک تراکنش نامعتبر یا با گس ناکافی حفره ایجاد کند، همهٔ تراکنشهای بعدی «گیر» میکنند. برای رفع باید تراکنش معتبر با nonce گمشده را بفرستید. وقتی پر شد، تراکنشهای بعدی بهتدریج معتبر میشوند؛ تراکنش را نمیتوان بازخوانی (recall) کرد.
- اگر دو تراکنش با nonce یکسان (مقصد یا مقدار متفاوت) ارسال شود، فقط یکی تأیید میشود و انتخاب تقریباً تصادفی است.
همروندی (Concurrency) و nonce
وقتی چند برنامه از یک آدرس تراکنش میسازند (مثلاً کیف پول گرم یک صرافی)، انتخاب nonce مسئلهساز است. تخصیص nonce توسط یک رایانه، تکنقطهٔ شکست میسازد و اگر یک nonce تخصیصیافته مصرف نشود، بقیه گیر میکنند. تولید تراکنش بدون nonce و صفکردن برای یک امضاکننده هم گلوگاه میسازد. در عمل بیشتر پیادهسازیها از همروندی پرهیز میکنند: یک فرآیند واحد برای برداشتها، یا چند کیف پول گرم مستقل که گاهی تراز میشوند.
گس تراکنش
گس «سوخت» اتریوم است و اتر نیست؛ ارزی مجازی با نرخ مبادله مستقل. دلیل جداسازی: محافظت از سیستم در برابر نوسان قیمت اتر و مدیریت نسبتهای حساس هزینهٔ منابع (محاسبه، حافظه، ذخیرهسازی). مدل محاسباتی تورینگکامل برای جلوگیری از DoS به اندازهگیری نیاز دارد.
- gasPrice: قیمت هر واحد گس به wei (مثلاً ۳ gwei در فصل ۲). قیمت بالاتر یعنی تأیید سریعتر. حداقل صفر است (تراکنش بدون کارمزد در پروتکل ممنوع نیست، ولی ممکن است هرگز تأیید نشود). سایت ETH Gas Station قیمتهای جاری را نشان میدهد و web3 با میانهگیری از چند بلاک پیشنهاد میدهد.
- gasLimit: سقف واحدهای گسی که فرستنده میخرد. برای انتقال ساده بین EOAها ثابت و ۲۱٬۰۰۰ واحد است. هزینه = ۲۱٬۰۰۰ × gasPrice. برای مقصد قرارداد فقط میتوان تخمین زد، چون مسیر اجرا به شرایط خارج از کنترل شما (مثل تعداد فراخوانیهای قبلی) بستگی دارد؛ مثال لاتاری که در فراخوانی صدم محاسبهٔ بیشتری دارد.
- تشبیه باک بنزین ناقص است؛ بیشتر شبیه حساب اعتباری است: پس از پایان سفر برای مصرف واقعی پرداخت میکنید. هنگام ارسال بررسی میشود که موجودی برای
gasPrice × gasLimitکافی باشد، ولی فقط گس مصرفشده کسر میشود.
گیرندهٔ تراکنش
فیلد to ۲۰ بایت است (EOA یا قرارداد). اتریوم اعتبارسنجی دیگری انجام نمیدهد: هر ۲۰ بایتی معتبر است حتی اگر کلید خصوصی یا قراردادی نداشته باشد. ارسال به آدرس اشتباه معمولاً اتر را میسوزاند (برای همیشه خرجنشدنی). اعتبارسنجی باید در رابط کاربری (مثلاً چکسام EIP-55) انجام شود. سوزاندن گاهی دلیل مشروع دارد (مثلاً بازدارنده در کانالهای پرداخت) و چون عرضهٔ اتر محدود است، سوزاندن ارزش را بین دارندگان باقیمانده توزیع میکند.
مقدار (Value) و داده (Data)
بارِ اصلی تراکنش دو فیلد value و data است و هر چهار ترکیب معتبر است:
- فقط value = پرداخت؛
- فقط data = فراخوانی؛
- هر دو = پرداخت و فراخوانی؛
- هیچکدام = احتمالاً هدررفت گس، اما ممکن.
ارسال مقدار به EOA و قرارداد
- برای EOA (یا هر آدرس غیرقرارداد) مقدار به موجودی آدرس اضافه میشود (آدرس تازه به حالت اضافه و با مقدار پرداخت مقداردهی میشود).
- برای قرارداد، EVM آن را اجرا میکند و تابع نامبرده در data را فراخوانی میکند. اگر data نباشد، تابع fallback اجرا میشود (اگر
payableباشد). اگر fallback نباشد، تأثیر تراکنش فقط افزایش موجودی است. قرارداد میتواند با پرتاب استثنا پرداخت را رد کند.
ارسال داده به EOA یا قرارداد
ارسال data به EOA در پروتکل معتبر است ولی معنای آن به کیف پول بستگی دارد و در اجماع نقشی ندارد (اغلب نادیده گرفته میشود). برای قراردادهای سازگار با ABI، داده شامل:
- انتخابگر تابع (function selector): ۴ بایت اول هش Keccak-256 نمونه (prototype) تابع (نام و نوع آرگومانها، مثل
withdraw(uint256))؛ - آرگومانها کدگذاریشده طبق قواعد ABI.
مثال: انتخابگر withdraw(uint256) برابر 0x2e1a7d4d است و مقدار ۰٫۰۱ اتر (۱۰¹⁶ wei) به ۳۲ بایت صفرپر میشود و به آن میچسبد.
تراکنش ویژه: ایجاد قرارداد
تراکنش ایجاد قرارداد به آدرس صفر میرود (0x0، نه EOA است و نه قرارداد؛ هرگز نمیتواند تراکنش بسازد یا اتر خرج کند). data شامل بایتکد کامپایلشده است؛ ارسال value اختیاری است (موجودی اولیهٔ قرارداد). ارسال value بدون data اتر را میسوزاند. گاهی اتر به آدرس صفر میرسد که یا اشتباه است یا سوزاندن عمدی؛ برای سوزاندن عمدی بهتر است از آدرس مخصوص 0x000...dEaD استفاده شود.
مثال کتاب: کامپایل Faucet.sol با solc --bin و ارسال دستی تراکنش با web3.eth.sendTransaction با to صفر، gas و gasPrice. بهتر است همیشه to را صریحاً مشخص و gasPrice/gasLimit را تعیین کنید. رسید تراکنش (getTransactionReceipt) شامل contractAddress است و با آن میتوان به قرارداد اتر فرستاد یا withdraw را با data مناسب فراخواند.
امضاهای دیجیتال
ECDSA
اتریوم از ECDSA (الگوریتم امضای دیجیتال منحنی بیضوی) استفاده میکند. امضا سه کار انجام میدهد:
- احراز مجوز: مالک کلید خصوصی خرجکردن اتر یا اجرای قرارداد را مجاز کرده است.
- عدم انکار (non-repudiation): انکار مجوز ممکن نیست.
- یکپارچگی: داده پس از امضا قابل تغییر نیست.
دو بخش دارد: الگوریتم ساخت امضا از پیام با کلید خصوصی، و الگوریتم تأیید توسط هر کس فقط با پیام و کلید عمومی.
ساخت امضا
«پیام» در واقع هش Keccak-256 دادهٔ RLP تراکنش است. خروجی دو مقدار r و s است. الگوریتم ابتدا یک کلید خصوصی موقتی تصادفی q میسازد (تا مهاجم از روی تراکنشهای امضاشده نتواند کلید اصلی را محاسبه کند)، کلید عمومی موقتی Q = q·G، r را برابر مختصات x نقطهٔ Q قرار میدهد، و s را از فرمول
s ≡ q⁻¹ (Keccak256(m) + r·k) (mod p)
محاسبه میکند (k کلید خصوصی امضاکننده و p مرتبهٔ منحنی).
تأیید امضا
با r, s, پیام و کلید عمومی K: محاسبهٔ w = s⁻¹، u1 = hash·w، u2 = r·w و نقطهٔ Q = u1·G + u2·K. اگر مختصات x نقطهٔ Q برابر r باشد، امضا معتبر است. در این فرآیند کلید خصوصی نه معلوم است و نه فاش میشود.
امضای تراکنش در عمل
مراحل: (۱) ساخت ساختار داده با ۹ فیلد (nonce, gasPrice, gasLimit, to, value, data, chainID, 0, 0)، (۲) سریالسازی RLP، (۳) محاسبهٔ هش Keccak-256، (۴) امضای هش با کلید خصوصی، (۵) افزودن v, r, s. مقدار v هم شناسهٔ زنجیره و هم شناسهٔ بازیابی (زوج/فرد بودن y) را رمز میکند: ۲۷ یا ۲۸ در امضاهای قدیمی، یا chainID×2 + 35 یا 36.
ساخت و امضای تراکنش خام
مثال با کتابخانهٔ ethereumjs-tx (فایل raw_tx_demo.js) نشان میدهد چگونه داده تراکنش ساخته، RLP سریال و هش میشود و با کلید خصوصی امضا میشود تا تراکنش خام امضاشده آماده پخش باشد.
EIP-155 (محافظت از حملهٔ بازپخش)
از هاردفورک Spurious Dragon (بلاک ۲٬۶۷۵٬۰۰۰). شناسهٔ زنجیره را پیش از امضا به داده اضافه میکند تا تراکنش یک شبکه (مثلاً اصلی) در شبکهٔ دیگر (مثلاً Ethereum Classic یا Ropsten) نامعتبر باشد. شناسهها: میننت اتریوم ۱، Ropsten ۳، Rinkeby ۴، Kovan ۴۲، اتریوم کلاسیک ۶۱، شبکههای آزمایشی خصوصی Geth ۱۳۳۷ و غیره.
مقدار پیشوند v و بازیابی کلید عمومی
چون «from» در تراکنش نیست، کلید عمومی فرستنده از امضا بازیابی میشود (public key recovery). از r دو نقطه R و R' بهدست میآید (تقارن منحنی نسبت به محور x) و دو کلید عمومی ممکن حاصل میشود؛ مقدار v مشخص میکند کدام درست است (زوج: R، فرد: R’).
جداسازی امضا و ارسال (امضای آفلاین)
ساخت، امضا و پخش معمولاً یک عملیاتاند (sendTransaction)، اما میتوان جدا کرد و تراکنش امضاشده را با sendSignedTransaction پخش کرد. دلیل اصلی امنیت است: رایانهٔ امضاکننده کلید بازشده در حافظه دارد و رایانهٔ پخشکننده آنلاین است؛ ترکیب هر دو خطرناک است. روند:
- ساخت تراکنش امضانشده روی رایانهٔ آنلاین (برای خواندن nonce و موجودی)؛
- انتقال به دستگاه آفلاین air-gapped برای امضا (مثلاً با QR یا USB)؛
- انتقال تراکنش امضاشده برای پخش.
درجات جداسازی: از زیرشبکهٔ ایزولهٔ دارای دیوار آتش (با پروتکل صف پیام مثل ZeroMQ) تا سیستم کاملاً آفلاین. جداسازی کامل مقیاسپذیر نیست اما حتی جداسازی ناقص امنیت را بسیار بالا میبرد.
انتشار تراکنش
شبکهٔ اتریوم از پروتکل flood routing استفاده میکند. هر کلاینت یک گره همتاست و هیچ گرهای ویژه نیست. گره مبدأ تراکنش امضاشده را اعتبارسنجی و برای همسایگان (بهطور متوسط حداقل ۱۳ همسایه) ارسال میکند؛ هر همسایه اگر معتبر دید ذخیره و برای همسایههای خود (بهجز فرستنده) پخش میکند. در چند ثانیه همه گرهها آن را دارند. هیچ گرهای نمیتواند مبدأ را تشخیص دهد و مهاجم برای ردیابی باید درصد قابلتوجهی از گرهها را کنترل کند.
ثبت روی بلاکچین
گرههای ماینر تراکنشها را به بلاک نامزد اضافه میکنند و در پی اثبات کار هستند (فصل ۱۴). تراکنشهای معتبر سرانجام در بلاک قرار میگیرند و حالت را تغییر میدهند (تغییر موجودی یا حالت داخلی قراردادها)؛ نتایج بهصورت رسید تراکنش (و رویدادها) ثبت میشود (فصل ۱۳).
تراکنشهای چندامضایی (Multisig)
تراکنشهای مقدار ساده EOA چندامضایی را پشتیبانی نمیکنند، اما با قرارداد کیف پول میتوان هر محدودیت امضا یا سقف خرج را پیاده کرد: اتر به قرارداد منتقل میشود و همهٔ امضاکنندگان لازم باید به قرارداد تراکنش بفرستند تا آن را مجاز کنند. امنیت به کد قرارداد بستگی دارد؛ انعطافپذیری شمشیر دولبه است و ممکن است باگ بیاورد. پیشنهادهایی برای یک فرمان چندامضایی داخل EVM (مشابه بیتکوین) وجود دارد.
جمعبندی
تراکنشها نقطهٔ شروع هر فعالیت در اتریوماند: ورودیهایی که EVM را برای ارزیابی قراردادها و بهروزرسانی حالت فعال میکنند. فصل بعد به قراردادهای هوشمند و زبان Solidity میپردازد.
فصل ۷ — قراردادهای هوشمند و Solidity
دو نوع حساب
- EOA: حساب ساده بدون کد یا ذخیرهسازی داده؛ با تراکنشهایی که در دنیای بیرونِ پروتکل با کلید خصوصی امضا میشوند کنترل میشود.
- حساب قرارداد: هم کد دارد و هم ذخیرهسازی داده؛ کلید خصوصی ندارد و مطابق منطق از پیش تعیینشدهٔ کدش «خودش را کنترل میکند».
این فصل به حسابهای قرارداد و کدی که آنها را کنترل میکند میپردازد.
قرارداد هوشمند چیست؟
اصطلاح را نیک سابو در دههٔ ۱۹۹۰ ساخت (مجموعهای از وعدهها به شکل دیجیتال، شامل پروتکلهایی برای انجام آنها). در اتریوم این اصطلاح تا حدی نامناسب است چون این قراردادها نه «هوشمند»اند و نه «قرارداد حقوقی». تعریف کتاب: برنامههای رایانهای تغییرناپذیر که بهصورت قطعی در بستر EVM و بهعنوان بخشی از پروتکل شبکه اجرا میشوند. اجزای تعریف:
- برنامهٔ رایانهای: «قرارداد» در اینجا معنای حقوقی ندارد.
- تغییرناپذیر (immutable): پس از استقرار، کد عوض نمیشود؛ تنها راه تغییر، استقرار نمونهٔ جدید است.
- قطعی (deterministic): با توجه به زمینهٔ تراکنش و حالت بلاکچین، نتیجه برای همه یکسان است.
- زمینهٔ محدود EVM: دسترسی فقط به حالت خود، زمینهٔ تراکنش فراخواننده و اطلاعاتی دربارهٔ بلاکهای اخیر.
- کامپیوتر جهانی غیرمتمرکز: EVM روی هر گره محلی اجرا میشود ولی همه از یک حالت اولیه به یک حالت نهایی میرسند.
چرخهٔ حیات قرارداد
- نوشتن به زبان سطح بالا (مثل Solidity) و کامپایل به بایتکد EVM.
- استقرار با تراکنش ایجاد قرارداد (مقصد
0x0). آدرس قرارداد تابعی از حساب سازنده و nonce است. - سازنده هیچ امتیاز ویژهای در سطح پروتکل ندارد (مگر اینکه خودش در کد قرارداد برنامهریزی شود) و کلید خصوصی وجود ندارد: «قراردادها صاحب خودشاناند».
- قرارداد فقط وقتی اجرا میشود که تراکنشی (مستقیم یا در زنجیرهای از فراخوانیها) آن را فعال کند؛ اولین حلقه همیشه تراکنش یک EOA است. قراردادها در پسزمینه یا مستقل اجرا نمیشوند و EVM را میتوان تکنخی دانست.
- تراکنشها اتمی هستند: در صورت هر خطا همهٔ تغییرات حالت برگشت میخورد (مثل اینکه هرگز اجرا نشده)، اما تلاش ثبت میشود و گس مصرفشده کسر میشود.
- کد قابل تغییر نیست، اما قرارداد با opcode به نام
SELFDESTRUCT(پیشترSUICIDE) «حذف» میشود: کد و ذخیرهسازی از آن آدرس پاک میشود و حساب خالی میماند. این عملیات «گس منفی» (بازپرداخت) دارد تا آزادسازی منابع تشویق شود. تاریخچه حذف نمیشود و تنها در صورتی ممکن است که نویسنده این قابلیت را در کد گذاشته باشد.
مقدمهای بر زبانهای سطح بالا
برنامهنویسی مستقیم با بایتکد دشوار است. زبانها به دو الگو تقسیم میشوند: اعلانی/تابعی (declarative) که منطق را بدون جریان و بدون اثر جانبی بیان میکند (Haskell، SQL) و دستوری/رویهای (imperative) که منطق و جریان را ترکیب میکند (C++، Java). در قراردادهای هوشمند اشکال مستقیماً هزینه دارد، پس استدلال دربارهٔ رفتار برنامه حیاتی است و زبانهای اعلانی ارزش بیشتری دارند؛ با این حال پرکاربردترین زبان (Solidity) دستوری است.
زبانهای پشتیبانیشده (به ترتیب قدمت):
- LLL: تابعی با نحو Lisp، نخستین زبان سطح بالا، اکنون کماستفاده؛
- Serpent: رویهای با نحو Python؛
- Solidity: رویهای، نحو شبیه JavaScript/C++/Java، محبوبترین و عملاً زبان استاندارد؛
- Vyper: جدیدتر، Python-مانند، نزدیکتر به تابعی؛
- Bamboo: الهامگرفته از Erlang، با گذارهای حالت صریح و بدون حلقه.
ساخت قرارداد با Solidity
Solidity را گَوین وود ساخت و کریستین رایتوایسنر و دیگران توسعه دادند؛ اکنون پروژهای مستقل در گیتهاب است. محصول اصلی آن کامپایلر solc و استاندارد ABI است.
- نسخهبندی معنایی:
MAJOR.MINOR.PATCH. چون در نسخهٔ ۰.x هر چیزی ممکن است تغییر کند، عملاً Solidity عدد «minor» را مثل major و «patch» را مثل minor استفاده میکند (۰٫۴٫۲۴ یعنی نسخهٔ اصلی ۴ و فرعی ۲۴). - نصب: مثلاً در اوبونتو با
aptو مخزنppa:ethereum/ethereum؛ بررسی باsolc --version. - محیط توسعه: هر ویرایشگر متن کافی است (فایل با پسوند
.sol)؛ Remix و EthFiddle محیطهای وباند.
کامپایل و ABI
solc --optimize --bin Faucet.sol بایتکد بهینهشده تولید میکند. ABI (Application Binary Interface) رابط دودویی بین ماژولهاست؛ در اتریوم برای کدگذاری فراخوانی قرارداد برای EVM و خواندن داده از تراکنشها به کار میرود. بهصورت آرایهٔ JSON از توصیف توابع (type, name, inputs, outputs, constant, payable) و رویدادها (type, name, inputs, anonymous) است و با solc --abi تولید میشود. برای تعامل با یک قرارداد فقط ABI و آدرس آن لازم است.
دستور pragma نسخه
pragma solidity ^0.4.19; میگوید برنامه حداقل با ۰٫۴٫۱۹ سازگار است و ^ یعنی هر نسخهٔ فرعی بالاتر (۰٫۴٫۲۰) مجاز، اما نه ۰٫۵٫۰. pragma به بایتکد تبدیل نمیشود و بهترینشیوه است.
برنامهنویسی با Solidity
انواع داده
boolبا عملگرهای منطقی؛int/uintاز ۸ تا ۲۵۶ بیت (بدون پسوند ۲۵۶ بیت، هماندازهٔ کلمهٔ EVM)؛fixed/ufixedاعشار ثابت؛address۲۰ بایتی با اعضایbalanceوtransfer؛- آرایهٔ بایت ثابت (
bytes1تاbytes32) و پویا (bytes,string)؛ enum، آرایه،structوmapping(جدول هش کلید–مقدار).
لیترالهای واحد: زمان (seconds, minutes, hours, days) و اتر (wei, finney, szabo, ether). مثلاً 0.1 ether بهجای عدد بلند wei خوانایی را بالا میبرد.
متغیرها و توابع سراسری از پیشتعریفشده
msg (زمینهٔ فراخوانی):
msg.sender: آدرسی که این فراخوانی را آغاز کرده (لزوماً EOA اولیه نیست)؛msg.value: اتر ارسالی (wei)؛msg.data: بارِ داده؛msg.sig: ۴ بایت اول داده (انتخابگر تابع)؛msg.gas: گس باقیمانده (منسوخ به نفعgasleft).
با فراخوانی قرارداد دیگر، مقادیر msg عوض میشوند، مگر در delegatecall.
tx: tx.gasprice و tx.origin (آدرس EOA آغازگر؛ ناامن).
block: blockhash, coinbase, difficulty, gaslimit, number, timestamp.
شیء address:
balance؛transfer(amount): ارسال و پرتاب استثنا در صورت خطا؛send(amount): مثل transfer ولیfalseبرمیگرداند (خروجی را حتماً بررسی کنید)؛call(payload): فراخوانی سطح پایین؛ ناامن (گیرنده میتواند همهٔ گس را مصرف کند)؛callcode: منسوخ؛delegatecall: اجرای کد دیگر در زمینهٔ کاملmsgفعلی؛ فقط برای کاربران پیشرفته.
توابع داخلی: addmod, mulmod, هشها (keccak256, sha256, sha3, ripemd160)، ecrecover (بازیابی آدرس امضاکننده)، selfdestruct و this.
تعریف قرارداد و انواع مشابه
contract ظرفی از داده و متد است. دو نوع مشابه: interface (فقط اعلان توابع، بدون پیادهسازی؛ هر وارث باید همه را تعریف کند) و library (یک بار مستقر میشود و با delegatecall توسط قراردادهای دیگر استفاده میشود).
توابع
الگوی اعلان: function Name([params]) {public|private|internal|external} [pure|constant|view|payable] [modifiers] [returns (...)]
- یک تابع بینام در هر قرارداد = fallback (بدون آرگومان و بدون خروجی).
- دید (visibility):
public(پیشفرض)،external(از داخل فقط باthis.)،internal(داخل قرارداد و وارثان) وprivate(فقط همان قرارداد). توجه: «private» به معنای پنهانبودن داده نیست؛ همه چیز روی بلاکچین عمومی قابل مشاهده است و این کلمات فقط روی نحوه و زمان فراخوانی اثر دارند. - رفتار:
view/constant(وعدهٔ نداشتن تغییر حالت)،pure(نه خواندن و نه نوشتن storage؛ تشویق سبک اعلانی) وpayable(پذیرش پرداخت؛ تابع غیر payable پرداخت را رد میکند، با دو استثنا: coinbase و وراثتSELFDESTRUCT).
سازنده (constructor) و selfdestruct
سازنده یک بار هنگام ایجاد قرارداد در همان تراکنش اجرا و سپس دور ریخته میشود. تا ۰٫۴٫۲۱ سازنده تابعی همنام قرارداد بود که با تغییر نام یا غلط املایی عملاً به یک تابع عادی قابل فراخوانی تبدیل میشد و میتوانست باعث ربودن مالکیت شود. از ۰٫۴٫۲۲ کلیدواژهٔ constructor معرفی شد. قرارداد فقط در صورتی قابل حذف است که selfdestruct(address) را صریحاً در خود داشته باشد؛ این به کاربران تضمین میدهد قراردادِ بدون آن جاودانه است.
در مثال Faucet: متغیر address owner با msg.sender در سازنده پر میشود و تابع destroy() با require(msg.sender == owner) محافظت میشود. از tx.origin استفاده نشده چون قراردادهای مخرب میتوانستند به نام مالک آن را فراخوانی کنند.
اصلاحکنندهٔ تابع (modifier)
مثل onlyOwner: شرطی که روی توابع متعدد اعمال میشود. علامت _; جای کد تابع اصلاحشده است (modifier مثل پوششی دور تابع). چندین modifier به ترتیب اعمال میشوند. modifier به متغیرهای قابل دید تابع دسترسی دارد، اما برعکس نه. این الگو خوانایی و قابلیت حسابرسی را افزایش میدهد.
وراثت
contract Child is Parent و وراثت چندگانه با is Parent1, Parent2. مثال کتاب: قرارداد پایه owned (مالک و onlyOwner)، mortal is owned (تابع destroy) و Faucet is mortal. هدف: ماژولار بودن، توسعهپذیری، استفادهٔ مجدد و خواناتر بودن.
مدیریت خطا (assert, require, revert, throw)
هنگام خطا، همهٔ تغییرات حالت (در کل زنجیرهٔ فراخوانی) برگشت میخورد و اتمیبودن حفظ میشود.
assert: برای شرایط داخلی که انتظار میرود همیشه درست باشند؛require: برای اعتبارسنجی ورودیها (آرگومانها، فیلدهای تراکنش) و بهصورت «دروازه»؛ از ۰٫۴٫۲۲ پیام متنی اختیاری دارد؛revert: توقف و برگشت با پیام اختیاری؛throw: منسوخ.
بررسی صریح موجودی پیش از transfer پیام بهتری میدهد ولی اندکی گس بیشتری میخواهد؛ تعادل بین هزینهٔ گس و جزئیات پیام باید بسته به کاربرد (testnet در مقابل mainnet) انتخاب شود.
رویدادها (Events)
هر تراکنش رسیدی (receipt) با ورودیهای لاگ دارد (فصل ۱۳). رویدادها اشیای سطح بالای Solidity برای ساخت این لاگها هستند و برای کلاینتهای سبک و DApp که باید «ناظر» رویدادها باشند مفیدند. کلیدواژهٔ indexed پارامتر را در جدول قابلجستجو و فیلتر قرار میدهد. با emit منتشر میشوند. در مثال Faucet: Withdrawal(address indexed to, uint amount) و Deposit(address indexed from, uint amount). با Truffle میتوان این رویدادها را از res.logs[0].event و .args مشاهده کرد. رویدادها برای ارتباط و اشکالزدایی هم به کار میروند.
فراخوانی قراردادهای دیگر
فراخوانی قرارداد دیگر مفید اما خطرناک است چون ممکن است قرارداد مقابل پیچیده و حتی مخرب باشد.
- ساخت نمونهٔ جدید با
new: امنترین راه؛ نوع و رابط را دقیقاً میدانید (باimportاگر تعریف در فایل دیگر است). میتوان اتر و آرگومانهای سازنده را هم داد. مالک قرارداد جدید همان قرارداد سازنده است (نه شما). - تبدیل آدرس موجود به نوع قرارداد (
Faucet(_f)): ریسک بیشتر چون مطمئن نیستید آدرس واقعاً همان نوع است. callوdelegatecallخام: انعطافپذیرترین و خطرناکترین؛ مثل ساخت تراکنش خام از درون قرارداد. باید مقدار برگشتی (falseدر خطا) بررسی شود؛ خطر مهم reentrancy (بخش امنیت).delegatecallفرق مهمی باcallدارد: زمینهٔmsg(از جملهmsg.sender) را عوض نمیکند و کد دیگری را در زمینهٔ ذخیرهسازی قرارداد فعلی اجرا میکند (برای کتابخانهها).
مثال CallExamples.sol نشان میدهد: فراخوانی مستقیم قرارداد msg.sender را برابر قرارداد فراخواننده میکند، ولی فراخوانی کتابخانه همیشه delegatecall است و msg.sender همان فرستندهٔ اصلی و this آدرس فراخواننده باقی میماند.
ملاحظات گس
اگر سقف گس رد شود: استثنای «out of gas»، برگشت حالت و سوختن کل اتر گس بهعنوان کارمزد. توصیهها:
- از آرایههای با اندازهٔ پویا پرهیز کنید (حلقه روی آنها خطر مصرف بیش از حد گس دارد)؛
- فراخوانی قراردادهای دیگر، بهویژه کتابخانههای کمآزموده، خطر اتمام گس دارد.
برآورد هزینه: با estimateGas روی متد قرارداد و getGasPrice میتوان هزینه را بر حسب اتر تخمین زد (مثال Faucet حدود ۳۱٬۳۹۷ گس ≈ ۰٫۰۰۰۶۲۷۹۴ اتر با قیمت ۲۰ gwei). چون EVM تورینگکامل است برآورد تقریبی است. محاسبهٔ هزینه را در گردش کار توسعه بگنجانید تا هنگام استقرار روی شبکهٔ اصلی غافلگیر نشوید.
جمعبندی
با قرارداد ساده Faucet شروع و آن را گامبهگام بهتر کردیم تا جنبههای Solidity را ببینیم. فصل بعد زبان Vyper را مقایسه میکند.
فصل ۸ — قراردادهای هوشمند و Vyper
Vyper چیست؟
Vyper یک زبان برنامهنویسی تجربی و قراردادمحور برای EVM است که هدف اصلیاش قابلیت حسابرسی (auditability) بالاتر است. یکی از اصول طراحی آن این است که نوشتن کدِ گمراهکننده عملاً غیرممکن باشد. فصل ابتدا مشکلات رایج قراردادها را معرفی و سپس Vyper را با Solidity مقایسه میکند.
آسیبپذیریها و Vyper
پژوهشی روی نزدیک به یک میلیون قرارداد مستقرشده سه دستهٔ آسیبپذیری را مشخص کرد:
- قراردادهای «خودکشیگرا» (Suicidal): هر آدرسی میتواند آنها را نابود کند؛
- قراردادهای «حریص» (Greedy): به حالتی میرسند که دیگر نمیتوانند اتر را آزاد کنند؛
- قراردادهای «ولخرج» (Prodigal): میتوان وادارشان کرد اتر را به آدرسهای دلخواه آزاد کنند.
Vyper با طراحی خود نوشتن کد ناامن یا گمراهکننده را سختتر میکند و برای این کار عمداً برخی قابلیتهای Solidity را کنار گذاشته است.
مقایسه با Solidity: آنچه Vyper ندارد
۱. اصلاحکنندهها (Modifiers)
modifierها فقط برای بررسی پیششرط نیستند؛ میتوانند محیط قرارداد را در زمینهٔ تابع فراخواننده تغییر دهند. مثال: modifier به نام stageTimeConfirmation در ظاهر زمان قرارداد را بررسی میکند، اما پنهانی تابع nextStage() را صدا میزند و متغیر stage را از SafeStage به DangerStage میبرد. توسعهدهندهای که تازه با قرارداد آشنا شده ممکن است این را نبیند. Vyper modifier را کلاً حذف کرده است: بررسیها را بهصورت assert درون خود تابع بنویسید و تغییرات حالت را هم صریحاً داخل تابع انجام دهید تا خواننده مجبور نباشد کد modifier را ذهنی دور تابع «بپیچد».
۲. وراثت کلاس
Solidity وراثت چندگانه و چندریختی (polymorphism) دارد؛ Vyper ندارد. استدلال: وراثت باعث میشود برنامهنویس و حسابرس برای فهم برنامه بین فایلها بپرد، و وراثت چندگانه کد را بیش از حد پیچیده میکند (حتی مستندات Solidity مثالی از مشکلساز بودن آن دارد).
۳. اسمبلی درونخطی (Inline Assembly)
دسترسی سطح پایین به دستورات EVM از داخل Solidity؛ Vyper معتقد است افت خوانایی بهای سنگینی برای این قدرت است و آن را پشتیبانی نمیکند.
۴. سربارگذاری تابع (Function Overloading)
چند تابع همنام با آرگومانهای متفاوت (مثلاً f(uint) و f(uint, bytes32)) سردرگمکننده است؛ Vyper آن را ندارد.
۵. تبدیل نوع (Typecasting)
تبدیل ضمنی (implicit) در نسخههای اولیهٔ Vyper بود اما حذف شد. در Solidity تبدیل صریح میتواند رفتار غیرمنتظره داشته باشد (مثلاً تبدیل uint32 به uint16 بیتهای بالا را بیصدا حذف میکند). Vyper تابع convert دارد که با جدول تبدیل (int128, uint256, decimal, bytes32) کار میکند و اگر تبدیل اطلاعات را از بین ببرد، استثنا میاندازد. نتیجه: کد پرحرفتر اما امنتر و قابلحسابرسیتر.
پیششرطها و پسشرطها
Vyper پیششرط، پسشرط و تغییر حالت را صریح مدیریت میکند. برنامهنویس سه نکته را باید ملاحظه کند:
- شرط (Condition): وضعیت فعلی متغیرهای حالت چیست؟
- اثرات (Effects): اجرای کد چه اثری بر متغیرها دارد و آیا با هدف قرارداد همخوان است؟
- تعامل (Interaction): پس از بررسی دو مورد قبل، تمام پیامدها و سناریوهای دائمی اجرا (از جمله تعامل با قراردادهای دیگر) را پیش از استقرار مرور کنید.
مستندسازی دقیق این موارد در کد، طراحی را بهتر میکند.
دکوراتورها (Decorators)
@private: تابع از بیرون قرارداد در دسترس نیست؛@public: تابع عمومی و قابل اجرا از بیرون (و نمایش در کیف پول)؛@constant: تابع حق تغییر متغیرهای حالت را ندارد و در صورت تلاش، کامپایلر برنامه را رد میکند؛@payable: فقط توابع دارای آن میتوانند مقدار انتقال دهند.
منطق دکوراتورها صریح است: ترکیب @payable و @constant شکست کامپایل میدهد (چون انتقال مقدار حالت را تغییر میدهد). هر تابع باید دقیقاً یکی از @public یا @private را داشته باشد.
ترتیب توابع و متغیرها
هر قرارداد Vyper در یک فایل است و ترتیب اعلانها اهمیت دارد (مثل پایتون): متغیر یا تابع باید پیش از استفاده اعلان شود. Solidity این الزام را ندارد. نمونه: اگر theBool بعد از topFunction اعلان شود یا topFunction بعد از lowerFunction، قرارداد کامپایل نمیشود.
کامپایل
Vyper ویرایشگر و کامپایلر آنلاین دارد که بایتکد، ABI و LLL تولید میکند و قراردادهای نمونه (مزایدهٔ باز، خرید راه دور امن، توکن ERC20 و غیره) دارد. ERC20 بهصورت قرارداد از پیشکامپایلشده (precompiled) پشتیبانی میشود. در خط فرمان، هر قرارداد در فایلی با پسوند .vy ذخیره و با vyper کامپایل میشود؛ خروجی ABI با vyper -f json.
محافظت در برابر سرریز در سطح کامپایلر
سرریز عدد صحیح میتواند در پول واقعی فاجعهبار باشد. مثال: تراکنش آوریل ۲۰۱۸ که مقدار نجومی توکن BEC (حدود ۵٫۸×۱۰⁷⁶) را بهدلیل سرریز در قرارداد ERC20 شرکت BeautyChain منتقل کرد. در Solidity ابزارهایی مثل کتابخانهٔ SafeMath و ابزار تحلیل Mythril وجود دارد، اما توسعهدهنده مجبور به استفاده از آنها نیست و کد ناامن هم کامپایل و «با موفقیت» اجرا میشود. Vyper محافظت دولایه دارد: ۱) معادل SafeMath با استثناهای لازم برای حساب صحیح؛ ۲) clamp هنگام بارگذاری ثابت، ارسال مقدار به تابع یا تخصیص به متغیر (با توابع سفارشی در کامپایلر LLL؛ غیرقابل غیرفعالسازی). خروجی کامپایلر Vyper بهجای بایتکد، LLL است که توسعه را ساده میکند.
خواندن و نوشتن داده
ذخیرهسازی پرهزینه اما برای بیشتر قراردادها ضروری است. دو محل نوشتن:
- حالت سراسری (Global state): متغیرهای حالت در درخت حالت سراسری اتریوم؛ قرارداد فقط به دادههای مربوط به آدرس خودش دسترسی دارد (نمیتواند حالت قرارداد دیگر را بخواند یا بنویسد).
- لاگها (Logs): رویدادها (مثلاً
MyLog: event({arg1: indexed(bytes[3])})و فراخوانی باlog.MyLog("123")). قرارداد نمیتواند لاگهای خودش را بخواند، اما کلاینتهای سبک میتوانند آنها را روی زنجیره کشف کنند:logsBloomدر سرآیند بلاک وجود لاگ را نشان میدهد و داده از رسید تراکنش برداشته میشود.
جمعبندی
Vyper زبانی جذاب و قراردادمحور است که بهجای انعطاف بیشتر، صحت را ترجیح میدهد. شاید بخشی از ظرافت طراحی آن پس از مطالعهٔ مسائل امنیتی فصل بعد روشنتر شود.
فصل ۹ — امنیت قراردادهای هوشمند
امنیت از مهمترین ملاحظات نوشتن قرارداد هوشمند است، چون خطا گران تمام میشود و بهراحتی مورد سوءاستفاده قرار میگیرد. قرارداد دقیقاً همان کاری را میکند که نوشته شده، نه لزوماً آنچه برنامهنویس قصد داشت؛ همهٔ قراردادها عمومیاند و هر کاربر با یک تراکنش میتواند با آنها تعامل کند؛ هر آسیبپذیری قابل بهرهبرداری است و زیان تقریباً همیشه جبرانناپذیر. این فصل بهترین شیوهها، الگوهای طراحی و «ضدالگوهای امنیتی» را مرور میکند.
بهترین شیوههای امنیتی (برنامهنویسی تدافعی)
- حداقلگرایی / سادگی: پیچیدگی دشمن امنیت است. هرچه کد سادهتر و کمکارتر باشد، احتمال باگ کمتر است. اگر پروژهای «هزاران خط» قرارداد دارد، به امنیتش مشکوک شوید.
- استفادهٔ مجدد از کد: چرخ را دوباره اختراع نکنید؛ از کتابخانهها و قراردادهای آزموده استفاده کنید و اصل DRY (تکرار نکن) را رعایت کنید. از «سندروم اختراع نشده در اینجا» (Not Invented Here) بپرهیزید.
- کیفیت کد: قرارداد هوشمند بخشش ندارد. با دقت مهندسی هوافضا برخورد کنید، نه ساخت ویجت وب.
- خوانایی / قابلیت حسابرسی: کد قابلفهم، مستند و مطابق سبک جامعه؛ چون بایتکد عمومی است و همه میتوانند مهندسی معکوس کنند، توسعهٔ علنی و متنباز مفید است.
- پوشش آزمون: همهچیز را آزمایش کنید؛ هرگز فرض نکنید ورودیها خوشفرم یا بینیت بدند.
ریسکها و ضدالگوها
۱. Reentrancy (ورود مجدد)
قراردادها اتر میفرستند و کد قراردادهای دیگر را فراخوانی میکنند؛ این فراخوانیها ممکن است توسط مهاجم ربوده شوند، بهطوری که از طریق تابع fallback قرارداد مهاجم، دوباره وارد قرارداد قربانی شوند. حمله به DAO از این نوع بود.
آسیبپذیری (مثال EtherStore): خزانهای که هر کاربر را به برداشت حداکثر ۱ اتر در هفته محدود میکند، اما اتر را با msg.sender.call.value(...)() پیش از بهروزرسانی balances و lastWithdrawTime میفرستد. قرارداد مهاجم (Attack) در fallback خود دوباره withdrawFunds را صدا میزند؛ چون هنوز موجودی کسر نشده، همهٔ شرطها دوباره برقرارند و این چرخه تا خالیشدن موجودی تکرار میشود. نتیجه: تقریباً کل اتر در یک تراکنش بیرون میرود.
پیشگیری:
- استفاده از
transfer(فقط ۲۳۰۰ گس میدهد و برای ورود مجدد کافی نیست)؛ - الگوی checks-effects-interactions: ابتدا شرطها، سپس تغییر حالت، و فراخوانی بیرونی همیشه آخر؛
- mutex (قفل متغیر حالت) برای مسدود کردن فراخوانیهای تودرتو.
مثال واقعی: هک DAO (بیش از ۱۵۰ میلیون دلار) که به هاردفورکی انجامید و اتریوم کلاسیک را پدید آورد.
۲. سرریز و زیرریز حسابی (Over/Underflow)
EVM اعداد صحیح با اندازهٔ ثابت دارد. مثلاً uint8 فقط ۰ تا ۲۵۵ را نگه میدارد؛ ذخیرهٔ ۲۵۶ به ۰ میرسد و کمکردن ۱ از ۰ مقدار ۲۵۵ میدهد (شبیه کیلومترشمار خودرو یا تناوب توابع مثلثاتی).
مثال TimeLock: خزانهٔ زمانبندیشده که کاربر میتواند increaseLockTime را با مقدار 2²⁵⁶ − userLockTime صدا بزند تا lockTime سرریز شود و صفر گردد و بلافاصله اتر را برداشت کند. مثال Ethernaut: در یک قرارداد توکن، شرط balances[msg.sender] - _value >= 0 برای عدد بدون علامت همیشه درست است و کاربر با موجودی صفر میتواند توکن رایگان بگیرد.
پیشگیری: کتابخانههای ریاضی امن مثل SafeMath از OpenZeppelin که جمع، تفریق و ضرب را با assert بررسی میکنند (تقسیم سرریز ندارد).
مثالهای واقعی: PoWHC (شمع پونزی با ۸۶۶ اتر خروجی غیرمجاز) و آسیبپذیری batchTransfer در توکنهای ERC20 (CVE-2018-10299).
۳. اتر غیرمنتظره (Unexpected Ether)
اتر میتواند بدون اجرای هیچ کدی وارد قرارداد شود، پس تکیه بر this.balance بهعنوان «پایدار» خطرناک است. دو راه:
selfdestruct: قراردادی نابود میشود و موجودی را به آدرس هدف میفرستد بدون فراخوانی هیچ تابعی (حتی fallback)؛- اتر پیشارسالشده: آدرس قرارداد از
sha3(rlp.encode([address, nonce]))قطعی است و میتوان پیش از ساخت، اتر به آن فرستاد.
مثال EtherGame: بازیای که پیشرفت را با this.balance و مضربهای ۰٫۵ اتر میسنجد؛ مهاجم با ارسال اجباری ۰٫۱ اتر موجودی را از مضرب ۰٫۵ خارج میکند و بازی قفل میشود، یا با ارسال مقداری بیش از حد نهایی، claimReward برای همیشه شکست میخورد.
پیشگیری: منطق را به مقدار دقیق this.balance وابسته نکنید؛ از متغیر خودتان (مثل depositedWei) استفاده کنید که فقط در توابع payable افزایش مییابد. همچنین اصل ثابتهای (invariant) را مستقل از this.balance بنویسید.
۴. DELEGATECALL
DELEGATECALL کد مقصد را در زمینهٔ ذخیرهسازی فراخواننده اجرا میکند و msg.sender و msg.value ثابت میمانند؛ پایهٔ کتابخانههاست اما خطر دارد.
مثال FibonacciLib / FibonacciBalance: کتابخانه متغیر start را در slot 0 و calculatedFibNumber را در slot 1 دارد، ولی قرارداد اصلی در slot 0 آدرس کتابخانه را نگه میدارد. بهدلیل تفاوت چیدمان ذخیرهسازی، setFibonacci بهجای متغیر مورد نظر روی متغیر دیگر مینویسد، و چون fallback تمام فراخوانیها را به کتابخانه تفویض میکند، مهاجم با setStart میتواند slot 0 (آدرس کتابخانه) را به آدرس قرارداد مخرب تغییر دهد و کل موجودی را بدزدد. نکته: «حفظ حالت» در delegatecall به اسلاتهای ذخیرهسازی اشاره دارد، نه نام متغیرها.
پیشگیری: استفاده از کلیدواژهٔ library (بدون حالت و غیرقابل selfdestruct)، بیحالت (stateless) نگهداشتن کتابخانهها، و توجه دقیق به زمینهٔ فراخوانی.
مثال واقعی – هک دوم Parity Multisig: کیف پولها همهٔ فراخوانیها را با delegatecall به قرارداد کتابخانه (WalletLibrary) میفرستادند، اما خود کتابخانه مقداردهی نشده بود. کاربری initWallet را روی کتابخانه صدا زد، مالک آن شد و kill را اجرا کرد. کتابخانه نابود شد و همهٔ کیفپولهایی که به آن ارجاع داشتند و راهی برای تغییر مرجع نداشتند، کارکردشان (از جمله برداشت اتر) برای همیشه از دست رفت.
۵. دید پیشفرض (Default Visibility)
توابع بدون مشخصکردن دید بهطور پیشفرض public هستند. مثال HashForEther: _sendWinnings که باید داخلی باشد، عمومی مانده و هرکس میتواند موجودی را بردارد. پیشگیری: همیشه دید همهٔ توابع را صریح بنویسید (نسخههای جدید solc هشدار میدهند).
مثال واقعی – هک اول Parity Multisig: حدود ۳۱ میلیون دلار اتر. توابع initWallet و initMultiowned دید مشخص نداشتند و عمومی بودند؛ مهاجم مالکیت کیف پولهای مستقرشده را به آدرس خود تغییر داد و آنها را خالی کرد.
۶. توهم آنتروپی (Entropy Illusion)
تراکنشها قطعیاند، پس منبع تصادفی واقعی داخل بلاکچین وجود ندارد. مثال: قمار روی بلاکچین. استفاده از متغیرهای بلاک (هش، زمانسنج، شمارهٔ بلاک، سقف گس) خطرناک است چون ماینر میتواند آنها را دستکاری کند (مثلاً بلاکی را که به نفعش نیست منتشر نکند و دوباره ماین کند). همچنین مقدار آن برای همهٔ تراکنشهای یک بلاک یکسان است.
پیشگیری: منبع آنتروپی باید خارج از بلاکچین باشد: commit–reveal بین همتایان، مدل اعتماد گروهی مثل RANDAO، یا یک اوراکل تصادفی متمرکز. مثال واقعی: تحلیل ۳٬۶۴۹ قرارداد دارای PRNG که ۴۳ تای آنها قابل بهرهبرداری بودند.
۷. ارجاع به قرارداد بیرونی (External Contract Referencing)
در Solidity هر آدرسی را میتوان به نوع قرارداد تبدیل کرد، حتی اگر کدش از آن نوع نباشد. مثال EncryptionContract: آدرس کتابخانهٔ رمزنگاری در سازنده تعیین میشود؛ مستقرکننده میتواند آدرس قراردادی را بدهد که رفتاری متفاوت دارد (مثلاً Rot26 که کاری نمیکند یا Print که متن خام را در رویداد چاپ میکند). حتی قرارداد حسابرسیشده را میتوان خراب مستقر کرد. اگر تابع در قرارداد ارجاعی نباشد، fallback اجرا میشود.
پیشگیری: ساخت نمونه با new، هاردکد کردن آدرسها، عمومی کردن آدرسهای بیرونی برای بررسی کاربران؛ متغیر آدرس خصوصی نشانهٔ احتمالی رفتار مخرب است؛ در صورت امکان تغییر آدرس، از time-lock یا رأیگیری استفاده شود.
مثال واقعی – Honey Pot: قرارداد Private_Bank که به ظاهر باگ reentrancy دارد، اما قراردادی که در سازنده جایگزین Log میشود مهاجم را فریب میدهد و اتر او را میگیرد.
۸. حملهٔ آدرس/پارامتر کوتاه (Short Address Attack)
حمله به خود قرارداد نیست، بلکه به برنامههای شخص ثالث (مثل صرافی). اگر پارامتر کوتاهتر از انتظار فرستاده شود (مثلاً آدرس ۱۹ بایتی)، EVM صفرهایی را به انتهای داده اضافه میکند. در تابع transfer(address, uint)، چون آدرس اول است، حذف یک بایت از آدرس باعث میشود مقدار توکن ۲۵۶ برابر شود (۱۰۰ توکن ← ۲۵٬۶۰۰). مهاجم آدرسی تولید میکند که به صفر ختم میشود. پیشگیری: اعتبارسنجی همهٔ ورودیها در برنامههای بیرونی و ترتیب مناسب پارامترها در قرارداد.
۹. مقادیر بازگشتی بررسینشدهٔ CALL
call و send در صورت شکست استثنا نمیاندازند و فقط false برمیگردانند. مثال Lotto: winner.send(winAmount) بدون بررسی، سپس payedOut = true. اگر ارسال شکست بخورد، برنده پولی نمیگیرد اما حالت «پرداختشده» ثبت میشود و دیگران باقیمانده را برمیدارند.
پیشگیری: ترجیح transfer یا بررسی همیشگی نتیجهٔ send؛ بهتر از آن الگوی برداشت (withdrawal pattern) که ارسال اتر را جدا و بار شکست احتمالی را بر دوش کاربر برداشتکننده میگذارد. مثالهای واقعی: Etherpot (علاوه بر خطای هش بلاک، send بررسینشده) و King of the Ether.
۱۰. شرایط رقابتی / Front-Running
ماینر تراکنشها را معمولاً بر اساس gasPrice مرتب میکند؛ مهاجم استخر تراکنشها را زیر نظر میگیرد و تراکنش مشابهی با gasPrice بالاتر میفرستد. مثال FindThisHash: جایزهٔ ۱۰۰۰ اتر برای کسی که پیشتصویر یک هش را پیدا کند؛ مهاجم راهحل را از استخر میبیند و با کارمزد بیشتر جایزه را میبرد.
پیشگیری: سقف gasPrice (فقط در برابر کاربران، نه ماینرها)؛ commit–reveal (ابتدا هش را ارسال کنید، بعد از ثبت، مقدار را افشا کنید؛ مقدار تراکنش را نمیپوشاند — ENS با تعهد مبلغ و بازپرداخت تفاوت این مشکل را حل کرده است)؛ و «ارسال زیردریایی» (submarine sends) که به CREATE2 نیاز دارد.
مثالهای واقعی: تابع approve در ERC20 (آلیس مجوز ۱۰۰ را به ۵۰ تغییر میدهد، باب با front-running ۱۰۰ را خرج میکند و بعد ۵۰ دیگر هم، در مجموع ۱۵۰) و Bancor (قیمتها بر اساس ارزش تراکنش و قابل بهرهبرداری با front-run).
۱۱. انکار سرویس (DoS)
الگوهایی که قرارداد را موقت یا دائمی غیرفعال میکنند:
- حلقه روی آرایه/نگاشت قابل دستکاری بیرونی (
distribute()برای توزیع توکن؛ مهاجم با حسابهای زیاد گس را از سقف بلاک بالاتر میبرد)؛ - عملیات مختص مالک (مثلاً
finalize()در ICO؛ گمشدن کلید مالک کل سیستم را از کار میاندازد؛ باگisFinalized == trueبهجای=هم در مثال)؛ - پیشرفت حالت بر اساس فراخوانی بیرونی (مثلاً قراردادی که اتر نمیپذیرد، پیشرفت را متوقف میکند).
پیشگیری: الگوی برداشت بهجای حلقه؛ مالک چندامضایی یا time-lock (مثلاً require(msg.sender == owner || now > unlockTime))؛ در نظر گرفتن شکست فراخوانیهای بیرونی و پیشرفت زمانی. جایگزین متمرکز (کاربر نگهدارنده) مشکل اعتماد میآورد.
مثال واقعی – GovernMental: طرح پونزی با ۱۱۰۰ اتر که برای برداشت نیاز به حذف نگاشت بزرگی داشت که گس آن از سقف بلاک بیشتر بود؛ تا زمانی که سقف گس بالا رفت (۲٫۵ میلیون گس) پول قفل ماند.
۱۲. دستکاری زمانسنج بلاک
block.timestamp (و now) را ماینر تا حدی میتواند تنظیم کند. مثال Roulette: برنده اگر now % 15 == 0؛ ماینر انگیزه دارد زمان را طوری تنظیم کند. محدودیتها: زمان باید از بلاک قبلی بیشتر باشد و نه خیلی در آینده.
پیشگیری: هرگز برای آنتروپی یا تصمیمگیری مهم از زمانسنج استفاده نکنید؛ برای منطق زمانی بهتر است از block.number و میانگین زمان بلاک استفاده شود (مثلاً قرارداد ICO توکن BAT). مثال واقعی: GovernMental که برندهٔ آخرین بازیکن (حداقل یک دقیقه ماندگار) بود و ماینر میتوانست زمان را جعل کند.
۱۳. سازندهها با احتیاط
پیش از Solidity 0.4.22 سازنده تابعی همنام قرارداد بود؛ تغییر نام قرارداد یا غلط املایی آن را به تابع عادی تبدیل میکرد. مثال OwnerWallet: حرف اول نام تابع تفاوت دارد و هرکس با فراخوانی آن مالک میشود. پیشگیری: کلیدواژهٔ constructor. مثال واقعی: Rubixi (نام قرارداد از DynamicPyramid به Rubixi تغییر کرد ولی سازنده نه).
۱۴. اشارهگرهای ذخیرهسازی مقداردهینشده
متغیرهای محلی پیچیده (مثل struct) بهصورت پیشفرض در storage هستند و اگر مقداردهی نشوند به slot 0 اشاره میکنند و متغیرهای دیگر (مثل unlocked) را بازنویسی میکنند. مثال NameRegistrar: NameRecord newRecord; بدون مقداردهی، newRecord.name را روی slot 0 مینویسد که همان unlocked است؛ یعنی با _name مناسب میتوان قفل را باز کرد. پیشگیری: به هشدارهای کامپایلر توجه کنید و memory / storage را صریح بنویسید. مثالهای واقعی: honey potهای OpenAddressLottery و CryptoRoulette.
۱۵. ممیز شناور و دقت
Solidity (تا نسخهٔ ۰٫۴٫۲۴) ممیز ثابت/شناور ندارد و باید با اعداد صحیح ساخته شود. مثال FunWithNumbers: msg.value/weiPerEth*tokensPerEth پیش از ضرب تقسیم میکند و برای کمتر از ۱ اتر نتیجه صفر است؛ فروش کمتر از ۱۰ توکن هم صفر اتر میدهد و گردکردن همیشه به پایین است.
پیشگیری: از نسبتهای با صورت بزرگ استفاده کنید (مثل weiPerToken)، ترتیب عملیات را رعایت کنید (اول ضرب، بعد تقسیم)، و با دقت بالاتر محاسبه و در انتها کاهش دهید (مشابه decimals در ERC20 و کتابخانهٔ DS-Math با «wad» و «ray»). مثال واقعی: Ethstick (دقت در سطح wei، اما مشکل اصلی آنتروپی).
۱۶. احراز هویت با tx.origin
tx.origin آدرس EOA آغازگر کل زنجیره است. مثال Phishable: مالک با require(tx.origin == owner) تأیید میشود. اگر مالک فریب بخورد و به قرارداد مهاجم تراکنشی بفرستد، fallback آن withdrawAll را روی قرارداد قربانی صدا میزند و چون tx.origin مالک است، شرط پاس میشود. پیشگیری: هرگز از tx.origin برای مجوزدهی استفاده نکنید (کاربرد مشروع: require(tx.origin == msg.sender) برای ممانعت از قراردادهای واسط).
کتابخانههای قرارداد
استفاده از کد آزموده شده مزایای زیادی دارد (بهرهمندی از ارتقاها، صرفهجویی هزینه، کاهش قراردادهای زنده). مهمترین مجموعه OpenZeppelin است: پیادهسازی ERC20 و ERC721، مدلهای فروش اولیه و الگوهایی مثل Ownable, Pausable و LimitBalance، که تا حدی پیادهسازی مرجع شدهاند. ZeppelinOS پلتفرمی برای توسعه و مدیریت امن برنامههای ارتقاپذیر است و ethpm یک سامانهٔ مدیریت بسته برای اکوسیستم.
جمعبندی
اصل بنیادین امنیت نرمافزار، بیشینهسازی استفادهٔ مجدد از کد قابلاعتماد است؛ معادل ضربالمثل رمزنگاری «رمزنگاری خودت را ننویس»: تا حد ممکن از کتابخانههای آزاد و بررسیشده توسط جامعه بهره ببرید.
فصل ۱۰ — توکنها (Tokens)
توکن چیست؟
واژهٔ «توکن» از ریشهٔ قدیمی انگلیسی به معنی نشانه و نماد است و بهطور سنتی به سکهمانندهای خصوصی با ارزش ذاتی ناچیز (مثل ژتون حملونقل یا لباسشویی) اشاره دارد. محدودیت این توکنهای فیزیکی این بود که به یک کسبوکار یا مکان و یک کارکرد محدود بودند. توکنهای بلاکچینی این محدودیتها را برمیدارند (یا کاملاً قابلتعریف میکنند): انتزاعهایی قابل مالکیت که دارایی، ارز یا حق دسترسی را نمایندگی میکنند و در بازارهای نقدشونده جهانی قابل مبادلهاند، پس انتظار «ارزش ناچیز» دیگر صدق نمیکند.
کاربردهای توکن
ارز تنها نخستین «اپ» است. یک توکن میتواند همزمان چند نقش داشته باشد:
- ارز: ارزشی که از معامله خصوصی تعیین میشود؛
- منبع: مثل توکن فضای ذخیرهسازی یا CPU در اقتصاد اشتراکی؛
- دارایی: مالکیت طلا، ملک، خودرو، نفت، انرژی، آیتمهای بازی و غیره؛
- دسترسی: عضویت در یک انجمن، سایت اختصاصی، اتاق هتل یا خودرو کرایهای؛
- سهام: سهامداری در یک سازمان دیجیتال (DAO) یا نهاد قانونی؛
- رأی: حق رأی در سامانهٔ دیجیتال یا قانونی؛
- کلکسیونی: مثل CryptoPunks یا اثر هنری فیزیکی؛
- هویت: هویت دیجیتال (آواتار) یا قانونی (کارت ملی)؛
- گواهی/تصدیق: شناسنامه، مدرک دانشگاهی، سند ازدواج؛
- سودمندی (Utility): دسترسی یا پرداخت برای خدمات.
اغلب یک توکن چند کارکرد را با هم دارد (مثل گواهینامهٔ رانندگی که هم گواهی و هم هویت است)؛ در فضای دیجیتال این کارکردها را میتوان جدا کرد (مثل گواهی ناشناس).
توکن و جایگزینپذیری (Fungibility)
توکن جایگزینپذیر (fungible) است اگر هر واحد بتواند بدون تفاوت در ارزش یا کارکرد جایگزین واحد دیگر شود. اگر بتوان سابقهٔ هر واحد را ردیابی کرد، جایگزینپذیری کامل نیست (امکان فهرست سیاه و سفید). توکن جایگزینناپذیر (non-fungible، NFT) یک آیتم منحصربهفرد را نمایندگی میکند (مثلاً یک تابلو یا یک CryptoKitty) و با شناسهای یکتا مثل شمارهٔ سریال مشخص میشود. (اینجا «fungible» به معنی «قابل تبدیل مستقیم به پول» نیست.)
ریسک طرف مقابل (Counterparty Risk)
ریسک اینکه طرف دیگر به تعهدش عمل نکند. وقتی دارایی از طریق توکن مالکیت معامله میشود، اغلب حداقل سه طرف هست: فروشنده، خریدار و نگهدارندهٔ دارایی؛ آیا او دارایی را دارد و انتقال توکن را به رسمیت میشناسد؟
توکن و ذاتیبودن (Intrinsicality)
- دارایی ذاتی (intrinsic): دارایی دیجیتالی که خودش «درون» بلاکچین است و تحت قواعد اجماع قرار دارد (مثلاً CryptoKitty)؛ داشتن کلید یعنی مالکیت مستقیم و ریسک طرف مقابل اضافی ندارد.
- دارایی غیرذاتی (extrinsic): مثل ملک، سهام شرکتی، علامت تجاری یا شمش طلا؛ مالکیت آن تابع قانون، عرف و سیاست بیرون از بلاکچین است و ریسک طرف مقابل دارد (نگهدارنده، ثبت بیرونی).
یکی از پیامدهای مهم، توانایی تبدیل دارایی غیرذاتی به ذاتی است (مثل رفتن از سهام شرکت به توکن سهام/رأی در یک DAO).
استفاده از توکن: سودمندی یا سهام
بیشتر پروژههای اتریوم با توکن راهاندازی میشوند. دو نقش اصلی (که اغلب با هم قاطی میشوند):
- توکن سودمندی (utility): لازمهٔ دسترسی به خدمت یا منبع؛
- توکن سهام (equity): نمایندهٔ سهم کنترل یا مالکیت، از سهام بدون حق رأی تا سهام رأیدار در DAO.
«اگر مثل اردک راه میرود…»
توکن مکانیزم عالی جمعآوری سرمایه است، اما عرضهٔ سهام به عموم در بیشتر کشورها فعالیتی تحت نظارت است. بسیاری از استارتاپها توکن سهام را در لباس «توکن سودمندی» (کوپن دسترسی به خدمت) عرضه میکنند. اما نهادهای ناظر احتمالاً فریب بازی با کلمات را نمیخورند.
چه کسی به توکن سودمندی نیاز دارد؟
توکن سودمندی برای استارتاپ ریسک و مانع پذیرش اضافی میآورد: هر نوآوری یک فیلتر بازار است و افزودن توکن، کاربر اولیه را مجبور میکند دو فناوری جدید را بپذیرد (محصول و اقتصاد توکن). ریسکها شامل پلتفرم، اقتصاد بزرگتر (صرافیها و نقدشوندگی)، محیط نظارتی و فناوری هستند. از طرف مقابل، طرفداران «همهچیز را توکنایز کن» میگویند با توکن، شور بازار، کاربران اولیه و نقدشوندگی کل اقتصاد توکن را به ارث میبرید. اگر توکن فقط در محدودهٔ پلتفرم شما کاربرد داشته باشد، دقیقاً شرایطی را بازسازی میکنید که توکنهای فیزیکی را بیارزش کرد (company scrip). توصیه: توکن را فقط وقتی بپذیرید که برنامه بدون آن کار نمیکند یا مانع اساسی بازار را برمیدارد، نه صرفاً برای جمعآوری سریع پول و پنهانکردن اینکه عرضهٔ عمومی اوراق است.
توکنها در اتریوم
پروتکل اتریوم از توکنها خبر ندارد. ارسال اتر یک عمل درونی پلتفرم است ولی ارسال یا حتی مالکیت توکن در سطح قرارداد هوشمند مدیریت میشود. برای ساخت توکن جدید باید یک قرارداد بنویسید که مالکیت، انتقال و دسترسی را مدیریت میکند؛ بهتر است از استاندارد موجود پیروی کنید.
استاندارد ERC20
اولین استاندارد را فابین فوگلشتلر در نوامبر ۲۰۱۵ بهصورت «درخواست نظر اتریوم» (ERC) ارائه کرد؛ شمارهٔ ۲۰ در گیتهاب نام آن شد (بعدها EIP-20). بیشتر توکنها بر پایهٔ ERC20 هستند. این استاندارد برای توکنهای جایگزینپذیر است و یک رابط مشترک تعریف میکند.
توابع و رویدادهای الزامی:
totalSupply: کل واحدهای موجود؛balanceOf(owner): موجودی آدرس؛transfer(to, value): انتقال از موجودی فراخواننده؛transferFrom(from, to, value): انتقال بین دو حساب (همراه با approve)؛approve(spender, value): مجاز کردن آدرس برای چند انتقال تا سقفی؛allowance(owner, spender): باقیماندهٔ مجوز؛- رویدادهای
TransferوApproval.
اختیاری: name، symbol، decimals.
ساختار داده: دو نگاشت — mapping(address => uint256) balances برای موجودیها و نگاشت دوبعدی allowed برای مجوزها.
دو روند: transfer و approve ‑ transferFrom
transfer: یک تراکنش؛ کیفپولها برای ارسال توکن از آن استفاده میکنند (اکثر معاملات). قرارداد توکن موجودی فرستنده را کم و گیرنده را زیاد و رویدادTransferمنتشر میکند.approve+transferFrom: دو تراکنش؛ مالک کنترل را به آدرس دیگر (معمولاً قرارداد توزیع، مثل قرارداد فروش در ICO) تفویض میکند. مثال: آلیس ۵۰٪ AliceCoin را برای قرارداد AliceICO تأیید میکند و این قرارداد هنگام دریافت اتر از باب، با نرخ تبدیلtransferFromرا فراخوانی میکند.
(ICO یعنی عرضهٔ اولیهٔ سکه — مکانیزم تأمین مالی جمعی از طریق فروش توکن، بر خلاف IPO بسیار باز، جهانی و بیقاعده؛ ذکر آن در کتاب به معنی تأیید نیست.)
پیادهسازیها
ERC20 را میتوان در حدود ۳۰ خط پیادهسازی کرد ولی پیادهسازیهای واقعی برای امنیت پیچیدهترند. دو نمونهٔ ذکرشده در EIP-20: Consensys EIP20 (ساده و خوانا) و OpenZeppelin StandardToken (با احتیاطهای امنیتی بیشتر و پایهای برای توکنهای پیچیدهتر با سقف جمعآوری، مزایده، vesting و …).
ساخت توکن ERC20 اختصاصی با Truffle
توکن «Mastering Ethereum Token» با نماد MET:
- ایجاد پروژه (
truffle init,npm init) و تنظیمtruffle-config.js(کلیدهای آزمایشی در.env؛ هرگز کلید حاوی پول واقعی برای آزمون استفاده نکنید). - نصب
openzeppelin-solidityو نوشتن قراردادMETokenکه ازStandardTokenارث میبرد، باdecimals = 2و عرضه ۲۱ میلیون توکن (۲٫۱ میلیارد واحد) که همهٔ آن به سازنده داده میشود و رویدادTransferاز آدرس صفر منتشر میشود. - نوشتن اسکریپت migration، اجرای شبکهٔ محلی
ganache، استقرار باtruffle migrate. - تعامل در
truffle console:totalSupply،balanceOf، وtransfer(۱۰۰٬۰۰۰ واحد = ۱۰۰۰ MET چون دو رقم اعشار).
ارسال توکن به آدرس قرارداد
اگر MET را به قرارداد Faucet بفرستیم، برای همیشه گیر میکند: فقط خود Faucet میتواند آن را جابهجا کند و کدی برای فراخوانی transfer ندارد. این تجربهٔ صدها کاربر واقعی است؛ بر اساس برخی برآوردها، بیش از ۲٫۵ میلیون دلار توکن به همین شکل قفل شده است. یک راه رایج اشتباه: ارسال به آدرس دریافت صرافی که در واقع قرارداد فقطاتر است.
نمایش روند approve و transferFrom
قرارداد METFaucet: بهجای اتر، MET میدهد. با متغیرهای METoken (نوع StandardToken) و METOwner، تابع withdraw سقف را ۱۰ MET (۱۰۰۰ واحد) قرار میدهد و METoken.transferFrom(METOwner, msg.sender, amount) را صدا میزند. fallback اتر را با revert() رد میکند. مالک ابتدا approve(METFaucet.address, 100000) میزند و سپس یک حساب دیگر withdraw میکند. نتیجه: بار مدیریت صحیح ERC20 روی رابط کاربری است.
مشکلات ERC20
- انتقال توکن، حالت قرارداد توکن را تغییر میدهد، نه حالت آدرس گیرنده: مقصد تراکنش خود قرارداد توکن است. کیف پول از موجودی توکن خبردار نمیشود مگر قرارداد آن را «تماشا» کند.
- بسیاری از توکنها مثل هرزنامهاند و برای حسابهای پرسابقه موجودی ساختگی ایجاد میکنند؛ در واقع آدرس شما در آن قراردادها هست، نه توکن در آدرس.
- توکنها
payableگیرنده را فعال نمیکنند (در ERC20). - برای ارسال توکن هم باید اتر برای گس داشته باشید؛ نمیتوان با توکن گس پرداخت. تجربهٔ کاربری عجیب: کسی که توکن را دریافت کرده، بدون اتر نمیتواند آن را بفرستد.
برخی از این مسائل نیاز به تغییر رابط توکن، برخی نیاز به تغییر ساختار اتریوم (تفاوت EOA/قرارداد، تراکنش/پیام) و برخی نیاز به طراحی رابط کاربری دارند.
ERC223
برای جلوگیری از انتقال ناخواسته به قراردادهایی که توکن نمیپذیرند: قراردادهای پذیرندهٔ توکن باید تابع tokenFallback را پیاده کنند و اگر مقصد قرارداد باشد و آن را نداشته باشد، انتقال شکست میخورد. تشخیص قرارداد با extcodesize در اسمبلی درونخطی انجام میشود. این استاندارد چندان پذیرفته نشد و بحث بر سر سازگاری با گذشته و تعادل بین تغییر در سطح قرارداد یا رابط کاربری ادامه دارد.
ERC777
اهداف: رابط سازگار با ERC20؛ انتقال با send شبیه اتر؛ سازگاری با ERC820 (ثبت رابط)؛ قلابهای tokensToSend (پیش از ارسال) و tokensReceived (اطلاع گیرنده و جلوگیری از قفلشدن توکن، چون قراردادهای گیرنده باید آن را فراهم کنند)؛ یکسان بودن رفتار برای EOA و قرارداد؛ رویدادهای ضرب (mint) و سوزاندن (burn)؛ اپراتورها (طرفهای ثالث مورد اعتماد که از طرف دارنده توکن جابهجا میکنند)؛ و فرادادهٔ userData و operatorData. هر آدرس فقط یک فرستنده و یک گیرندهٔ قلاب ثبت میکند. بحث بر سر پیچیدگی پذیرش همزمان دو تغییر بزرگ (استاندارد جدید توکن و ثبت رابط) ادامه دارد.
ERC721 (توکن جایگزینناپذیر / Deed)
برای مالکیت یک چیز یگانه: آیتم دیجیتال (بازی، کلکسیون) یا فیزیکی (خانه، خودرو، اثر هنری)، یا حتی چیزهای با ارزش منفی (وام، حق رهن). هر deed با شناسه ۲۵۶ بیتی یکتا مشخص میشود. تفاوت بنیادین با ERC20: در ERC20 کلید نگاشت «مالک» است ولی در ERC721 کلید نگاشت «شناسهٔ deed» است (mapping(uint256 => address) deedOwner) و از همین تفاوت همهٔ ویژگیهای NFT ناشی میشود. توابع: balanceOf, ownerOf, transfer, transferFrom, approve, setApprovalForAll, supportsInterface و رویدادهای Transfer, Approval, ApprovalForAll. دو رابط اختیاری: فراداده (name, symbol, deedUri) و شمارشپذیری (totalSupply, deedByIndex, ownerByIndex, …).
استفاده از استانداردهای توکن
- هدف استانداردها: حداقل مشخصات برای قابلیت همکاری؛ هر کیف پول و صرافی بهصورت قابلپیشبینی با هر قرارداد منطبق کار میکند. استانداردها توصیفیاند، نه تجویزی (نحوهٔ پیادهسازی با شماست).
- آیا باید استفاده کرد؟ استاندارد نوآوری را محدود میکند ولی تجربهٔ صدها برنامه و زیرساخت موجود را میدهد. نادیدهگرفتن آنها همان سندروم «Not Invented Here» است. تصمیم دشوار است.
- امنیت از طریق بلوغ: پیادهسازیهای رایج «جنگآزموده»اند؛ نوشتن از صفر پر از ظرافتهای خطرناک است. گسترش آنها هم سطح حمله را بزرگ میکند («پیچیدگی دشمن امنیت است»).
گسترشهای رایج رابط توکن
کنترل مالک (فهرست سیاه/سفید، ضرب، بازیابی)، سوزاندن، ضرب (mint)، جمعآوری سرمایه (مزایده و فروش)، سقف عرضه، درهای پشتی بازیابی، فهرست سفید (مثلاً سرمایهگذاران معتبر) و فهرست سیاه. برای بسیاری از اینها پیادهسازی مرجع هست (مثلاً در OpenZeppelin) ولی استاندارد پذیرفتهشدهای نیست. تصمیم گسترش همیشه مبادلهٔ نوآوری/ریسک در برابر همکاریپذیری/امنیت است.
توکنها و ICOها
اهمیت استانداردها نباید با تأیید عرضههای فعلی توکن اشتباه گرفته شود. موج نخست محصولات و شرکتها تقریباً همه شکست میخورند؛ بسیاری از توکنهای فعلی کلاهبرداری یا طرح هرمیاند. باید چشمانداز بلندمدت فناوری (که احتمالاً بزرگ است) را از حباب کوتاهمدت ICOهای پرتقلب جدا کرد.
جمعبندی
توکنها مفهومی قدرتمند و پایهٔ بسیاری از برنامههای غیرمتمرکزند. در این فصل انواع و استانداردها را دیدید و نخستین توکن و برنامهٔ مرتبط را ساختید؛ در فصل ۱۲ از یک توکن جایگزینناپذیر برای یک DApp مزایده استفاده میشود.
فصل ۱۱ — اوراکلها (Oracles)
اوراکل چیست؟
واژهٔ «اوراکل» (پیشگو) از اساطیر یونان میآید: شخصی که با خدایان در ارتباط بود و آینده را میدید. در بلاکچین، اوراکل سامانهای است که میتواند به پرسشهایی که بیرون از اتریوم هستند پاسخ دهد. در حالت ایدهآل اوراکل «بینیاز از اعتماد» (trustless) است، یعنی بر اصول غیرمتمرکز کار میکند و لازم نیست به آن اعتماد کرد.
چرا اوراکل لازم است؟
برای حفظ اجماع، اجرای EVM باید کاملاً قطعی باشد و فقط به زمینهٔ مشترک حالت اتریوم و تراکنشهای امضاشده تکیه کند. دو پیامد مهم:
- منبع تصادفی ذاتی در EVM نمیتواند وجود داشته باشد. اگر یک گره عدد ۳ و گره دیگر عدد ۷ را ذخیره کند، به حالتهای متفاوت میرسند و اجماع از بین میرود، و اثرات زنجیرهای (مثل انتقال اتر) بهسرعت نمایی میشود. توابع شبهتصادفی (مثل هش) هم برای بسیاری از کاربردها کافی نیستند؛ مثلاً در قمار، ماینر میتواند فقط بلاکهایی را منتشر کند که برایش برندهاند.
- دادههای بیرونی فقط بهعنوان بارِ داده یک تراکنش وارد میشوند. تراکنشهای امضاشده را همه میتوانند ببینند، اما درستی دادهای که از منبع تأییدنشده میآید قابل اعتماد نیست؛ پس مشکل فقط به تعویق افتاده است. اوراکلها برای حل همین مسئله پدید آمدهاند.
موارد استفاده
اوراکلها اطلاعات بیرونی (نتیجهٔ مسابقه، قیمت طلا، عدد تصادفی واقعی) را برای قراردادها فراهم میکنند و میتوانند داده را مستقیم به رابط DApp هم برسانند. این گستره را بسیار وسیع میکند اما ریسک بیرونی به مدل امنیتی اتریوم میافزاید. مثال: «وصیتنامهٔ هوشمند» که پس از مرگ شخص دارایی را توزیع میکند؛ اگر مبلغ بالا باشد، انگیزهٔ هک اوراکل و اعلام زودهنگام مرگ بسیار زیاد است.
بعضی اوراکلها دادهای مختص منبع خصوصی ارائه میکنند (مدرک تحصیلی، شناسنامه دولتی). منبع کاملاً مورد اعتماد است و حقیقت آن ذهنی است (تنها با ارجاع به مرجعیت منبع تعیین میشود)، پس بدون اعتماد به منبع ممکن نیست. اینها هم اوراکل محسوب میشوند و دادهشان معمولاً «گواهی/تصدیق» (attestation) است که برای هویت و اعتبار در آینده نقش بزرگی خواهد داشت.
نمونههای دیگر داده: اعداد تصادفی از فرایندهای فیزیکی (کوانتومی/حرارتی) برای لاتاری؛ محرکهای پارامتریک مبتنی بر مخاطرات طبیعی (مثل مقیاس ریشتر برای اوراق زلزله)؛ نرخ ارز برای اتصال دقیق ارزهای دیجیتال به پول رایج؛ داده بازار سرمایه؛ نرخهای مرجع بهره برای مشتقات؛ دادهٔ ایستا (کد کشور/ارز)؛ زمان؛ آبوهوا برای بیمه؛ رویدادهای سیاسی و ورزشی برای بازارهای پیشبینی؛ داده موقعیت جغرافیایی زنجیره تأمین؛ تأیید خسارت؛ رویدادهای زنجیرههای دیگر؛ قیمت بازار اتر؛ آمار پرواز.
الگوهای طراحی اوراکل
هر اوراکل سه کارکرد کلیدی دارد: ۱) جمعآوری داده از منبع برونزنجیرهای؛ ۲) انتقال داده به زنجیره با پیام امضاشده؛ ۳) در دسترس قرار دادن آن در ذخیرهسازی یک قرارداد هوشمند (که دیگر قراردادها از طریق تابع «retrieve» یا گرهها با نگاه مستقیم به ذخیرهسازی میخوانند).
سه شکل اصلی:
۱. خواندن فوری (Immediate-read)
دادهای که فقط برای تصمیم فوری لازم است («این فرد بالای ۱۸ سال است؟»، «آدرس ethereumbook.info چیست؟»). داده یک بار در ذخیرهسازی قرارداد قرار میگیرد (و قابل بهروزرسانی است)، قراردادهای دیگر با فراخوانی آن را میخوانند و برنامههای متصل به کلاینت بدون هزینهٔ تراکنش مستقیماً از ذخیرهسازی میخوانند. برای سازمانها جذاب است چون لازم نیست سرور نگه دارند. معمولاً خود داده ذخیره نمیشود، بلکه هش آن (یا ریشهٔ درخت مرکل با نمک) برای کارایی و حریم خصوصی.
۲. انتشار–اشتراک (Publish–subscribe)
اوراکل مثل یک سرویس پخش برای دادهای که تغییر میکند (قیمتها، آبوهوا، آمار) است؛ شبیه RSS. مشترکها یا خودشان نظرسنجی (poll) میکنند یا به رویدادهای قرارداد گوش میدهند. در بلاکچین نظرسنجی بر خلاف وب ناکارآمد نیست، چون کلاینت همه تغییرات حالت را دارد و خواندن محلی است؛ لاگ رویدادها هم شبیه سرویس «push» عمل میکنند. ولی نظرسنجی از داخل قرارداد گس زیادی مصرف میکند.
۳. درخواست–پاسخ (Request–response)
پیچیدهترین: فضای داده برای ذخیره در قرارداد بیش از حد بزرگ است و کاربر هر بار فقط بخش کوچکی را میخواهد (یا مدل کسبوکار ارائهدهنده داده است). معمولاً مجموعهای از قراردادهای زنجیرهای بهعلاوهٔ زیرساخت برونزنجیرهای برای پایش درخواستها. فرآیند ناهمگام است: EOA با DApp تراکنش میزند ← تابع اوراکل درخواست را (با آرگومانها و احتمالاً تابع بازگشتی و زمانبندی) شروع میکند ← رویداد یا تغییر حالت دیده میشود ← پرسش از منبع برونزنجیرهای انجام میشود (ممکن است پرداخت و مجوز دسترسی لازم باشد) ← نتیجه توسط مالک اوراکل امضا و در تراکنشی به DApp تحویل داده میشود.
مراحل: دریافت پرسش ← تجزیه ← بررسی پرداخت و مجوز ← بازیابی داده (و رمزگذاری در صورت نیاز) ← امضای تراکنش ← پخش ← زمانبندی تراکنشهای بعدی. اوراکل میتواند انسان، نرمافزار یا سختافزار (حسگر IoT) باشد.
درخواست–پاسخ همیشه مناسب نیست: مثلاً اوراق هوشمند که نرخ بهره را میخواهد و نرخ بهره بهندرت تغییر میکند؛ مدل انتشار–اشتراک با ذخیرهٔ نرخ در خود اوراکل و خواندن آن توسط چند DApp به پهنای باند اتریوم کمتر فشار میآورد. در الگوی پخش/چندپخشی اوراکل پیامها را روی کانال میگذارد و مشترکها با حالتهای مختلف (کل سری زمانی برای میانگین متحرک یا فقط آخرین نرخ) گوش میدهند.
احراز اصالت داده
حتی اگر منبع داده معتبر باشد، اوراکل و سازوکار درخواست–پاسخ ممکن است توسط نهادهای متفاوت اداره شوند و داده در راه دستکاری شود. دو رویکرد:
- اثبات اصالت (Authenticity proofs): تضمینهای رمزنگاری که داده دستنخورده است و اعتماد را از حامل داده به گواهیدهنده منتقل میکنند. مثال: Oraclize با اثبات TLSNotary؛ این اثباتها شواهدی برای شخص ثالث ارائه میدهند که ترافیک HTTPS بین کلاینت و سرور رخ داده است. کلید اصلی TLS بین سه طرف (سرور/اوراکل، حسابرسیشونده Oraclize و حسابرس) تقسیم میشود؛ Oraclize از یک ماشین مجازی AWS تغییرنیافته بهعنوان حسابرس استفاده میکند. فرض لازم این است که خود آمازون در ماشین دست نمیبرد.
- محیط اجرای قابل اعتماد (TEE): مانند Town Crier که از Intel SGX استفاده میکند: یکپارچگی (CPU از برنامهٔ داخل enclave در برابر سایر فرایندها محافظت میکند)، محرمانگی (حالت برنامه مات است) و گواهیدهی (اثبات امضاشده که برنامه با هش بیلد مشخص داخل enclave اجرا میشود). همین ویژگی اجازه میدهد پرسوجوهای خصوصی با کلید عمومی Town Crier رمز شوند. فرض: به Intel/SGX اعتماد دارید.
اوراکلهای محاسباتی
اوراکلها علاوه بر تحویل داده میتوانند محاسبهٔ دلخواه را انجام دهند، که با توجه به سقف گس بلاک و هزینهٔ گران محاسبه در اتریوم مفید است (مثلاً رگرسیون سنگین برای برآورد بازده یک اوراق).
- Oraclize: اجرای محاسبه در ماشین مجازی AWS ایزوله با کانتینر داکر که از آرشیو آپلودشده در IPFS (با هش) بارگذاری میشود؛ خروجی استاندارد به DApp برمیگردد. قابل حسابرسی ولی غیرمتمرکز نیست.
- Cryptlet (از چارچوب ESC مایکروسافت): اجرای داخل کپسول رمزنگاریشده که زیرساخت (I/O) را انتزاع میکند، پیامها را خودکار امضا و اعتبارسنجی میکند و تراکنشهای توزیعشده با ویژگی ACID را پشتیبانی میکند.
- TrueBit: راهحل غیرمتمرکزتر برای محاسبهٔ برونزنجیرهای مقیاسپذیر و قابلراستیآزمایی. حلکنندهها و راستیآزماها با مشوق کار میکنند و در صورت اعتراض، یک «بازی راستیآزمایی» روی زنجیره بهصورت دوربهدور زیرمجموعههای کوچکتری از محاسبه را بررسی میکند تا در دور آخر داوران (ماینرها) رأی نهایی بدهند. در عمل «بازار محاسبه» است. مثال: پل Doge–Ethereum که اثبات کار Scrypt دوجکوین را (که در سقف گس اتریوم نمیگنجد) راستیآزمایی میکند.
اوراکلهای غیرمتمرکز
اوراکلهای متمرکز نقطهٔ شکست واحدند. طرحهای غیرمتمرکز:
- ChainLink: سه قرارداد (اعتبار، تطبیق سفارش و تجمیع) و ثبتنام برونزنجیرهای فراهمکنندگان. قرارداد اعتبار عملکرد ارائهدهندگان را ثبت میکند؛ قرارداد تطبیق سفارش پیشنهادها را بر اساس اعتبار انتخاب و توافق سطح خدمات را نهایی میکند؛ قرارداد تجمیع پاسخها (با commit–reveal) را جمع و نتیجهٔ نهایی را محاسبه میکند و به قرارداد اعتبار بازمیخورد. چالش اصلی تعریف تابع تجمیع و تشخیص پاسخ نامعتبر است، چون داده پرت لزوماً غلط نیست؛ ChainLink مجموعهای استاندارد از قراردادهای تجمیع میدهد و امکان سفارشیسازی.
- SchellingCoin: چند شرکتکننده مقدار گزارش میکنند و میانه پاسخ درست است؛ سپرده گزارشدهندگان به نفع مقادیر نزدیکتر به میانه بازتوزیع میشود، پس همگرایی روی «نقطه شلینگ» تشویق میشود.
- طرح جیسون توئچ (TrueBit): اوراکل دسترسپذیری داده بر پایهٔ یک بلاکچین اثبات کار اختصاصی که ماینرها دادههای ثبتشده را دانلود، ذخیره و پخش میکنند. هزینهبر است اما پس از پایان دورهٔ ثبت، ذخیرهسازی آزاد میشود.
رابطهای کلاینت اوراکل در Solidity
مثال Oraclize (EthUsdPriceTicker): قراردادی که از usingOraclize ارث میبرد و هر ۱۰ دقیقه قیمت ETH/USD را از API (مثلاً CryptoCompare با JSONPath) میگیرد. تابع oraclize_query منبع داده (URL، WolframAlpha، IPFS، computation) و آرگومان آن را میگیرد. هزینهٔ اندک اتری شامل گس بازگشتی و کارمزد سرویس لازم است. نتیجه توسط حسابی کنترلشده با Oraclize به تابع __callback با queryId داده میشود و قرارداد بررسی میکند که فراخواننده آدرس مجاز باشد، رشته را با parseInt به عدد صحیح تبدیل کند و پرسوجوی بعدی را دوباره زمانبندی کند.
مثال BlockOne IQ (Thomson Reuters): اوراکل داده مالی برای شبکههای خصوصی/مجاز. کلاینت با initRequest نوع درخواست و دو تابع بازگشتی (موفقیت/شکست) را مشخص میکند، آرگومانها (کد ابزار RIC مثل IBM و زمان) را با addArgumentToRequestString/Uint اضافه و با executeRequest اجرا میکند. در موفقیت، فیلدهای OHLC و bid/ask خوانده و پاسخ حذف میشود.
جمعبندی
اوراکلها خدمتی حیاتی ارائه میدهند — واقعیتهای بیرونی را وارد اجرای قرارداد میکنند — اما اگر منبع قابلاعتماد هک شود، اجرای قراردادهای وابسته هم مخدوش میشود. هنگام انتخاب اوراکل، مدل اعتماد را با دقت بسنجید؛ اوراکلهای غیرمتمرکز بعضی نگرانیها را برطرف میکنند و پل بین اتریوم و «دنیای واقعی» را ممکن میسازند.
فصل ۱۲ — برنامههای غیرمتمرکز (DApps)
از قرارداد هوشمند تا Web3
از همان روزهای نخست، چشمانداز بنیانگذاران اتریوم بسیار فراتر از «قرارداد هوشمند» بود: بازآفرینی وب و ساخت جهانی از DAppها به نام Web3. قراردادهای هوشمند منطق کنترل و کارکرد پرداخت برنامه را غیرمتمرکز میکنند، اما Web3 میخواهد همهٔ جنبههای دیگر — ذخیرهسازی، پیامرسانی، نامگذاری و … — را نیز غیرمتمرکز کند.
هشدار: واژهٔ «DApp» اغلب برای هر قرارداد هوشمند با رابط وب به کار میرود؛ بسیاری از آنها در واقع برنامههای بسیار متمرکزند («CApp»). مراقب DAppهای جعلی باشید.
در این فصل یک DApp نمونه، پلتفرم مزایده، ساخته و مستقر میشود (کد در code/auction_dapp مخزن کتاب).
DApp چیست؟
برنامهای که بیشتر یا کاملاً غیرمتمرکز است. جنبههای قابل غیرمتمرکزسازی: نرمافزار بکاند (منطق)، فرانتاند، ذخیرهسازی داده، ارتباطات پیام و تفکیک نام (name resolution). هرکدام میتوانند کم یا زیاد متمرکز باشند.
مزایا نسبت به معماری متمرکز:
- تابآوری (Resiliency): منطق در قرارداد هوشمند است؛ تا وقتی پلتفرم کار میکند، DApp قطعی ندارد.
- شفافیت: همه میتوانند کد را بررسی کنند و هر تعامل برای همیشه روی بلاکچین میماند.
- مقاومت در برابر سانسور: تا وقتی به گره اتریوم دسترسی دارید، هیچ کنترل متمرکزی (حتی مالک قرارداد) نمیتواند کد مستقر شده را تغییر دهد.
امروز DAppهای واقعاً غیرمتمرکز بسیار کماند؛ بیشترشان برای بخشی از کار به سرویسهای متمرکز تکیه دارند.
بکاند (قرارداد هوشمند)
قرارداد هوشمند جایگزین مؤلفهٔ سمت سرور میشود، با تفاوتهایی: هر محاسبه گران است، پس باید کمینه شود و فقط بخشهایی که به اجرای مورد اعتماد و غیرمتمرکز نیاز دارند روی زنجیره باشند. ملاحظات طراحی: ۱) عدم امکان تغییر کد پس از استقرار (فقط حذف از طریق SELFDESTRUCT ممکن است)؛ ۲) اندازه: قرارداد یکپارچهٔ بزرگ استقرار و استفادهٔ پرهزینه دارد؛ استفاده از محاسبهٔ برونزنجیرهای یا داده بیرونی ممکن است ولی کاربران را به آن منابع وابسته میکند.
فرانتاند
رابط کاربری با فناوریهای معمول وب (HTML, CSS, JavaScript) ساخته میشود. امضای پیام، ارسال تراکنش و مدیریت کلید معمولاً از طریق افزونهٔ مرورگر (MetaMask) انجام میشود. DApp موبایلی هنوز منابع کمی دارد (کمبود کلاینت سبک با مدیریت کلید). اتصال معمول با کتابخانهٔ web3.js.
ذخیرهسازی داده
بهدلیل هزینهٔ بالای گس و سقف پایین گس بلاک، حجم زیاد داده روی زنجیره نمیرود. گزینهها: ذخیرهسازی متمرکز (پایگاه ابری) یا P2P غیرمتمرکز:
- IPFS (سامانهٔ فایل میانسیارهای): ذخیرهسازی مبتنی بر آدرسدهی محتوا: هر فایل هش میشود و با هش آن از هر گره بازیابی میشود. هدف جایگزینی HTTP.
- Swarm: سامانهٔ مشابه از بنیاد اتریوم (بخشی از Go-Ethereum) که فایلها را بین گرهها پخش و تکثیر میکند و میتوان وبسایت را از آن بارگذاری کرد.
پروتکلهای پیامرسانی غیرمتمرکز
پیامرسانی بین برنامهها، نمونهها یا کاربران که معمولاً به سرور متمرکز نیاز دارد. برجستهترین پروتکل P2P برای DAppها Whisper (از Go-Ethereum) است. جنبهٔ آخر غیرمتمرکزسازی، تفکیک نام است (ENS، در ادامه).
مثال: DApp مزایده
کاربر یک توکن deed (نمایندهٔ دارایی یکتا مثل خانه، خودرو یا علامت تجاری) ثبت میکند؛ مالکیت به DApp منتقل میشود تا فهرست شود؛ دیگران پیشنهاد میدهند؛ هر مزایده اتاق گفتگوی اختصاصی دارد؛ پس از پایان، مالکیت به برنده منتقل میشود.
مؤلفهها: قرارداد ERC721 (DeedRepository)، قرارداد مزایده (AuctionRepository)، فرانتاند Vue/Vuetify، web3.js، کلاینت Swarm (برای تصاویر) و کلاینت Whisper (چت).
بکاند
DeedRepository: پیادهسازی ساده توکن ERC721 باregisterDeed(tokenId, uri)که_mintمیکند، فرادادهٔ URI را ثبت و رویدادDeedRegisteredرا منتشر میکند.AuctionRepository: آرایهٔauctions، نگاشت پیشنهادها به ازای هر مزایده، ساختارهایBidوAuction(نام، مهلت بلاک، قیمت شروع، فراداده، شناسه deed، آدرس مخزن deed، مالک، فعال/نهاییشده) و توابعی مثلcreateAuction،bidOnAuction،cancelAuction،finalizeAuctionوapproveAndTransfer. استقرار باtruffle migrate --network ropsten.
حاکمیت DApp (Governance)
در این قراردادها هیچ حساب یا نقش ممتازی وجود ندارد؛ هر مزایده مالک خودش را دارد اما خود DApp کاربر ممتاز ندارد. انتخابی عمدی برای غیرمتمرکزسازی حاکمیت. بعضی DAppها حسابهای ممتاز برای خاتمهدادن به قرارداد یا تغییر پیکربندی یا وتوی عملیات دارند تا باگهای ناشناخته را کنترل کنند. این شمشیر دولبه است: حساب ممتاز اگر هک شود امنیت را زیر پا میگذارد و بدون آن در صورت باگ راه بازیابی نیست. هر دو خطر در عمل دیده شده است: در DAO «سرپرستها» توان ناچیزی داشتند و نتوانستند مانع برداشت مهاجم شوند؛ در Bancor سرقت بزرگی بهدلیل هک حساب مدیریتی رخ داد و معلوم شد به اندازهٔ ادعا غیرمتمرکز نبوده است. DApp واقعی در بلندمدت نباید دسترسی ویژه برای حسابهای ممتاز داشته باشد.
فرانتاند
ساختهشده با Vue2/Vuetify (گوگل) در پوشهٔ frontend. پس از استقرار قراردادها، آدرسهای DeedRepository و AuctionRepository در src/config.js تنظیم میشود؛ برنامه به گره اتریوم با JSON-RPC و WebSocket نیاز دارد و با npm install و npm run dev روی localhost:8080 اجرا میشود.
غیرمتمرکزسازی بیشتر
قرارداد مزایده مستقل از هر نظارتی است و پس از استقرار قابل توقف نیست. هر مزایده اتاق چت بدون سانسور و ناشناس دارد. داراییهای مزایده (توضیح و تصویر) روی Swarm هستند. کد متنباز است. دو گام دیگر: ۱) ذخیرهٔ کل کد برنامه روی Swarm یا IPFS؛ ۲) دسترسی از طریق نام با ENS.
ذخیرهٔ DApp روی Swarm
نصب Swarm (بخشی از Go-Ethereum)، اتصال به Geth برای JSON-RPC، آزمون با دروازه (gateway) محلی http://localhost:8500. با swarm up فایل آپلود و هش بازگردانده میشود. برای کل فرانتاند: ابتدا npm run build (webpack) پوشهٔ dist را میسازد و سپس swarm --recursive --defaultpath dist/index.html up dist/ کل برنامه را آپلود و آدرس bzz://<hash> میدهد. غیرمتمرکز شد اما استفاده سختتر شد؛ ENS راهحل است.
سرویس نام اتریوم (ENS)
مشابه DNS در اینترنت: نامهای خوانا را به آدرس یا شناسههای دیگر نگاشت میکند، اما غیرمتمرکز. مثلاً آدرس کمک مالی بنیاد اتریوم در کیف پولِ سازگار با ENS همان ethereum.eth است. ENS خودش یک DApp بنیادین است که DAppهای دیگر را سرویس میدهد.
تاریخچه: ثبت نام اولین کاربرد غیرارزی بلاکچین بود (Namecoin). وایتپیپر اتریوم نیز نمونهٔ دوخطی داشت. کار جدی با ورود نیک جانسون به بنیاد اتریوم در ۲۰۱۶ آغاز شد و ENS در ۴ مه ۲۰۱۷ (روز جنگ ستارگان) راهاندازی شد.
مشخصات: EIP-137 (توابع پایه)، EIP-162 (سامانهٔ مزایدهٔ ریشهٔ .eth) و EIP-181 (ثبت معکوس آدرسها). فلسفهٔ «ساندویچ»: لایهٔ پایین بسیار ساده، لایههای میانی پیچیدهتر اما قابل جایگزینی، و لایهٔ بالای ساده که وجوه را در حسابهای جداگانه نگه میدارد.
لایهٔ پایین: مالکان نام و Resolverها
ENS روی «گرهها» (nodes) کار میکند نه نامهای خوانا؛ نام با الگوریتم Namehash به گره تبدیل میشود. لایهٔ پایه قراردادی کوچک (کمتر از ۵۰ خط) است که فقط مالک گره میتواند اطلاعات آن (resolver، TTL، انتقال مالکیت) را تنظیم و زیرگره (معادل زیردامنه) بسازد.
Namehash: الگوریتم بازگشتی: namehash([]) = 0x00…0 (۳۲ بایت صفر، حالت پایه) و namehash([label, ...]) = keccak256(namehash(...) + keccak256(label)). مثلاً mastering-ethereum.eth ← sha3(sha3(('�'*32) + sha3('eth')) + sha3('mastering-ethereum')). میتوان گره را از پیش محاسبه کرد.
انتخاب نام معتبر: برچسبهای جدا شده با نقطه؛ پیش از هش، نرمالسازی UTS #46 (تبدیل به حروف کوچک). برای سازگاری با DNS: هر برچسب حداکثر ۶۴ نویسه، نام کامل حداکثر ۲۵۵، برچسب نباید با خط تیره آغاز/پایان یابد یا با رقم شروع شود.
مالکیت گره ریشه: مالکان ریشه میتوانند دامنههای سطح بالا (TLD) بسازند. در زمان نگارش، ریشه توسط یک multisig ۴ از ۷ در کشورهای مختلف کنترل میشد (برگرفته از ۷ نگهدارنده کلید DNS). هدف: مهاجرت .eth به قرارداد دائمی، افزودن TLD جدید با توافق جامعه، مهاجرت مالکیت ریشه به قرارداد غیرمتمرکزتر و ابزار آخرین راه برای اشکالات.
Resolverها: قرارداد پایه نمیتواند فراداده اضافه کند؛ resolverها قراردادهای ساخت کاربرند که میگویند آدرس Swarm برنامه کدام است، آدرس دریافت پرداخت کجاست، هش برنامه چیست و غیره.
لایهٔ میانی: گرههای .eth
تنها TLD که منحصراً در قرارداد قابل ثبت بود .eth بود (تلاش برای اتصال دامنههای DNS سنتی در جریان؛ فقط .xyz روی Ropsten). دامنهها از طریق مزایده توزیع میشوند، بدون فهرست رزرو یا اولویت. مزایده قراردادی پیچیده (بیش از ۵۰۰ خط) و قابل جایگزینی بدون ریسک برای وجوه است.
مزایدهٔ ویکری (Vickrey) اصلاحشده: در ویکری سنتی همه پیشنهاد مهرشده میدهند و همزمان باز میشود؛ بالاترین پیشنهاد برنده است اما دومین قیمت را میپردازد؛ پس بهترین رفتار پیشنهاد ارزش واقعی است. در بلاکچین تغییراتی لازم است: قفل کردن مبلغ برابر یا بیشتر از پیشنهاد؛ حداقل دو تراکنش (commit–reveal)؛ و افشای پیشنهاد توسط خود شرکتکننده (در غیر این صورت وجه قفلشده را از دست میدهد).
چهار مرحله:
- شروع مزایده: نیت ثبت نام را اعلام میکند و مهلتها را میسازد؛ نامها هش میشوند و میتوان مزایدهٔ ساختگی هم باز کرد.
- پیشنهاد مهرشده: قبل از مهلت، مبلغ اتر را به هش پیام مخفی (نام، مبلغ واقعی و نمک) گره میزنید؛ میتوان بیش از پیشنهاد واقعی قفل کرد.
- افشا (Reveal): در دورهٔ افشا، تراکنش افشا میفرستید؛ بالاترین و دومین پیشنهاد محاسبه و وجه بازندگان بازگردانده میشود.
- پاکسازی: برنده مزایده را نهایی و اختلاف را پس میگیرد؛ در صورت فراموشی، افشای دیرهنگام بخشی از وجه را بازمیگرداند.
لایهٔ بالا: Deedها
قراردادی فوقالعاده ساده که فقط وجه را نگه میدارد. وقتی نامی را میبرید، وجه جایی نمیرود بلکه برای دورهای (حداقل یک سال) قفل میشود و مثل بازخرید تضمینشده است: هزینهٔ نگهداشتن نام، هزینهٔ فرصت است. چون نگهداشتن میلیونها دلار در یک قرارداد پرخطر است، برای هر نام یک قرارداد deed کوچک (حدود ۵۰ خط) ساخته میشود که وجه را فقط به مالک deed برمیگرداند و فقط توسط ثبتکننده قابل فراخوانی است؛ سطح حمله را بسیار کاهش میدهد.
ثبت نام در عمل
با رابط MyCrypto و MetaMask: جستجو (مثلاً mastering.eth اشغال بود چون ثبتها یک ساله است) ← شروع مزایده برای ethereumbook.eth ← ثبت پیشنهاد (نگهداشتن عبارت راز و یادآور تقویم برای افشا) ← افشا پس از ۴۸ ساعت. هشدار: اگر ظرف ۴۸ ساعت افشا نکنید، وجه پیشنهاد را از دست میدهید (نویسندگان خود ۰٫۰۱ اتر را به همین دلیل از دست دادهاند).
مدیریت نام با ENS Manager
ساخت زیردامنه (مثل auction.ethereumbook.eth)، تنظیم resolver و اتصال به منبع.
Resolverها در عمل
حل نام دو مرحله دارد: ۱) ثبتکننده ENS با هش نام فراخوانی میشود و آدرس resolver را میدهد؛ ۲) resolver با متد مناسب نوع منبع فراخوانی میشود. این جداسازی انعطاف زیادی میدهد (مثلاً resolver برای مختصات جغرافیایی). resolver عمومی پیشفرض آدرس (کیف پول/قرارداد) و محتوا (هش Swarm) را پشتیبانی میکند. بنابراین auction.ethereumbook.eth را به هش Swarm فرانتاند پیوند میزنیم و دسترسی با http://swarm-gateways.net/bzz:/auction.ethereumbook.eth/ یا کیفپول/مرورگر سازگار با ENS (مثل Mist) ممکن میشود.
از اپ به DApp
گامبهگام ساختیم: قراردادهای مزایدهٔ ERC721 بدون حساب ممتاز؛ فرانتاند جاوااسکریپت؛ Swarm برای داراییها؛ Whisper برای چت رمزگذاریشده؛ آپلود کل فرانتاند روی Swarm؛ و نام ENS. نتیجه DApp بدون نقطهٔ اقتدار یا شکست مرکزی است و چشمانداز Web3 را بیان میکند.
جمعبندی
DAppها اوج چشمانداز اتریوماند. اگرچه بسیاری از برنامهها امروز خود را DApp مینامند، بیشترشان کاملاً غیرمتمرکز نیستند و راه رسیدن به جزئیات غیرمتمرکزسازی هنوز باز است.
فصل ۱۳ — ماشین مجازی اتریوم (EVM)
EVM چیست؟
EVM قلب پروتکل و عملکرد اتریوم است؛ موتور محاسباتی مشابه ماشینهای مجازی .NET یا مفسرهای بایتکد زبانهایی مثل Java. بخشی از اتریوم است که استقرار و اجرای قراردادهای هوشمند را انجام میدهد. انتقال ساده مقدار بین دو EOA عملاً نیازی به آن ندارد، اما هر چیز دیگری شامل یک بهروزرسانی حالت است که EVM محاسبه میکند. میتوان آن را رایانهای جهانی و غیرمتمرکز شامل میلیونها شیء اجرایی با ذخیرهساز دائمی خودشان تصور کرد.
- EVM یک ماشین حالت نیمهتورینگکامل (quasi-Turing-complete) است: چون تمام اجراها با مقدار گس موجود به تعداد محدودی گام محاسباتی محدود میشوند، مسئلهٔ توقف عملاً «حل» میشود و اجرای بیپایان که کل پلتفرم را متوقف کند، رخ نمیدهد.
- معماری پشتهای با اندازهٔ کلمهٔ ۲۵۶ بیت (بهدلیل تسهیل هش و عملیات منحنی بیضوی).
- اجزای قابل آدرسدهی: ROM کد تغییرناپذیر (بایتکد قرارداد)، حافظه فرّار (مقداردهی اولیه با صفر) و ذخیرهسازی دائمی (بخشی از حالت اتریوم، آن هم با صفر مقدار اولیه)؛ بهعلاوهٔ متغیرهای محیطی.
مقایسه با فناوری موجود
«ماشین مجازی» اغلب به مجازیسازی کامپیوتر واقعی (VirtualBox، QEMU) یا یک سیستمعامل کامل اشاره دارد. EVM در حوزهٔ بسیار محدودتری کار میکند: فقط موتور محاسبه و ذخیرهسازی، شبیه مشخصات JVM. زبانهای سطح بالا (LLL، Serpent، Mutan، Solidity) به مجموعه دستورات بایتکد آن کامپایل میشوند. EVM زمانبندی (scheduling) ندارد، چون ترتیب اجرا بیرونی است (کلاینتها تراکنشهای تأییدشدهٔ بلاک را مرور میکنند)؛ مثل جاوااسکریپت تکنخی است. رابط سیستم یا پشتیبانی سختافزاری هم ندارد، چون کاملاً مجازی است.
مجموعه دستورات EVM
همهٔ عملوندها از پشته گرفته میشوند و نتیجه معمولاً روی پشته مینشیند. دستهها:
- حساب:
ADD, MUL, SUB, DIV, SDIV, MOD, SMOD, ADDMOD, MULMOD, EXP, SIGNEXTEND, SHA3(همه محاسبات به پیمانهٔ ۲²⁵⁶؛ ۰⁰ برابر ۱)؛ - پشته، حافظه، ذخیرهسازی:
POP, MLOAD, MSTORE, MSTORE8, SLOAD, SSTORE, MSIZE, PUSHx (1 تا 32), DUPx (1 تا 16), SWAPx (1 تا 16)؛ - جریان کنترل:
STOP, JUMP, JUMPI, PC, JUMPDEST؛ - سیستمی:
LOGx (0 تا 4 موضوع), CREATE, CALL, CALLCODE, RETURN, DELEGATECALL, STATICCALL, REVERT, INVALID, SELFDESTRUCT؛ - منطقی/بیتی:
LT, GT, SLT, SGT, EQ, ISZERO, AND, OR, XOR, NOT, BYTE؛ - محیطی:
GAS, ADDRESS, BALANCE, ORIGIN, CALLER, CALLVALUE, CALLDATALOAD/SIZE/COPY, CODESIZE, CODECOPY, GASPRICE, EXTCODESIZE, EXTCODECOPY, RETURNDATASIZE, RETURNDATACOPY؛ - بلاک:
BLOCKHASH(۲۵۶ بلاک اخیر)،COINBASE, TIMESTAMP, NUMBER, DIFFICULTY, GASLIMIT.
(فهرست کامل opcodeها و هزینهٔ گس در پیوست C است.)
حالت اتریوم
وظیفهٔ EVM بهروزرسانی حالت اتریوم با محاسبهٔ گذارهای معتبر ناشی از اجرای کد است؛ پس اتریوم یک ماشین حالت مبتنی بر تراکنش است. حالت جهانی (world state) نگاشت آدرسهای ۱۶۰ بیتی به حسابهاست. هر حساب شامل: موجودی اتر (بر حسب wei)، nonce (تعداد تراکنشهای موفق برای EOA یا تعداد قراردادهای ایجادشده برای قرارداد)، ذخیرهسازی و کد برنامه (فقط برای قرارداد). EOA همیشه کد ندارد و ذخیرهسازی خالی دارد.
هنگام اجرای کد قرارداد برای یک تراکنش، یک EVM ایجاد میشود: ROM کد با کد قرارداد فراخواندهشده بارگذاری، شمارندهٔ برنامه صفر، ذخیرهسازی از حساب قرارداد، حافظه صفر و متغیرهای بلاک/محیط مقداردهی میشود. کلید: منبع گس برابر مقداری که فرستنده پرداخته. با اجرا، گس کم میشود؛ اگر به صفر برسد، استثنای «Out of Gas»: اجرا فوراً متوقف و تراکنش رها میشود؛ هیچ تغییر حالتی اعمال نمیشود جز افزایش nonce فرستنده و کسر اتر برای پرداخت منابع مصرفی به ذینفع بلاک. میتوان تصور کرد EVM روی یک نسخهٔ جعبهشنی (sandbox) از حالت جهانی کار میکند که در صورت شکست دور ریخته میشود، و در صورت موفقیت حالت واقعی با آن همتراز میشود.
چون قرارداد میتواند خودش فراخوانی ایجاد کند، اجرا بازگشتی است: هر فراخوانی یک EVM جدید با sandbox مشتق از سطح بالاتر و گس تخصیصی (نه بیشتر از باقیماندهٔ سطح بالا) میسازد؛ شکست در سطح پایین، sandbox آن را دور میاندازد و کنترل به سطح بالا برمیگردد.
کامپایل Solidity به بایتکد EVM
با solc میتوان جریان opcode (--opcodes)، اسمبلی کامل با حاشیهنویسی (--asm)، بایتکد (--bin) و بایتکد زماناجرا (--bin-runtime) ساخت. مثال قرارداد example که فقط contractOwner = msg.sender را در سازنده انجام میدهد. چند دستور اول خروجی: PUSH1 0x60 PUSH1 0x40 MSTORE CALLVALUE …:
PUSHxبایت(های) بعد از opcode را بهعنوان مقدار ثابت روی پشته میگذارد (تا ۳۲ بایت)؛MSTOREدو آرگومان از پشته pop میکند (آدرس حافظه و مقدار) و مقدار را در حافظه ذخیره میکند (در مثال ۰x60 در آدرس ۰x40)؛CALLVALUEمقدار اتر ارسالی در فراخوانی را روی پشته میگذارد.
کد استقرار (Deployment Code)
تفاوت ظریف و مهم: کدی که برای ایجاد قرارداد میفرستید با کد خود قرارداد فرق دارد. در تراکنش ایجاد (to=0x0)، فیلد data شامل کد مقداردهی اولیه است. EVM با این کد ساخته میشود و خروجی اجرای آن بهعنوان کد حساب قرارداد جدید ثبت میشود؛ تا قرارداد بتواند در زمان استقرار برنامهوار مقداردهی شود (ذخیرهسازی، ارسال اتر، ساخت قرارداد دیگر). solc --bin بایتکد استقرار و --bin-runtime فقط بایتکد زمان اجرا را میدهد؛ دومی زیرمجموعهٔ اولی است (مثال Faucet.sol).
دیساسمبلکردن بایتکد
ابزارها: Porosity (دیکامپایلر متنباز)، Ethersplay (افزونه Binary Ninja)، IDA-Evm. پس از آمادهسازی کد، قرارداد dispatcher دارد که فیلد data تراکنش را میخواند و به تابع مناسب میفرستد. تحلیل مرحلهبهمرحله در Faucet:
PUSH1 0x4; CALLDATASIZE; LT; PUSH1 0x3f; JUMPI: اگر اندازهٔ calldata کمتر از ۴ بایت باشد (چون شناسهٔ تابع ۴ بایت است) به تابع fallback میپرد (در Faucet فقطSTOP؛ اگر fallback نبود قرارداد استثنا میداد).- اگر calldata بزرگتر بود:
PUSH1 0; CALLDATALOAD۳۲ بایت calldata را از ایندکس ۰ میخواند؛PUSH29 0x1000…; SWAP1; DIVبا تقسیم بر ۱۶²⁹ فقط ۴ بایت بالا (شناسهٔ تابع) را نگه میدارد (مثل ۱۲۳۴۰۰۰ ÷ ۱۰۰۰ = ۱۲۳۴ در مبنای ده)؛PUSH4 0xffffffff; ANDزائد است. DUP1; PUSH4 0x2e1a7d4d; EQ: مقایسهٔ شناسه با شناسهٔ از پیشمحاسبهشدهٔwithdraw(uint256)(چهار بایت اولkeccak256("withdraw(uint256)"))؛ اگر برابر بودPUSH1 0x41; JUMPIبه آدرس تابع withdraw میپرد.
تورینگکامل بودن و گس
سیستم تورینگکامل میتواند هر برنامهای را اجرا کند، اما برخی برنامهها هرگز تمام نمیشوند و نمیتوان با نگاه به برنامه فهمید (مسئلهٔ توقف). چون اتریوم تکنخی و بدون زمانبند است، حلقهٔ بیپایان آن را غیرقابل استفاده میکند. راهحل گس: اگر پس از حداکثر محاسبهٔ پیشتعیینشده اجرا پایان نیافت، EVM متوقفش میکند. پس EVM «نیمهتورینگکامل» است: هر برنامهای را اجرا میکند مشروط به آنکه در حد محاسبهٔ معینی تمام شود. این سقف ثابت نیست؛ میتوان با پرداخت تا «سقف گس بلاک» افزایش داد و جامعه میتواند حداکثر را بالا ببرد.
گس
واحد اندازهگیری منابع محاسباتی و ذخیرهسازی. بر خلاف بیتکوین که کارمزد فقط بر اساس اندازه (کیلوبایت) است، اتریوم هر گام محاسبه را حساب میکند. هر عملیات هزینهٔ ثابتی دارد (مثال Yellow Paper: جمع ۳ گس؛ هش Keccak-256 برابر ۳۰ گس + ۶ گس به ازای هر ۲۵۶ بیت داده؛ ارسال تراکنش ۲۱٬۰۰۰ گس). گس دو نقش دارد: حائل بین قیمت نوسانی اتر و پاداش ماینر؛ و دفاع در برابر DoS (فرستنده باید سقف محاسبهای را که میخواهد بپردازد تعیین کند؛ تراکنش هرزنامه هزینهبر است).
حسابداری گس در اجرا
EVM ابتدا گس برابر gas limit تراکنش دارد؛ هر opcode هزینهای دارد و پیش از هر عملیات بررسی میشود که گس کافی باشد، وگرنه اجرا متوقف و تراکنش برگشت میخورد. در پایان موفق: کارمزد ماینر = gas cost × gas price و باقیمانده بازگردانده میشود: remaining gas = gas limit − gas cost و refunded ether = remaining gas × gas price. اگر گس تمام شود: استثنا، برگشت همهٔ تغییرات حالت، اما فرستنده کارمزد را میپردازد چون ماینر کار محاسباتی را انجام داده است.
ملاحظات
هزینهٔ گس عملیات به دقت انتخاب شده تا از زنجیره در برابر حمله محافظت کند (مثلاً SHA3 ده برابر ADD است و EXP هزینهٔ اضافی بسته به اندازهٔ عملوند دارد؛ حافظه و ذخیرهسازی هم هزینه دارند). اهمیت تطبیق هزینهٔ گس با هزینهٔ واقعی منابع در حملهٔ ۲۰۱۶ آشکار شد که تراکنشهای محاسباتی سنگین شبکه را تقریباً متوقف کرد و با هاردفورک Tangerine Whistle اصلاح شد.
هزینه در برابر قیمت گس
- Gas cost: تعداد واحدهای گس لازم برای یک عملیات.
- Gas price: مقدار اتر که فرستنده برای هر واحد حاضر به پرداخت است؛ بازار رابطه بین قیمت اتر و هزینهٔ محاسبه را تعیین میکند.
transaction fee = total gas used × gas price. ماینرها تراکنشهای با قیمت بالاتر را ترجیح میدهند. گس نه «مالکیتپذیر» است و نه «خرجشدنی»؛ فقط درون EVM بهعنوان شمارنده کار انجامشده وجود دارد.
هزینههای منفی
برای تشویق پاکسازی: حذف قرارداد (SELFDESTRUCT) بازپرداخت ۲۴٬۰۰۰ گس و تغییر آدرس ذخیرهسازی از غیرصفر به صفر (SSTORE[x]=0) بازپرداخت ۱۵٬۰۰۰ گس. برای جلوگیری از سوءاستفاده، حداکثر بازپرداخت هر تراکنش نصف کل گس مصرفی (گرد به پایین) است.
سقف گس بلاک
حداکثر مجموع گس تمام تراکنشهای یک بلاک؛ تعداد تراکنش در بلاک را محدود میکند. مثال: پنج تراکنش با سقفهای ۳۰k، ۳۰k، ۴۰k، ۵۰k، ۵۰k و سقف بلاک ۱۸۰k: چهار تای آنها جا میشوند. ماینری که تراکنش را بیش از سقف بگنجاند بلاکش رد میشود. در زمان نگارش سقف شبکهٔ اصلی ۸ میلیون گس بود (حدود ۳۸۰ تراکنش ساده ۲۱٬۰۰۰ گسی).
چه کسی سقف را تعیین میکند؟ ماینرها بهصورت جمعی: مکانیزم رأیگیری داخلی پروتکل؛ ماینر هر بلاک میتواند سقف را حداکثر ۱/۱۰۲۴ (۰٫۰۹۷۶٪) تغییر دهد. راهبرد پیشفرض: حداقل ۴٫۷ میلیون گس و هدف ۱۵۰٪ میانگین متحرک نمایی (۱۰۲۴ بلاک) مصرف اخیر.
جمعبندی
در این فصل اجرای قراردادها در EVM را رهگیری کردیم، مکانیسم حسابداری گس را دیدیم که مسئلهٔ توقف را حل و در برابر DoS محافظت میکند. فصل بعد سازوکار اجماع غیرمتمرکز را بررسی میکند.
فصل ۱۴ — اجماع (Consensus)
اجماع چیست؟
در سراسر کتاب از «قواعد اجماع» سخن گفتهایم: قواعدی که همه باید بر سر آن توافق کنند تا سامانه بهصورت غیرمتمرکز اما قطعی کار کند. در علوم کامپیوتر، اجماع مفهومی پیش از بلاکچین است و به مسئلهٔ کلی همگامسازی حالت در سیستمهای توزیعشده مربوط میشود: شرکتکنندگان مختلف در نهایت بر سر یک حالت سراسری واحد توافق میکنند («رسیدن به اجماع»).
در شبکههای غیرمتمرکز هیچ مرجع مرکزی وجود ندارد که تعیین کند چه چیزی درست است. این غیبت مرجع از جذابیتهای اصلی بلاکچین است (مقاومت در برابر سانسور و بینیازی از مجوز اقتدار) اما بها دارد: بدون داور مورد اعتماد، هر اختلاف، فریب یا تفاوتی باید با ابزار دیگری حل شود. الگوریتمهای اجماع سازوکاریاند که امنیت و عدم تمرکز را با هم آشتی میدهند. در بلاکچین، اجماع ویژگی حیاتی است («پول در میان است!»)؛ هدف: دستیابی به حالت مشترک در عین حفظ عدم تمرکز، یا به بیان دیگر «قواعد سختگیرانه بدون حاکم». قدرت و کنترل بین شبکهای گسترده از شرکتکنندگان پخش میشود که منافع شخصیشان در پیروی از قواعد و رفتار صادقانه است. این اصل هستهٔ همهٔ بلاکچینهای عمومی باز است و جامعه همچنان مدلهای مختلف اجماع را آزمایش میکند.
نکته: اجماع در لایهٔ زیرین کار میکند و بیشتر جزئیاتش از نویسندگان قرارداد هوشمند پنهان است؛ برای استفاده از اتریوم لازم نیست آن را بدانید، همانطور که برای استفاده از اینترنت نیاز به دانستن مسیریابی نیست.
اجماع از طریق اثبات کار (PoW)
سازندهٔ بلاکچین اول (بیتکوین) الگوریتم اثبات کار را اختراع کرد؛ شاید مهمترین نوآوری زیربنای بیتکوین. نام محاورهای آن «استخراج/ماینینگ» است که سوءبرداشت میآفریند: هدف اصلی ماینینگ تولید پول جدید نیست، بلکه ایمنسازی بلاکچین با حفظ عدم تمرکز است. پاداش پول تازهضربشده مشوق مشارکتکنندگان در امنیت است (وسیله)، و امنیت غیرمتمرکز هدف است. در PoW «مجازات» متناظر نیز وجود دارد: هزینهٔ انرژی؛ اگر شرکتکنندگان از قواعد پیروی نکنند و پاداش نگیرند، پولی را که برای برق خرج کردهاند از دست میدهند. پس PoW توازن دقیق ریسک و پاداش است که انگیزهٔ رفتار صادقانه را از منفعت شخصی میسازد.
اتریوم در زمان نگارش یک بلاکچین PoW بود با الگوریتم Ethash، با سیستم مشوق مشابه و هدف یکسان.
اجماع از طریق اثبات سهام (PoS)
از لحاظ تاریخی، اثبات کار نخستین الگوریتم پیشنهادی نبود؛ پیش از آن پژوهشگران گونههایی از اجماع بر پایهٔ سهم مالی (اثبات سهام) پیشنهاد کرده بودند و در برخی جهات PoW جایگزینی برای آن بود. با موفقیت بیتکوین بسیاری از زنجیرهها PoW را تقلید کردند اما انفجار پژوهش دربارهٔ اجماع، PoS را احیا کرد. بنیانگذاران اتریوم از ابتدا امید داشتند به PoS مهاجرت کنند؛ حتی یک عقبماندگی عمدی به نام بمب دشواری (difficulty bomb) در PoW اتریوم تعبیه شد تا استخراج را رفتهرفته سختتر کند و گذار به PoS را اجباری سازد. الگوریتم برنامهریزیشده Casper است که چند بار به تعویق افتاده و باعث شده بمب دشواری خنثی شود.
روش کلی PoS: بلاکچین مجموعهای از اعتبارسنجها (validators) را دنبال میکند. هر کس اتر دارد میتواند با تراکنشی ویژه آن را بهعنوان سپرده قفل کند و اعتبارسنج شود. اعتبارسنجها به نوبت بلاک بعدی را پیشنهاد میدهند و رأی میدهند؛ وزن رأی به اندازهٔ سپرده (سهم) بستگی دارد. اگر اکثریت بلاکی را که اعتبارسنجی روی آن شرط بسته رد کند، او سپردهاش را از دست میدهد؛ در مقابل برای هر بلاک پذیرفتهشده پاداش کوچک متناسب با سهم میگیرد. تفاوت اصلی با PoW: مجازات در PoS درونی است (از دست رفتن اتر سپردهشده)، در حالی که در PoW بیرونی است (هزینهٔ برق).
Ethash: الگوریتم اثبات کار اتریوم
Ethash تکاملیافتهٔ الگوریتم Dagger–Hashimoto (ترکیب Dagger ویتالیک و Hashimoto تادئوس دریا) است. به تولید و تحلیل مجموعهدادهای بزرگ به نام DAG (گراف جهتدار بدون دور) وابسته است. اندازهٔ اولیهٔ DAG حدود ۱ گیگابایت است و بهآهستگی و بهصورت خطی رشد میکند و هر «دوره» (epoch) (۳۰٬۰۰۰ بلاک ≈ ۱۲۵ ساعت) بهروز میشود.
هدف DAG این است که PoW به نگهداشتن ساختار دادهای بزرگ و پرتکرار وابسته شود، یعنی مقاومت در برابر ASIC: ساختن سختافزار اختصاصی که چند مرتبه سریعتر از GPU باشد دشوارتر شود. بنیانگذاران میخواستند از تمرکز استخراج جلوگیری کنند (آنهایی که به کارخانههای تولید تراشه و بودجهٔ بزرگ دسترسی دارند بر زیرساخت چیره نشوند). استفاده از GPU مصرفی باعث میشود افراد بیشتری در سراسر جهان استخراج کنند و قدرت استخراج غیرمتمرکزتر باشد (برخلاف بیتکوین که در دست چند کارخانه صنعتی بزرگ است). نقطهٔ ضعف: کمبود جهانی GPU در ۲۰۱۷، افزایش شدید قیمت و اعتراض گیمرها و محدودیت خرید در فروشگاهها.
تا اخیراً تهدید ASIC برای اتریوم تقریباً وجود نداشت (طراحی و تولید نیازمند سرمایهگذاری زیاد بود و برنامهٔ دیرینهٔ مهاجرت به PoS تأمینکنندگان را دور نگه میداشت). اما با وجود زنجیرههای دیگر مبتنی بر Ethash (PIRL، Ubiq) و تعهد اتریوم کلاسیک به ماندن در PoW، احتمال ورود ASIC پیش از گذار اتریوم به PoS وجود دارد.
Casper: الگوریتم اثبات سهام اتریوم
نام پیشنهادی برای اجماع PoS اتریوم؛ هنوز در پژوهش و توسعه فعال و پیادهسازینشده بود. در دو شکل رقیب توسعه مییابد:
- Casper FFG («ابزار نهاییسازی دوستانه»)؛
- Casper CBC («GHOST دوستانه / درستبنا»).
FFG ابتدا بهعنوان الگوریتم ترکیبی PoW/PoS و گذار به PoS خالص پیشنهاد شد، اما در ژوئن ۲۰۱۸ ویتالیک تصمیم گرفت مدل ترکیبی را کنار بگذارد و PoS خالص را دنبال کند. اکنون هر دو موازی توسعه مییابند. به گفتهٔ ویتالیک: CBC ویژگیهای نظری بهتری دارد اما FFG سادهتر پیاده میشود.
اصول اجماع
برای فهم مفروضات الگوریتمهای اجماع باید پرسید:
- چه کسی میتواند گذشته را تغییر دهد و چگونه؟ (تغییرناپذیری / immutability)
- چه کسی میتواند آینده را تغییر دهد و چگونه؟ (نهاییبودن / finality)
- هزینهٔ این تغییرات چیست؟
- قدرت این تغییرات تا چه حد غیرمتمرکز است؟
- چه کسی از تغییر باخبر میشود و چگونه؟
مناقشه و رقابت
چرا الگوریتمهای اجماع اینقدر متنوعاند و کدام بهتر است؟ پاسخ در مرکز هیجانانگیزترین حوزهٔ پژوهش سیستمهای توزیعشدهٔ دهه است. «بهتر» وابسته به مفروضات، اهداف و مبادلات ناگزیر است. احتمالاً هیچ الگوریتمی در همهٔ ابعاد بهینه نیست. وقتی کسی میگوید یک الگوریتم بهتر است، بپرسید: در چه چیزی بهتر؟ (تغییرناپذیری، نهاییبودن، عدم تمرکز، هزینه). طراحی الگوریتم اجماع در مرکز صنعتی چند میلیارد دلاری است و بحثهای داغ ایجاد میکند. کل صنعت بلاکچین یک آزمایش بزرگ است که این پرسشها در شرایط خصمانه و با ارزش پولی عظیم آزموده میشوند و تاریخ پاسخ خواهد داد.
جمعبندی
الگوریتم اجماع اتریوم در زمان پایان کتاب هنوز در حال تغییر بود و نویسندگان گفتهاند در ویرایشهای بعدی جزئیات بیشتری دربارهٔ Casper و فناوریهای مرتبط افزوده خواهد شد. این فصل پایان سفر Mastering Ethereum است؛ پیوستها مواد مرجع بیشتری ارائه میدهند.
پیوست A — تاریخچهٔ فورکهای اتریوم
بیشتر هاردفورکها بخشی از نقشهٔ راه ارتقا هستند و بهروزرسانیهایی را شامل میشوند که جامعه (بهصورت اجماع اجتماعی) با آن موافق است. اما برخی هاردفورکها اجماع ندارند و به چند بلاکچین مجزا میانجامند؛ جدایی اتریوم و اتریوم کلاسیک نمونهای از این است.
اتریوم کلاسیک (ETC)
اتریوم کلاسیک پس از آن پدید آمد که اعضای جامعهٔ اتریوم هاردفورکی زمانحساس با نام رمز «DAO» پیاده کردند. در ۲۰ ژوئیهٔ ۲۰۱۶ در ارتفاع بلاک ۱٫۹۲ میلیون، اتریوم با یک هاردفورک تغییر حالت نامنظم اعمال کرد تا حدود ۳٫۶ میلیون اتر را که از قرارداد هوشمند «The DAO» برداشته شده بود بازگرداند. تقریباً همه موافق بودند که این اتر سرقت شده است و باقی ماندنش در دست سارق به توسعهٔ اکوسیستم و پلتفرم آسیب جدی میزند.
بازگرداندن اتر به صاحبان از نظر فنی آسان بود اما از نظر سیاسی مناقشهبرانگیز: عدهای معتقد بودند تغییرناپذیری باید اصل بنیادین بدون استثنای بلاکچین اتریوم باشد و زنجیرهٔ اصلی را با نام اتریوم کلاسیک ادامه دادند. جدایی ابتدا ایدئولوژیک بود اما دو زنجیره از آن زمان به موجودیتهایی مستقل تبدیل شدهاند.
سازمان خودگردان غیرمتمرکز (The DAO)
توسط شرکت Slock.it برای تأمین مالی و حاکمیت مبتنی بر جامعه ساخته شد: پیشنهادها ارسال میشد، «سرپرستها» (curators) آنها را مدیریت میکردند، وجوه از سرمایهگذاران جامعهٔ اتریوم گرد میآمد و در صورت موفقیت پروژهها، سرمایهگذاران سهمی از سود میگرفتند. یکی از نخستین آزمایشهای توکن اتریوم بود: شرکتکنندگان اتر خود را با توکن DAO مبادله میکردند، با آن در تأمین مالی پروژهها رأی میدادند و بعداً میتوانستند به اتر برگردانند. فروش توکن از ۵ تا ۳۰ آوریل ۲۰۱۶ انجام شد و حدود ۱۴٪ کل اتر موجود (حدود ۱۵۰ میلیون دلار آن زمان) را جذب کرد.
باگ Reentrancy
در ۹ ژوئن ۲۰۱۶، پیتر وسنس و کریسایث گزارش دادند بیشتر قراردادهای مدیریت وجوه ممکن است در برابر حملهای که قرارداد را خالی میکند آسیبپذیر باشند. چند روز بعد (۱۲ ژوئن) استیون توال (همبنیانگذار Slock.it) اعلام کرد کد DAO آسیبپذیر نیست؛ اما پنج روز بعد مهاجمی ناشناس با روشی مشابه شروع به تخلیهٔ DAO کرد و در نهایت حدود ۳٫۶ میلیون اتر برداشت.
همزمان گروهی از داوطلبان به نام Robin Hood Group (RHG) با همان روش باقیمانده را برداشتند تا از سرقت نجات یابد. در ۲۱ ژوئن اعلام کردند حدود ۷۰٪ وجوه (حدود ۷٫۲ میلیون اتر) را ایمن کردهاند و قصد بازگرداندن آن را به جامعه دارند (که روی شبکهٔ ETC انجام شد و پس از فورک روی اتریوم ضرورتی نداشت).
جزئیات فنی
خلاصه: در تابع کلیدی DAO دو خط کد به ترتیب اشتباه بودند و مهاجم میتوانست درخواستهای برداشت را بارها اجرا کند پیش از آنکه بررسی استحقاق برداشت تمام شود (نمونهای از reentrancy، فصل ۹).
جریان حمله
مثل مشتری بانک که بتواند هر تعداد برگهٔ برداشت به صندوقدار بدهد و صندوقدار برداشت را فقط پس از پردازش همهٔ برگهها ثبت کند. مراحل: ۱) مهاجم از DAO درخواست برداشت توکن میکند؛ ۲) پیش از بهروزرسانی سوابق دوباره درخواست میدهد؛ ۳) این را تا حد ممکن تکرار میکند؛ ۴) قرارداد سرانجام فقط یک برداشت ثبت میکند و برداشتهای میانی را گم میکند.
هاردفورک DAO
چند محافظت در DAO وجود داشت: بهویژه تأخیر ۲۸ روزه برای همهٔ درخواستهای برداشت، که از حدود ۱۷ ژوئن تا ۲۰ ژوئیه مهاجم را از تبدیل توکنها به اتر بازمیداشت و به جامعه فرصت بحث میداد. راهحلهای بررسیشده: سافتفورک (۲۴ ژوئن) برای توقف برداشتها تا حصول اجماع، و هاردفورک (۱۵ ژوئیه) برای معکوسکردن اثر حمله با تغییر حالت استثنایی. در ۲۸ ژوئن در سافتفورک آسیبپذیری DoS کشف شد و هاردفورک تنها راه عملی ماند: انتقال تمام اتر سرمایهگذاریشده در DAO به قرارداد بازپرداخت جدید تا صاحبان اصلی آن را پس بگیرند، همراه با گروهی امین برای باقیمانده در childDAOها (حدود ۶ تا ۷ میلیون دلار آن زمان).
تیمهای توسعه کلاینتهایی ساختند که کاربر بتواند فورک را فعال کند، و بحث «انتخابی (opt-in، بهطور پیشفرض فورک نشود)» در مقابل «انصرافی (opt-out، بهطور پیشفرض فورک شود)» مطرح بود. رأیگیری در carbonvote.com در ۱۵ ژوئیه آغاز و روز بعد در بلاک ۱٬۸۹۴٬۰۰۰ بسته شد: ۵٫۵٪ کل اتر شرکت کرد و حدود ۸۰٪ آرا (حدود ۴٫۵٪ کل عرضه) به opt-out بود؛ یکچهارم آن از یک آدرس. نتیجه opt-out شد. در ۲۰ ژوئیه، بلاک ۱٬۹۲۰٬۰۰۰، هاردفورک انجام و دو شبکه پدید آمد: یکی با تغییر حالت (اتریوم امروزی) و دیگری بدون آن.
انتظار میرفت زنجیرهٔ اقلیت محو شود اما حدود ۱۰٪ جامعه (از نظر ارزش و قدرت استخراج) زنجیرهٔ فورکنشده را حمایت کردند که اتریوم کلاسیک شد. طی چند روز صرافیها هر دو (ETH و ETC) را فهرست کردند؛ طبیعت هاردفورک یعنی همهٔ دارندگان اتر روی هر دو زنجیره وجوه داشتند، و Poloniex در ۲۴ ژوئیه ETC را فهرست کرد.
خط زمانی
- ۵ آوریل ۲۰۱۶: Slock.it پس از حسابرسی امنیتی The DAO را میسازد.
- ۳۰ آوریل: آغاز فروش جمعی؛ ۲۷ مه: پایان.
- ۹ ژوئن: کشف باگ فراخوانی بازگشتی عمومی.
- ۱۲ ژوئن: توال اعلام میکند وجوه DAO در خطر نیست.
- ۱۷ ژوئن: حمله به DAO و تخلیهٔ حدود ۳۰٪ اتر.
- ۲۱ ژوئن: RHG اعلام میکند ۷۰٪ باقی را ایمن کرده است.
- ۲۴ ژوئن: اعلام رأی سافتفورک؛ ۲۸ ژوئن: کشف آسیبپذیری و رها شدن آن.
- ۲۸ ژوئن تا ۱۵ ژوئیه: بحث جامعه (عمدتاً در r/ethereum).
- ۱۵ ژوئیه: پیشنهاد هاردفورک و رأی CarbonVote.
- ۱۶ ژوئیه: نتیجهٔ رأی.
- ۲۰ ژوئیه: هاردفورک در بلاک ۱٬۹۲۰٬۰۰۰؛ مخالفان کلاینت قدیمی را اجرا میکنند و مشکل بازپخش تراکنشها بین دو زنجیره پیش میآید.
- ۲۴ ژوئیه: Poloniex زنجیرهٔ اصلی را با نماد ETC فهرست میکند.
- ۱۰ و ۳۰ اوت: انتقال و بازگشت وجوه RHG در Poloniex و ایجاد قرارداد بازپرداخت در ETC.
- ۱۱ دسامبر ۲۰۱۶: تیم IOHK برای ETC به رهبری چارلز هاسکینسون.
- ۱۳ ژانویه ۲۰۱۷: ETC برای رفع مشکل بازپخش بهروز میشود؛ زنجیرهها عملاً جدا میشوند.
- ۲۰ فوریه ۲۰۱۷: تیم ETCDEV به رهبری ایگور آرتامونوف.
اتریوم و اتریوم کلاسیک
اگرچه جدایی اولیه حول DAO بود، دو شبکه اکنون پروژههای جدا هستند، هرچند بیشتر توسعه توسط جامعهٔ اتریوم انجام و به کد اتریوم کلاسیک منتقل میشود.
- EVM: تا حد زیادی سازگار؛ تفاوتهای جزئی در opcodeها (EIPهای ۱۴۰، ۱۴۵ و ۲۱۴).
- توسعهٔ هستهٔ شبکه: در اتریوم توسط بنیاد اتریوم و داوطلبان؛ در ETC توسط ETCDEV، IOHK و داوطلبان.
سایر فورکهای شاخص
- Ellaism: شبکهٔ مبتنی بر اتریوم که فقط از PoW استفاده میکند؛ بدون pre-mine و بدون کارمزد اجباری توسعهدهنده. اصول: سیاست پولی ۲۸۰ میلیون سکه، بدون سانسور، متنباز، بدون مجوز، شبهناشناس، جایگزینپذیر، تراکنشهای برگشتناپذیر و بدون هاردفورک مناقشهبرانگیز (فورک فقط با اجماع کل جامعه).
- فورکها دو نوعاند: هاردفورک مستقیم از شبکهٔ موجود، و فورک نرمافزاری که از نرمافزار اتریوم استفاده میکند اما شبکهای کاملاً مجزا بدون تاریخچهٔ مشترک است. بعضی پروژهها (مثل EtherBTC و Ethereum Modification) مدعی فورکاند اما در واقع توکن ERC20 روی اتریوماند («airdrop»).
- Expanse: نخستین فورک مطرح (اعلام ۷ سپتامبر ۲۰۱۵، فورک ۱۴ سپتامبر در بلاک ۸۰۰٬۰۰۰)، با چشمانداز زنجیرهای برای هویت، حاکمیت، خیریه، تجارت و سهام.
- EthereumFog (ETF): ۱۴ دسامبر ۲۰۱۷، بلاک ۴٬۷۳۰٬۶۶۰؛ محاسبات مه و ذخیرهسازی غیرمتمرکز.
- EtherZero (ETZ): ۱۹ ژانویه ۲۰۱۸، بلاک ۴٬۹۳۶٬۲۷۰؛ معماری masternode و حذف کارمزد قراردادهای هوشمند؛ نقدهایی دربارهٔ شفافیت توسعه و اتهام احتمالی فیشینگ.
- EtherInc (ETI): ۱۳ فوریه ۲۰۱۸، بلاک ۵٬۰۷۸٬۵۸۵؛ تمرکز بر سازمانهای غیرمتمرکز، کاهش زمان بلاک، افزایش پاداش ماینر، حذف پاداش uncle و سقف عرضه؛ حفاظت بازپخش.
پیوست B — استانداردهای اتریوم (EIP و ERC)
پیشنهادهای بهبود اتریوم (EIP)
مخزن رسمی: github.com/ethereum/EIPs. بر اساس EIP-1، هر EIP یک سند طراحی است که به جامعهٔ اتریوم اطلاعات میدهد یا قابلیت جدیدی را برای اتریوم، فرایندها یا محیط آن توصیف میکند. باید مشخصات فنی مختصر و توجیه قابلیت را بدهد. نویسنده مسئول ساختن اجماع در جامعه و مستندسازی نظرات مخالف است. (چرخهٔ کاری EIP در کتاب با نمودار نشان داده شده است.)
لایهٔ هر پیشنهاد میتواند Core (هسته/اجماع)، Networking، Interface، ERC (استانداردهای سطح برنامه و قرارداد) یا Meta باشد.
مهمترین EIP/ERCها (مرور موضوعی)
فرایند و هسته:
- EIP-1: هدف و رهنمودهای EIP (Meta)؛
- EIP-2: تغییرات هاردفورک Homestead؛
- EIP-5: مصرف گس برای RETURN و CALL*؛
- EIP-6: تغییر نام opcode
SUICIDE(بهSELFDESTRUCT)؛ - EIP-7: opcode
DELEGATECALL؛ - EIP-8: الزامات سازگاری رو به جلوی devp2p برای Homestead؛
- EIP-86: انتزاع مبدأ و امضای تراکنش؛ زمینهساز «حسابهای قرارداد» که در بلندمدت همهٔ حسابها قرارداد باشند و کاربران مدل امنیتی خود را تعریف کنند (به تعویق افتاد)؛
- EIP-96: تغییرات blockhash و ریشهٔ حالت (به تعویق افتاد)؛
- EIP-100: تنظیم دشواری برای هدفگیری میانگین زمان بلاک و شمول uncleها (Metropolis/Byzantium)؛
- EIP-101: انتزاع ارز و رمزنگاری در Serenity؛
- EIP-105: شاردینگ دودویی و داربست شاردینگ؛
- EIP-140: opcode
REVERT(برگشت حالت بدون مصرف تمام گس و بازگرداندن پیام خطا)؛ - EIP-141: دستور نامعتبر تعیینشدهٔ EVM؛
- EIP-145: دستورات شیفت بیتی در EVM؛
- EIP-150: تغییر هزینهٔ گس عملیات پرمصرف I/O (Tangerine Whistle)؛
- EIP-155: حفاظت ساده از حملهٔ بازپخش (شناسه زنجیره)؛
- EIP-158 / EIP-161: پاکسازی حالت؛ EIP-160: افزایش هزینهٔ EXP؛
- EIP-170: سقف اندازهٔ کد قرارداد؛
- EIP-196 / EIP-197: precompileهای جمع و ضرب اسکالر و بررسی جفت (pairing) روی منحنی
alt_bn128برای راستیآزمایی zkSNARK در سقف گس بلاک؛ - EIP-198: توانرسانی پیمانهای اعداد بزرگ (برای RSA و …)؛
- EIP-211: opcodeهای
RETURNDATASIZEوRETURNDATACOPY؛ - EIP-214: opcode
STATICCALL(فراخوانی بدون اجازهٔ تغییر حالت؛ جلوگیری از باگ reentrancy)؛ - EIP-225: شبکهٔ آزمایشی Rinkeby با اثبات اختیار (Clique)؛
- EIP-649: تأخیر یکساله در بمب دشواری و کاهش پاداش بلاک از ۵ به ۳ اتر؛
- EIP-658: درج کد وضعیت تراکنش در رسید؛
- EIP-706: فشردهسازی snappy در DEVp2p؛
- EIP-615 / EIP-616: زیرروالها و پرشهای ایستا، و عملیات SIMD برای EVM (پیشنویس).
استانداردهای برنامه و قرارداد (ERC):
- EIP-20 (ERC-20): توکن جایگزینپذیر؛
- EIP-55: چکسام آدرس با حروف بزرگ/کوچک؛
- EIP-137: مشخصات ENS؛ EIP-162: مزایدهٔ ثبتکنندهٔ اولیهٔ ENS؛ EIP-181: رزولوشن معکوس آدرسها در ENS؛
- EIP-165: تشخیص رابط استاندارد؛
- EIP-190: استاندارد بستهبندی قرارداد هوشمند؛
- EIP-721 (ERC-721): توکن جایگزینناپذیر؛ CryptoKitties نخستین پیادهسازی پرطرفدار آن بود؛
- EIP-234: افزودن
blockHashبه گزینههای فیلتر JSON-RPC؛ EIP-681: قالب URL برای درخواست تراکنش؛ EIP-758: اشتراک و فیلتر برای تراکنشهای تکمیلشده؛ - EIP-801: استاندارد Canary؛ EIP-827: توکن ERC827 (گسترش ERC20 با اجرای فراخوانی در انتقال/تأیید)؛ EIP-930: Eternal Storage (ذخیرهسازی ماندگار برای مهاجرت آسان).
(برای فهرست کامل با نویسندگان، وضعیت و لایه، به جدول B-1 کتاب مراجعه کنید.)
پیوست C — Opcodeهای EVM و مصرف گس
این پیوست جدول مرجع opcodeهای EVM را با شمارهٔ هگزادسیمال، نام، توضیح و هزینهٔ گس ارائه میدهد. در اینجا فقط الگوی کلی و چند هزینهٔ شاخص خلاصه میشود؛ برای مقادیر دقیق همهٔ ردیفها به جدول C-1 کتاب مراجعه کنید.
نظم کلی فضای کدها
| بازه | دسته |
|---|---|
0x00 – 0x0b |
توقف و حساب (STOP, ADD, MUL, SUB, DIV, SDIV, MOD, SMOD, ADDMOD, MULMOD, EXP, SIGNEXTEND) |
0x10 – 0x1a |
مقایسه و منطق بیتی (LT, GT, SLT, SGT, EQ, ISZERO, AND, OR, XOR, NOT, BYTE) |
0x20 |
SHA3 (Keccak-256) |
0x30 – 0x3e |
اطلاعات محیط اجرا (ADDRESS, BALANCE, ORIGIN, CALLER, CALLVALUE, CALLDATA*, CODE*, GASPRICE, EXTCODE*, RETURNDATA*) |
0x40 – 0x45 |
اطلاعات بلاک (BLOCKHASH, COINBASE, TIMESTAMP, NUMBER, DIFFICULTY, GASLIMIT) |
0x50 – 0x5b |
پشته، حافظه، ذخیرهسازی و جریان کنترل (POP, MLOAD, MSTORE, MSTORE8, SLOAD, SSTORE, JUMP, JUMPI, PC, MSIZE, GAS, JUMPDEST) |
0x60 – 0x7f |
PUSH1 تا PUSH32 |
0x80 – 0x8f |
DUP1 تا DUP16 |
0x90 – 0x9f |
SWAP1 تا SWAP16 |
0xa0 – 0xa4 |
LOG0 تا LOG4 |
0xf0 – 0xff |
سیستمی (CREATE, CALL, CALLCODE, RETURN, DELEGATECALL, CREATE2, STATICCALL, REVERT, INVALID, SELFDESTRUCT) |
برخی کدهای بینراهی بدون استفادهاند، برخی مربوط به پیشنهاد EIP-615 (زیرروالها) تجربیاند و برخی فقط در pyethereum اشاره شدهاند.
هزینههای شاخص گس (مطابق جدول کتاب)
STOP،RETURN: ۰؛JUMPDEST: ۱؛- بیشتر عملیات ساده و پشته (
ADD,SUB, مقایسهها،PUSHx,DUPx,SWAPx,MLOAD,MSTORE): ۳؛ MUL,DIV,SDIV,MOD,SMOD,SIGNEXTEND: ۵؛ADDMOD,MULMOD,JUMP: ۸؛JUMPI,EXP(پایه): ۱۰ (با هزینهٔ اضافی بسته به اندازه توان)؛- اطلاعات محیط ساده مثل
ADDRESS,ORIGIN,CALLER,CALLVALUE: ۲؛ SHA3: ۳۰ (بهعلاوهٔ ۶ به ازای هر کلمه)؛BLOCKHASH: ۲۰؛SLOAD: ۲۰۰؛BALANCE: ۴۰۰؛EXTCODESIZE/EXTCODECOPY: ۷۰۰؛LOG0تاLOG4: ۳۷۵ تا ۱۸۷۵ (به ازای هر موضوع ۳۷۵ بیشتر، بهعلاوهٔ هزینه داده)؛CREATE: ۳۲٬۰۰۰؛STATICCALL: ۴۰؛CALL,CALLCODE,DELEGATECALL,SSTORE: هزینهٔ «پیچیده» و وابسته به شرایط؛SELFDESTRUCT: بازپرداخت ۲۴٬۰۰۰ گس (فصل ۱۳)،SSTOREبه صفر: بازپرداخت ۱۵٬۰۰۰.
توجه: هزینهها در هاردفورکها تغییر کردهاند (مثلاً EIP-150)، پس هنگام استفادهٔ عملی به مشخصات بهروز مراجعه کنید.
پیوست D — ابزارها، فریمورکها و کتابخانههای توسعه
انجام همهچیز با دست درک بهتری از ساختار کلی میدهد اما کاری خستهکننده و تکراری است؛ فریمورکها بخشی از کارها را خودکار میکنند.
فریمورکها
Truffle
مجموعهای از بستههای Node.js. پیشنیاز: Node.js و npm (توصیه میشود با nvm نصب شود؛ nvm install --lts و ثبت نسخه در فایل .nvmrc). نصب: npm -g install truffle. Truffle Boxes پروژههای آمادهٔ اولیهاند (truffle unbox BOX_NAME).
روند کار:
- ساخت پوشهٔ پروژه و
truffle init(ایجادcontracts،migrations،testو فایلهای پیکربندی) وnpm init؛ - نصب وابستگیهایی مثل
dotenv،truffle-wallet-providerوethereumjs-wallet؛ - روی ویندوز بهتر است
truffle.jsحذف و ازtruffle-config.jsاستفاده شود (تداخل با اجرای فایل); - تعریف شبکهها در
truffle-config.js(مثلاًlocalnodeرویlocalhost:8545باnetwork_id: "*")؛ - قرار دادن قرارداد (مثل Faucet.sol) در
contractsوtruffle compile؛ - Migrations: سازوکار استقرار؛ چون قراردادها تغییرناپذیر و استقرارشان پرهزینه است، قرارداد
Migrations.solمشخص میکند کدام قراردادها (و نسخهها) مستقر شدهاند تا هزینهٔ بازاستقرار قراردادهای بدون تغییر پرداخت نشود. اسکریپت1_initial_migration.jsخود Migrations را مستقر میکند و2_deploy_contracts.jsقرارداد شما را (مثلاًdeployer.deploy(Faucet))؛ اجرا باtruffle migrate --network localnode. چهار تراکنش: استقرار Migrations، بهروزرسانی شمارنده، استقرار Faucet و بهروزرسانی شمارنده؛ - کنسول Truffle: محیط جاوااسکریپت تعاملی با تکمیل خودکار (Tab) و شیء
web3؛ مثالها:web3.eth.accounts،getBalance،sendTransactionباweb3.toWeiو فراخوانی تابع قرارداد باFaucet.deployed().then(...). هشدار: همیشه بدانید به کدام شبکه متصلاید تا تراکنش آزمایشی را به شبکهٔ اصلی نفرستید.
Embark
فریمورک ساخت و استقرار DApp با یکپارچگی با Ethereum، IPFS، Whisper و Swarm: استقرار خودکار قراردادها و دسترسی در JS، پایش تغییر و بازاستقرار، مدیریت زنجیرههای مختلف، مدیریت قراردادهای وابسته، ذخیره و بازیابی داده (IPFS)، استقرار کل برنامه روی IPFS/Swarm و پیامرسانی با Whisper. نصب: npm -g install embark.
OpenZeppelin
چارچوب متنباز قراردادهای قابل استفادهٔ مجدد و امن (مجوز MIT) به رهبری تیم Zeppelin با بیش از صد مشارکتکننده؛ محور آن امنیت است. شامل ERC20/ERC721/ERC827، انواع مدل فروش جمعی و الگوهای Ownable, Pausable, LimitBalance.
مثال: توکن قابل ضرب SampleToken (ارثبر از MintableToken، نام SAMPLE TOKEN، نماد SAM، ۱۸ رقم اعشار) و قرارداد SampleCrowdsale که از PostDeliveryCrowdsale (برداشت توکن پس از پایان فروش) و MintedCrowdsale (ضرب توکن هنگام دریافت اتر) ارث میبرد. انواع دیگر: RefundableCrowdsale، IncreasingPriceCrowdsale، CappedCrowdsale، TimedCrowdsale، WhitelistedCrowdsale. هشدار: تست مجزا برای هر واحد کافی نیست؛ ترکیب واحدها (از جمله وراثت چندگانه) رفتار غیرمنتظره میآورد و باید کل ترکیب را کامل آزمود. استقرار با اسکریپت migration در Truffle.
ZeppelinOS
پلتفرم متنباز ابزارها و خدمات روی EVM برای توسعه و مدیریت امن برنامهها. برخلاف OpenZeppelin که کد باید با هر برنامه دوباره مستقر شود، کد ZeppelinOS روی زنجیره است؛ برنامه مستقیماً با پیادهسازی روی زنجیره (مثل توکن ERC20) تعامل میکند. هستهاش قرارداد proxy است: میتواند هر قرارداد را بپوشاند و بدون از دستدادن حالت ارتقا دهد (در اصل منطق در یک کتابخانه است که میتوان آن را جایگزین کرد). AppManager بستهٔ هر نسخه را مدیریت میکند. ویژگیهای برنامهریزیشده: ابزارهای توسعه، زمانبند، جایزههای توسعه و بازار.
ابزارهای کمکی
- EthereumJS helpeth: ابزار خط فرمان کار با کلید و تراکنش: امضا و راستیآزمایی پیام،
createTx،assembleTx،parseTx،keyGenerate،keyConvert(به قالب keystore V3)،keyDetails،bip32Details،addressDetailsوunitConvert. - dapp.tools: مجموعه ابزار به سبک یونیکس:
Dapp(ساخت DApp، آزمون واحد Solidity، دیباگ، استقرار و اجرای شبکه آزمایشی)،Seth(تراکنش، پرسوجو و تبدیل قالب)،Hevm(EVM هسکلی با دیباگر Solidity)،evmdis(دیساسمبلر EVM با تحلیل ایستا) وSputnikVM(ماشین مجازی مستقل Rust).
کتابخانهها
- web3.js (جاوااسکریپت، بنیاد اتریوم)، web3.py (پایتون)، EthereumJS (مجموعه کتابخانهها)، web3j (جاوا/اندروید)، EtherJar (جاوا، سمت سرور)، Nethereum (.NET)، ethers.js (کتابخانهٔ جامع و کوچک با مجوز MIT)، Emerald Platform (کتابخانهها و مؤلفههای UI برای DApp و کتابخانهٔ Rust برای کلید و امضا، مجوز Apache 2.0).
آزمون قراردادهای هوشمند
| فریمورک | زبان آزمون | چارچوب | شبیهساز زنجیره |
|---|---|---|---|
| Truffle | JavaScript/Solidity | Mocha | TestRPC/Ganache |
| Embark | JavaScript | Mocha | TestRPC/Ganache |
| Dapp | Solidity | ds-test | ethrun (Parity) |
| Populus | Python | pytest | شبیهساز پایتونی |
- Truffle: آزمون واحد با JS (Mocha) یا Solidity روی Ganache.
- Embark: آزمون Mocha، استقرار و بازاستقرار خودکار و
embark test. - Dapp: با کتابخانهٔ Solidity به نام
ds-test(توابعassert,assertEq,expectEventsExactو دستورهای لاگ مثلlog_named_uint) وethrunساخت Parity. - Populus: آزمون با پایتون و
pytest؛ قراردادهای ویژهٔ آزمون با الگویTest*.sol.
آزمون روی بلاکچین
اگرچه بیشتر آزمون نباید روی قراردادهای مستقرشده باشد، فرمانهای کنسول Geth/web3 برای بررسی وضعیت مفیدند: eth.getTransactionReceipt(txhash) (آدرس قرارداد)، eth.getCode(address) (راستیآزمایی استقرار)، eth.getPastLogs(options) (تاریخچه رویدادها) و eth.getStorageAt(address, position) (ذخیرهسازی).
Ganache
بلاکچین آزمایشی محلی؛ هم برنامهٔ دسکتاپ و هم ganache-cli (نصب با npm install -g ganache-cli). مثال اجرا با گزینههای --networkId, --port, --gasLimit (همخوان با سقف شبکهٔ اصلی، مثلاً ۸٬۰۰۰٬۰۰۰ تا از خطای out-of-gas بیهوده پرهیز شود), --gasPrice و --mnemonic برای بازیابی کیف پول HD.
پیوست E — آموزش web3.js
معرفی
این آموزش بر پایهٔ [email protected] است. web3.js مجموعهای از ماژولهای کارکردی برای اکوسیستم اتریوم بهعلاوهٔ یک API جاوااسکریپتی سازگار با مشخصات JSON-RPC عمومی است. برای اجرای اسکریپت به گره محلی نیازی نیست، چون از سرویس Infura استفاده میکند.
تعامل ناهمگام با قرارداد (پایه)
- نسخهٔ
npmرا بررسی و در صورت نیاز باnpm initمقداردهی کنید. - وابستگیها را نصب کنید:
command-line-args،web3وnode-rest-client-promise. - اسکریپت را با Node اجرا کنید (
node code/web3js/web3-contract-basic-interaction.js)؛ میتوانید توکن Infura خود را (ثبتنام در infura.io) در فایلی محلی (infura_token) نگه دارید و مسیر آن را با--infuraFileTokenیا بهصورت آرگومان بدهید.
مرور اسکریپت نمایشی
- ساخت ارائهدهندهٔ پایه:
var web3 = new Web3(infura_host); - فراخوانیهای ساده (همگی Promise):
web3.eth.getProtocolVersion()،web3.eth.getGasPrice()وweb3.eth.getBlockNumber().
تعامل با قرارداد
نمونه از قرارداد WETH9 روی شبکهٔ آزمایشی Kovan:
- تعریف آدرس قرارداد؛
web3.eth.getBalance(address)برای موجودی وweb3.eth.getCode(address)برای بایتکد؛ - دریافت ABI از API اتراسکن (
module=contract&action=getabi&address=...) با یک کلاینت REST مبتنی بر Promise (node-rest-client-promise)؛ - ساخت شیء قرارداد:
new web3.eth.Contract(abi, address)؛ - نمایش آدرس (
our_contract._addressیاour_contract.options.address) و ABI (options.jsonInterface)؛ - خواندن
totalSupplyبه دو شیوه: با callback (methods.totalSupply().call(function(err, result){...})) یا با Promise (.call().then(...).catch(...)).
async/await
همین تعاملات را میتوان با ساختار async/await نوشت (فایل web3-contract-basic-interaction-async-await.js در code/web3js). خوانایی بیشتری دارد چون تعاملات ناهمگام را شبیه دنبالهای از فراخوانیهای مسدودکننده نشان میدهد.

