فهرست دسترسی در اتریوم (EIP-2930)

تراکنش های فهرست دسترسی در اتریوم (Access List Transactions) به کاربران کمک می‌کنند هنگام اجرای فراخوانی های بین قراردادها، مصرف گس را کاهش دهند. برای این کار، کاربر از قبل اعلام می‌کند کدام قراردادها و اسلات های ذخیره سازی قرار است در طول تراکنش مورد استفاده قرار بگیرند. با این روش، هر اسلاتی که از قبل در فهرست آمده باشد، می‌تواند تا 100 واحد گس صرفه‌جویی ایجاد کند.

توسعه دهندگان EIP-2930 را معرفی کردند تا تأثیر منفی تغییرات EIP-2929 را کاهش دهند. آن تغییرات هزینه دسترسی به حافظه سرد را افزایش داده بودند.
در EIP-2929، تیم توسعه تصمیم گرفت قیمت‌گذاری اشتباه عملیات های حافظه را اصلاح کند؛ زیرا این قیمت های پایین، خطر حملات انکار سرویس (DoS) را افزایش می‌داد. با اینکه این تصمیم امنیت شبکه را بالا برد، اما باعث شد برخی قراردادهای هوشمند دیگر به‌درستی کار نکنند. برای حل این مشکل، توسعه دهندگان از EIP-2930 استفاده کردند تا فهرست های دسترسی اختیاری را وارد شبکه کنند.

EIP-2930 به کاربران اجازه می‌دهد اسلات های مورد نیاز را پیش از اجرای تراکنش، گرم کنند (pre-warm). در نتیجه این اسلات ها دیگر حافظه سرد محسوب نمی‌شوند و هزینه کمتری برای دسترسی به آن‌ها پرداخت می‌شود. توالی شماره‌های EIP-2929 و EIP-2930 نیز کاملاً هدفمند بوده است؛ زیرا اولی مشکل را ایجاد کرد و دومی راه‌حل آن را ارائه داد. این مفهوم، به‌ویژه برای افرادی که به‌تازگی وارد حوزه برنامه نویسی و توسعه قراردادهای هوشمند شده‌اند، کاربردی و مهم است؛ زیرا می‌تواند در بهینه سازی عملکرد و هزینه تراکنش ها نقش کلیدی داشته باشد.

نحوه عملکرد EIP-2930

تراکنش هایی که بر اساس EIP-2930 اجرا می‌شوند، دقیقاً مانند سایر تراکنش های اتریوم عمل می‌کنند، با این تفاوت که هزینه دسترسی به حافظه سرد (cold storage) پیش از اجرای عملیات SLOAD و با تخفیف پرداخت می‌شود، نه در حین اجرای آن.

برای استفاده از این قابلیت، نیازی نیست تغییری در کد سالیدیتی ایجاد شود؛ این ویژگی به‌طور کامل در سمت کلاینت (node client) مشخص می‌شود و از طریق رابط کاربری یا API تراکنش اعمال می‌گردد.

با پرداخت این هزینه از پیش، دسترسی سرد به اسلات ذخیره سازی پیش‌پرداخت می‌شود. بنابراین، زمانی که تراکنش در حال اجرا است، فقط هزینه دسترسی گرم (warm access fee) از کاربر کسر می‌شود. اگر کلاینت گره اتریوم از قبل کلیدهای ذخیره سازی موردنیاز را بداند، می‌تواند مقادیر حافظه را زودتر بارگذاری کند. این کار امکان اجرای موازی برخی عملیات ها بین محاسبات و دسترسی به ذخیره سازی را فراهم می‌کند.

EIP-2930 دسترسی به حافظه‌ای که در فهرست دسترسی وجود ندارد را محدود نمی‌کند. افزودن یک ترکیب «آدرس و اسلات حافظه» به فهرست دسترسی، هیچ تعهدی برای استفاده از آن ایجاد نمی‌کند. اما اگر آن ترکیب واقعاً در زمان اجرای تراکنش استفاده نشود، کاربر بدون هیچ منفعتی هزینه دسترسی سرد را از قبل پرداخت کرده است.

کاهش هزینه دسترسی

بر اساس EIP-2930، هاردفورک Berlin هزینه دسترسی “سرد” به اپ‌کدهای مربوط به حساب‌ها (مانند BALANCE، تمام فراخوانی‌های CALL و EXT*) را به 2600 گس افزایش داد. همچنین هزینه دسترسی سرد به وضعیت ذخیره سازی (اپ‌کد SLOAD) نیز از 800 به 2100 گس افزایش پیدا کرد. در مقابل، هزینه دسترسی “گرم” برای هر دو نوع عملیات به 100 گس کاهش یافت.

با این حال، EIP-2930 یک مزیت اضافی هم دارد: این استاندارد به هر تراکنش شامل فهرست دسترسی، تخفیف 200 گس اختصاص می‌دهد. این تخفیف باعث می‌شود هزینه نهایی تراکنش کمتر شود.

در نتیجه، به جای پرداخت 2600 گس برای CALL و 2100 گس برای SLOAD در حالت سرد، کاربر فقط 2400 گس برای CALL و 1900 گس برای SLOAD می‌پردازد. در ادامه، اگر همان دسترسی‌ها مجدداً انجام شوند، چون در وضعیت “گرم” قرار دارند، فقط 100 گس مصرف خواهند کرد.

پیاده سازی یک تراکنش با فهرست دسترسی

در این بخش، یک فهرست دسترسی (Access List) را پیاده سازی می‌کنیم، سپس آن را با یک تراکنش معمولی مقایسه می‌کنیم و در نهایت، چند معیار عددی از نظر مصرف گس ارائه می‌دهیم.

برای شروع، بیایید نگاهی بیندازیم به قراردادی که قصد داریم آن را فراخوانی کنیم:

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

قسمت type که مقدار آن 1 است و درست بالای accessList قرار دارد، مشخص می‌کند که این تراکنش از نوع تراکنش فهرست دسترسی (Access List Transaction) است.

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

مقادیر storage slots (یا همان storageKeys که در کد تعریف شده‌اند) باید دقیقاً ۳۲ بایت باشند. به همین دلیل است که در مقداردهی آن‌ها، صفرهای زیادی در ابتدای رشته ها مشاهده می‌کنیم.

در این مثال، ما دو کلید ذخیره سازی ۳۲ بایتی داریم که مقادیرشان متناظر با صفر و یک هستند؛ چون تابع getSum که از طریق قرارداد Caller فراخوانی می‌شود، دقیقاً به همین اسلات ها در قرارداد Calculator دسترسی پیدا می‌کند. به‌طور خاص، متغیر x در اسلات شماره صفر و متغیر y در اسلات شماره یک قرار دارد.

نتایج

خروجی اجرای کد به صورت زیر نمایش داده شده:

با مقایسه نتایج، متوجه می‌شویم که با استفاده از فهرست دسترسی، توانستیم 300 واحد گس صرفه‌جویی کنیم (این نتیجه بدون توجه به تنظیمات بهینه‌ساز (optimizer) همچنان برقرار است).

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

با این حال، باید هزینه دسترسی گرم (warm access) را هم در نظر بگیریم؛ چون هر سه عملیات (فراخوانی خارجی و دو دسترسی به حافظه) همچنان به میزان 100 گس برای هر کدام نیاز دارند. بنابراین، از 600 گس صرفه‌جویی بالقوه، فقط 300 گس صرفه‌جویی خالص به‌دست آمده است.

محاسبه دقیق:

اگر از فهرست دسترسی استفاده نمی‌کردیم، هزینه های مربوط به دسترسی سرد به صورت زیر محاسبه می‌شد:

2600 (برای CALL) + 2100 × 2 (برای SLOAD) = 6800 گس

اما با فهرست دسترسی، این مقادیر از پیش پرداخت شده و به شکل زیر کاهش یافتند:

2400 (برای CALL) + 1900 × 2 (برای SLOAD) = 6200 گس

سپس باید هزینه دسترسی گرم را نیز اضافه کنیم:

100 (برای CALL) + 100 × 2 (برای دو SLOAD) = 300 گس

در مجموع، در این حالت 6500 گس پرداخت کردیم، در حالی که بدون فهرست دسترسی باید 6800 گس پرداخت می‌کردیم. بنابراین، صرفه‌جویی خالص دقیقاً 300 گس بوده است.

به‌دست آوردن اسلات های ذخیره سازی در یک تراکنش فهرست دسترسی

کلاینت Go-Ethereum (معروف به geth) متدی با نام eth_createAccessList در اختیار دارد که از طریق RPC به راحتی می‌توان اسلات های ذخیره سازی موردنیاز برای یک تراکنش را مشخص کرد. (برای مثال می‌توان به مستندات API مربوط به web3.js مراجعه کرد.)

با استفاده از این متد RPC، کلاینت به‌طور خودکار اسلات های ذخیره سازی مورد استفاده در تراکنش را شناسایی می‌کند و سپس فهرست دسترسی (access list) متناظر را برمی‌گرداند.

علاوه بر این، در ابزار Foundry نیز می‌توان از این قابلیت بهره‌برداری کرد. با اجرای دستور cast access-list در Foundry، متد eth_createAccessList در پس‌زمینه اجرا می‌شود و در نهایت فهرست دسترسی تولید می‌شود.

در این مثال، می‌خواهیم با قرارداد UniswapV2 Factory در شبکه آزمایشی Göerli تعامل داشته باشیم. هدف این است که تابع allPairs را فراخوانی کنیم. این تابع بر اساس ایندکس ورودی، آدرس یکی از قراردادهای جفت‌ارز (pair contract) را از یک آرایه بازمی‌گرداند.

برای این کار، دستور زیر را در یک نسخه فورک شده از شبکه Göerli اجرا می‌کنیم:

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

نمونه‌ای از هدر رفت گس هنگام استفاده نادرست از فهرست دسترسی

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

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

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

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

در ادامه، اسکریپتی را مشاهده می‌کنید که برای استقرار و اجرای قرارداد روی نود محلی Hardhat مورد استفاده قرار می‌گیرد:

نتایج به شرح زیر است:

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

در واقع، استفاده از فهرست دسترسی که به‌درستی محاسبه نشده، نه تنها مزیتی ایجاد نمی‌کند، بلکه باعث افزایش غیرضروری هزینه گس نیز می‌شود. این مثال به‌خوبی نشان می‌دهد که دقت در تعیین صحیح اسلات های حافظه برای استفاده از EIP-2930 کاملاً حیاتی است.

از فهرست دسترسی در شرایط غیرقابل پیش بینی استفاده نکنید

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

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

مثال دیگر، اسلات هایی هستند که وابسته به زمان اجرای تراکنش‌اند. در برخی پیاده‌سازی‌های استاندارد ERC-721، آدرس مالک توکن به‌صورت پویا به یک آرایه اضافه می‌شود و سیستم از ایندکس آرایه برای تعیین مالکیت NFT استفاده می‌کند. در این ساختار، موقعیت ذخیره سازی هر توکن وابسته به ترتیب مینت شدن توسط کاربران است و معمولاً امکان پیش‌بینی آن از قبل وجود ندارد.

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

به همین دلیل، توسعه دهندگان باید از به‌کارگیری Access List در تراکنش‌هایی با الگوهای دسترسی غیرقطعی اجتناب کنند.

در چه مواقعی فهرست دسترسی باعث صرفه‌جویی در گس می‌شود؟

هر زمان که قرار است فراخوانی بین قراردادها (cross-contract call) انجام دهید، بهتر است استفاده از تراکنش با فهرست دسترسی (Access List Transaction) را مدنظر قرار دهید.

در حالت عادی، اجرای یک فراخوانی بین دو قرارداد باعث مصرف 2600 گس اضافی برای دسترسی سرد به قرارداد مقصد می‌شود. اما اگر از فهرست دسترسی استفاده کنید، تنها 2400 گس برای دسترسی اولیه پرداخت می‌کنید و از آنجا که قرارداد مقصد به حالت “گرم” (prewarmed) منتقل شده، در ادامه تنها 100 گس دیگر بابت استفاده از آن دریافت می‌شود. در نتیجه، هزینه کل از 2600 به 2500 گس کاهش پیدا می‌کند.

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

در مخزن (repository) مربوطه، چند نمونه از استفاده‌های رایج فهرست دسترسی در فراخوانی‌های بین قراردادها ارائه شده‌اند، از جمله:

  • دسترسی به قیمت در اوراکل Chainlink

  • فراخوانی delegatecall توسط یک قرارداد پراکسی به قرارداد پیاده‌سازی

  • انتقال توکن ERC-20 از طریق فراخوانی بین دو قرارداد

چه زمانی نباید از تراکنش فهرست دسترسی استفاده کرد؟

زمانی که تراکنش فقط به یک قرارداد هوشمند خاص دسترسی دارد، استفاده از فهرست دسترسی هیچ مزیتی ایجاد نمی‌کند. دلیل آن این است که فراخوانی مستقیم یک قرارداد، هزینه اضافی جداگانه‌ای ندارد و هزینه آن در همان ۲۱٬۰۰۰ گس پایه تراکنش گنجانده شده است. بنابراین، افزودن فهرست دسترسی در چنین مواردی تنها پیچیدگی ایجاد می‌کند، بدون اینکه موجب صرفه‌جویی در مصرف گس شود.

جمع‌بندی

تراکنش های فهرست دسترسی در اتریوم (EIP-2930) ابزار مناسبی برای صرفه جویی در مصرف گس هستند، به‌ویژه زمانی که آدرس قرارداد و اسلات ذخیره سازی مورد نظر در یک فراخوانی بین قراردادها قابل پیش‌بینی باشد. در این شرایط، می‌توان برای هر اسلات ذخیره سازی تا ۲۰۰ گس صرفه‌جویی ایجاد کرد.

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

5/5 - (1 امتیاز)

راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.

پکیج صفر تا صد آموزش سئو و بهینه سازی بصورت عملی
  • انتشار: ۳۱ تیر ۱۴۰۴

دسته بندی موضوعات

آخرین محصولات فروشگاه

مشاهده همه

نظرات

بازخوردهای خود را برای ما ارسال کنید