আপনি যা কিছু ক্যাশ করতে পারেন
রোলআপস ব্যবহার করার সময় লেনদেনের একটি বাইটের খরচ একটি স্টোরেজ স্লটের খরচের চেয়ে অনেক বেশি ব্যয়বহুল। অতএব, অনচেইন এ যতটা সম্ভব তথ্য ক্যাশ করা যৌক্তিক।
এই নিবন্ধে আপনি শিখবেন কীভাবে এমনভাবে একটি ক্যাশিং কন্ট্রাক্ট তৈরি এবং ব্যবহার করতে হয় যাতে একাধিকবার ব্যবহার হওয়ার সম্ভাবনা রয়েছে এমন যেকোনো প্যারামিটার ভ্যালু ক্যাশ করা হবে এবং (প্রথমবারের পরে) অনেক কম সংখ্যক বাইট সহ ব্যবহারের জন্য উপলব্ধ থাকবে, এবং কীভাবে অফচেইন কোড লিখতে হয় যা এই ক্যাশ ব্যবহার করে।
আপনি যদি নিবন্ধটি এড়িয়ে যেতে চান এবং শুধুমাত্র সোর্স কোড দেখতে চান, তবে এটি এখানে আছে (opens in a new tab)। ডেভেলপমেন্ট স্ট্যাক হলো Foundry (opens in a new tab)।
সামগ্রিক ডিজাইন
সরলতার খাতিরে আমরা ধরে নেব সমস্ত লেনদেন প্যারামিটার হলো uint256, যা 32 বাইট দীর্ঘ। যখন আমরা একটি লেনদেন পাই, তখন আমরা প্রতিটি প্যারামিটারকে এভাবে পার্স করব:
-
যদি প্রথম বাইট
0xFFহয়, তবে পরবর্তী 32 বাইটকে একটি প্যারামিটার ভ্যালু হিসেবে নিন এবং এটি ক্যাশে লিখুন। -
যদি প্রথম বাইট
0xFEহয়, তবে পরবর্তী 32 বাইটকে একটি প্যারামিটার ভ্যালু হিসেবে নিন কিন্তু এটি ক্যাশে লিখবেন না। -
অন্য যেকোনো ভ্যালুর জন্য, শীর্ষ চারটি বিটকে অতিরিক্ত বাইটের সংখ্যা হিসেবে নিন এবং নিচের চারটি বিটকে ক্যাশ কি-এর সবচেয়ে গুরুত্বপূর্ণ বিট হিসেবে নিন। এখানে কিছু উদাহরণ দেওয়া হলো:
কলডাটায় বাইট ক্যাশ কি 0x0F 0x0F 0x10,0x10 0x10 0x12,0xAC 0x02AC 0x2D,0xEA, 0xD6 0x0DEAD6
ক্যাশ ম্যানিপুলেশন
ক্যাশটি Cache.sol (opens in a new tab)-এ প্রয়োগ করা হয়েছে। চলুন এটি লাইন বাই লাইন দেখে নিই।
1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.13;345contract Cache {67 bytes1 public constant INTO_CACHE = 0xFF;8 bytes1 public constant DONT_CACHE = 0xFE;এই ধ্রুবকগুলো (constants) সেই বিশেষ ক্ষেত্রগুলো ব্যাখ্যা করতে ব্যবহৃত হয় যেখানে আমরা সমস্ত তথ্য প্রদান করি এবং এটি ক্যাশে লিখতে চাই বা চাই না। ক্যাশে লেখার জন্য পূর্বে অব্যবহৃত স্টোরেজ স্লটগুলোতে দুটি SSTORE (opens in a new tab) অপারেশনের প্রয়োজন হয়, যার প্রতিটির খরচ 22100 গ্যাস, তাই আমরা এটিকে ঐচ্ছিক করে তুলি।
12 mapping(uint => uint) public val2key;ভ্যালু এবং তাদের কি-গুলোর মধ্যে একটি ম্যাপিং (opens in a new tab)। আপনি লেনদেন পাঠানোর আগে ভ্যালুগুলোকে এনকোড করার জন্য এই তথ্যটি প্রয়োজনীয়।
1 // লোকেশন n-এ key n+1 এর ভ্যালু আছে, কারণ আমাদের সংরক্ষণ করতে হবে2 // শূন্যকে "ক্যাশে নেই" হিসেবে।3 uint[] public key2val;আমরা কি থেকে ভ্যালুতে ম্যাপিংয়ের জন্য একটি অ্যারে ব্যবহার করতে পারি কারণ আমরা কি-গুলো নির্ধারণ করি এবং সরলতার জন্য আমরা এটি ক্রমানুসারে করি।
1 function cacheRead(uint _key) public view returns (uint) {2 require(_key <= key2val.length, "Reading uninitialize cache entry");3 return key2val[_key-1];4 } // cacheReadক্যাশ থেকে একটি ভ্যালু পড়ুন।
1 // যদি ভ্যালুটি আগে থেকেই ক্যাশে না থাকে, তবে এটি ক্যাশে লিখুন2 // শুধুমাত্র টেস্ট কাজ করার জন্য পাবলিক করা হয়েছে3 function cacheWrite(uint _value) public returns (uint) {4 // যদি ভ্যালুটি আগে থেকেই ক্যাশে থাকে, তবে বর্তমান কী (key) রিটার্ন করুন5 if (val2key[_value] != 0) {6 return val2key[_value];7 }একই ভ্যালু একাধিকবার ক্যাশে রাখার কোনো মানে নেই। যদি ভ্যালুটি আগে থেকেই সেখানে থাকে, তবে কেবল বিদ্যমান কি-টি রিটার্ন করুন।
1 // যেহেতু 0xFE একটি বিশেষ ক্ষেত্র, ক্যাশ ধারণ করতে পারে এমন সবচেয়ে বড় কী (key) হলো2 // 0x0D এবং এরপরে ১৫টি 0xFF। যদি ক্যাশের দৈর্ঘ্য আগে থেকেই এত3 // বড় হয়, তবে ফেইল করবে।4 // 1 2 3 4 5 6 7 8 9 A B C D E F5 require(key2val.length+1 < 0x0DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,6 "cache overflow");আমার মনে হয় না আমরা কখনো এত বড় ক্যাশ পাব (প্রায় 1.8*1037 এন্ট্রি, যা সংরক্ষণ করতে প্রায় 1027 TB প্রয়োজন হবে)। তবে, আমার বয়স যথেষ্ট হয়েছে এটা মনে রাখার জন্য যে "640kB সর্বদা যথেষ্ট হবে" (opens in a new tab)। এই পরীক্ষাটি খুব সস্তা।
1 // পরবর্তী কী (key) ব্যবহার করে ভ্যালুটি লিখুন2 val2key[_value] = key2val.length+1;রিভার্স লুকআপ যোগ করুন (ভ্যালু থেকে কি-তে)।
1 key2val.push(_value);ফরোয়ার্ড লুকআপ যোগ করুন (কি থেকে ভ্যালুতে)। যেহেতু আমরা ক্রমানুসারে ভ্যালু নির্ধারণ করি, তাই আমরা এটিকে শেষ অ্যারে ভ্যালুর পরে যোগ করতে পারি।
1 return key2val.length;2 } // cacheWritekey2val-এর নতুন দৈর্ঘ্য রিটার্ন করুন, যা হলো সেই সেল যেখানে নতুন ভ্যালুটি সংরক্ষিত আছে।
1 function _calldataVal(uint startByte, uint length)2 private pure returns (uint)এই ফাংশনটি যেকোনো দৈর্ঘ্যের (32 বাইট পর্যন্ত, ওয়ার্ড সাইজ) কলডাটা থেকে একটি ভ্যালু পড়ে।
1 {2 uint _retVal;34 require(length < 0x21,5 "_calldataVal length limit is 32 bytes");6 require(length + startByte <= msg.data.length,7 "_calldataVal trying to read beyond calldatasize");এই ফাংশনটি ইন্টারনাল, তাই যদি বাকি কোডটি সঠিকভাবে লেখা হয় তবে এই পরীক্ষাগুলোর প্রয়োজন নেই। তবে, এগুলোর খরচ খুব বেশি নয় তাই আমরা এগুলো রাখতে পারি।
1 assembly {2 _retVal := calldataload(startByte)3 }এই কোডটি Yul (opens in a new tab)-এ লেখা। এটি কলডাটা থেকে একটি 32 বাইট ভ্যালু পড়ে। এটি কাজ করে এমনকি যদি কলডাটা startByte+32-এর আগে থেমে যায় কারণ EVM-এ আনইনিশিয়ালাইজড স্পেসকে শূন্য হিসেবে বিবেচনা করা হয়।
1 _retVal = _retVal >> (256-length*8);আমরা অগত্যা একটি 32 বাইট ভ্যালু চাই না। এটি অতিরিক্ত বাইটগুলো থেকে মুক্তি দেয়।
1 return _retVal;2 } // _calldataVal345 // _fromByte থেকে শুরু করে কলডেটা (calldata) থেকে একটি একক প্যারামিটার পড়ুন6 function _readParam(uint _fromByte) internal7 returns (uint _nextByte, uint _parameterValue)8 {কলডাটা থেকে একটি একক প্যারামিটার পড়ুন। মনে রাখবেন যে আমাদের কেবল পঠিত ভ্যালুটিই রিটার্ন করতে হবে না, বরং পরবর্তী বাইটের অবস্থানও রিটার্ন করতে হবে কারণ প্যারামিটারগুলো 1 বাইট থেকে 33 বাইট পর্যন্ত দীর্ঘ হতে পারে।
1 // প্রথম বাইট আমাদের বলে দেয় কীভাবে বাকি অংশকে ব্যাখ্যা করতে হবে2 uint8 _firstByte;34 _firstByte = uint8(_calldataVal(_fromByte, 1));সলিডিটি সম্ভাব্য বিপজ্জনক ইমপ্লিসিট টাইপ কনভার্সন (opens in a new tab) নিষিদ্ধ করে বাগের সংখ্যা কমানোর চেষ্টা করে। একটি ডাউনগ্রেড, উদাহরণস্বরূপ 256 বিট থেকে 8 বিটে, এক্সপ্লিসিট হতে হবে।
12 // ভ্যালুটি পড়ুন, কিন্তু এটি ক্যাশে লিখবেন না3 if (_firstByte == uint8(DONT_CACHE))4 return(_fromByte+33, _calldataVal(_fromByte+1, 32));56 // ভ্যালুটি পড়ুন এবং এটি ক্যাশে লিখুন7 if (_firstByte == uint8(INTO_CACHE)) {8 uint _param = _calldataVal(_fromByte+1, 32);9 cacheWrite(_param);10 return(_fromByte+33, _param);11 }1213 // যদি আমরা এখানে পৌঁছাই, তার মানে আমাদের ক্যাশ থেকে পড়তে হবে1415 // পড়ার জন্য অতিরিক্ত বাইটের সংখ্যা16 uint8 _extraBytes = _firstByte / 16;সব দেখাননিচের নিবল (nibble) (opens in a new tab) নিন এবং ক্যাশ থেকে ভ্যালুটি পড়ার জন্য এটিকে অন্যান্য বাইটের সাথে একত্রিত করুন।
1 uint _key = (uint256(_firstByte & 0x0F) << (8*_extraBytes)) +2 _calldataVal(_fromByte+1, _extraBytes);34 return (_fromByte+_extraBytes+1, cacheRead(_key));56 } // _readParam789 // n সংখ্যক প্যারামিটার পড়ুন (ফাংশনগুলো জানে তারা কতগুলো প্যারামিটার আশা করে)10 function _readParams(uint _paramNum) internal returns (uint[] memory) {সব দেখানআমরা কলডাটা থেকেই আমাদের কাছে থাকা প্যারামিটারের সংখ্যা পেতে পারতাম, কিন্তু যে ফাংশনগুলো আমাদের কল করে তারা জানে যে তারা কতগুলো প্যারামিটার আশা করে। তাদের আমাদের বলতে দেওয়াটা সহজ।
1 // যে প্যারামিটারগুলো আমরা পড়েছি2 uint[] memory params = new uint[](_paramNum);34 // প্যারামিটারগুলো বাইট ৪ থেকে শুরু হয়, এর আগে থাকে ফাংশন সিগনেচার5 uint _atByte = 4;67 for(uint i=0; i<_paramNum; i++) {8 (_atByte, params[i]) = _readParam(_atByte);9 }সব দেখানআপনার প্রয়োজনীয় সংখ্যা না পাওয়া পর্যন্ত প্যারামিটারগুলো পড়ুন। যদি আমরা কলডাটার শেষ পেরিয়ে যাই, তবে _readParams কলটি রিভার্ট করবে।
12 return(params);3 } // readParams45 // _readParams টেস্ট করার জন্য, চারটি প্যারামিটার পড়ার টেস্ট করুন6 function fourParam() public7 returns (uint256,uint256,uint256,uint256)8 {9 uint[] memory params;10 params = _readParams(4);11 return (params[0], params[1], params[2], params[3]);12 } // fourParamসব দেখানFoundry-এর একটি বড় সুবিধা হলো এটি সলিডিটিতে পরীক্ষা লেখার অনুমতি দেয় (নিচে ক্যাশ টেস্টিং দেখুন)। এটি ইউনিট টেস্টগুলোকে অনেক সহজ করে তোলে। এটি এমন একটি ফাংশন যা চারটি প্যারামিটার পড়ে এবং সেগুলো রিটার্ন করে যাতে পরীক্ষাটি যাচাই করতে পারে যে সেগুলো সঠিক ছিল।
1 // একটি ভ্যালু নিন, এমন বাইট রিটার্ন করুন যা এটিকে এনকোড করবে (সম্ভব হলে ক্যাশ ব্যবহার করে)2 function encodeVal(uint _val) public view returns(bytes memory) {encodeVal হলো এমন একটি ফাংশন যা অফচেইন কোড কল করে কলডাটা তৈরি করতে সাহায্য করার জন্য যা ক্যাশ ব্যবহার করে। এটি একটি একক ভ্যালু গ্রহণ করে এবং এটিকে এনকোড করা বাইটগুলো রিটার্ন করে। এই ফাংশনটি একটি view, তাই এর জন্য কোনো লেনদেন প্রয়োজন হয় না এবং বাহ্যিকভাবে কল করা হলে কোনো গ্যাস খরচ হয় না।
1 uint _key = val2key[_val];23 // ভ্যালুটি এখনও ক্যাশে নেই, এটি যোগ করুন4 if (_key == 0)5 return bytes.concat(INTO_CACHE, bytes32(_val));EVM-এ সমস্ত আনইনিশিয়ালাইজড স্টোরেজকে শূন্য বলে ধরে নেওয়া হয়। তাই যদি আমরা এমন কোনো ভ্যালুর জন্য কি খুঁজি যা সেখানে নেই, তবে আমরা একটি শূন্য পাই। সেক্ষেত্রে এটিকে এনকোড করা বাইটগুলো হলো INTO_CACHE (যাতে এটি পরের বার ক্যাশ করা হয়), যার পরে প্রকৃত ভ্যালুটি থাকে।
1 // যদি কী (key) <0x10 হয়, তবে এটিকে একটি একক বাইট হিসেবে রিটার্ন করুন2 if (_key < 0x10)3 return bytes.concat(bytes1(uint8(_key)));একক বাইটগুলো সবচেয়ে সহজ। আমরা কেবল bytes.concat (opens in a new tab) ব্যবহার করি একটি bytes<n> টাইপকে একটি বাইট অ্যারেতে পরিণত করতে যা যেকোনো দৈর্ঘ্যের হতে পারে। নাম সত্ত্বেও, এটি শুধুমাত্র একটি আর্গুমেন্ট প্রদান করা হলে ঠিকঠাক কাজ করে।
1 // দুই বাইটের ভ্যালু, 0x1vvv হিসেবে এনকোড করা2 if (_key < 0x1000)3 return bytes.concat(bytes2(uint16(_key) | 0x1000));যখন আমাদের কাছে এমন একটি কি থাকে যা 163-এর কম, তখন আমরা এটিকে দুই বাইটে প্রকাশ করতে পারি। আমরা প্রথমে _key-কে, যা একটি 256 বিট ভ্যালু, একটি 16 বিট ভ্যালুতে রূপান্তর করি এবং প্রথম বাইটে অতিরিক্ত বাইটের সংখ্যা যোগ করতে লজিক্যাল অর (or) ব্যবহার করি। তারপর আমরা এটিকে একটি bytes2 ভ্যালুতে পরিণত করি, যা bytes-এ রূপান্তরিত হতে পারে।
1 // নিচের লাইনগুলোকে লুপ হিসেবে করার হয়তো কোনো চতুর উপায় আছে,2 // কিন্তু এটি একটি ভিউ (view) ফাংশন, তাই আমি প্রোগ্রামারের সময় এবং3 // সরলতার জন্য অপ্টিমাইজ করছি।45 if (_key < 16*256**2)6 return bytes.concat(bytes3(uint24(_key) | (0x2 * 16 * 256**2)));7 if (_key < 16*256**3)8 return bytes.concat(bytes4(uint32(_key) | (0x3 * 16 * 256**3)));9 .10 .11 .12 if (_key < 16*256**14)13 return bytes.concat(bytes15(uint120(_key) | (0xE * 16 * 256**14)));14 if (_key < 16*256**15)15 return bytes.concat(bytes16(uint128(_key) | (0xF * 16 * 256**15)));সব দেখানঅন্যান্য ভ্যালুগুলো (3 বাইট, 4 বাইট ইত্যাদি) একইভাবে পরিচালনা করা হয়, শুধুমাত্র ভিন্ন ফিল্ড সাইজ সহ।
1 // যদি আমরা এখানে পৌঁছাই, তবে কোথাও কোনো ভুল আছে।2 revert("Error in encodeVal, should not happen");যদি আমরা এখানে পৌঁছাই তবে এর মানে হলো আমরা এমন একটি কি পেয়েছি যা 16*25615-এর কম নয়। কিন্তু cacheWrite কি-গুলোকে সীমাবদ্ধ করে তাই আমরা 14*25616 পর্যন্তও পৌঁছাতে পারি না (যার প্রথম বাইট 0xFE হবে, তাই এটি DONT_CACHE-এর মতো দেখাবে)। তবে ভবিষ্যতের কোনো প্রোগ্রামার যদি কোনো বাগ প্রবর্তন করে, তার জন্য একটি পরীক্ষা যোগ করতে আমাদের খুব বেশি খরচ হয় না।
1 } // encodeVal23} // ক্যাশক্যাশ টেস্টিং
Foundry-এর অন্যতম সুবিধা হলো এটি আপনাকে সলিডিটিতে পরীক্ষা লেখার সুযোগ দেয় (opens in a new tab), যা ইউনিট টেস্ট লেখা সহজ করে তোলে। Cache ক্লাসের পরীক্ষাগুলো এখানে (opens in a new tab) রয়েছে। যেহেতু টেস্টিং কোড পুনরাবৃত্তিমূলক হয়, যেমনটা পরীক্ষাগুলোর ক্ষেত্রে হয়ে থাকে, এই নিবন্ধটি কেবল আকর্ষণীয় অংশগুলো ব্যাখ্যা করে।
1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.13;34import "forge-std/Test.sol";567// কনসোলের জন্য `forge test -vv` রান করতে হবে।8import "forge-std/console.sol";এটি কেবল বয়লারপ্লেট যা টেস্ট প্যাকেজ এবং console.log ব্যবহার করার জন্য প্রয়োজনীয়।
1import "src/Cache.sol";আমরা যে কন্ট্রাক্টটি পরীক্ষা করছি তা আমাদের জানতে হবে।
1contract CacheTest is Test {2 Cache cache;34 function setUp() public {5 cache = new Cache();6 }প্রতিটি পরীক্ষার আগে setUp ফাংশনটি কল করা হয়। এই ক্ষেত্রে আমরা কেবল একটি নতুন ক্যাশ তৈরি করি, যাতে আমাদের পরীক্ষাগুলো একে অপরকে প্রভাবিত না করে।
1 function testCaching() public {পরীক্ষাগুলো হলো এমন ফাংশন যাদের নাম test দিয়ে শুরু হয়। এই ফাংশনটি মৌলিক ক্যাশ কার্যকারিতা পরীক্ষা করে, ভ্যালুগুলো লেখে এবং সেগুলো আবার পড়ে।
1 for(uint i=1; i<5000; i++) {2 cache.cacheWrite(i*i);3 }45 for(uint i=1; i<5000; i++) {6 assertEq(cache.cacheRead(i), i*i);এভাবেই আপনি প্রকৃত টেস্টিং করেন, assert... ফাংশনগুলো (opens in a new tab) ব্যবহার করে। এই ক্ষেত্রে, আমরা পরীক্ষা করি যে আমরা যে ভ্যালুটি লিখেছি সেটিই আমরা পড়েছি। আমরা cache.cacheWrite-এর ফলাফল বাতিল করতে পারি কারণ আমরা জানি যে ক্যাশ কি-গুলো লিনিয়ারলি নির্ধারিত হয়।
1 }2 } // testCaching345 // একই ভ্যালু একাধিকবার ক্যাশ করুন, নিশ্চিত করুন যে কী (key) একই6 // থাকে7 function testRepeatCaching() public {8 for(uint i=1; i<100; i++) {9 uint _key1 = cache.cacheWrite(i);10 uint _key2 = cache.cacheWrite(i);11 assertEq(_key1, _key2);12 }সব দেখানপ্রথমে আমরা প্রতিটি ভ্যালু ক্যাশে দুবার লিখি এবং নিশ্চিত করি যে কি-গুলো একই (যার মানে দ্বিতীয় লেখাটি আসলে ঘটেনি)।
1 for(uint i=1; i<100; i+=3) {2 uint _key = cache.cacheWrite(i);3 assertEq(_key, i);4 }5 } // testRepeatCachingতাত্ত্বিকভাবে এমন একটি বাগ থাকতে পারে যা পরপর ক্যাশ রাইটগুলোকে প্রভাবিত করে না। তাই এখানে আমরা কিছু রাইট করি যা পরপর নয় এবং দেখি যে ভ্যালুগুলো এখনো পুনরায় লেখা হয়নি।
1 // মেমরি বাফার থেকে একটি uint পড়ুন (আমরা যে প্যারামিটারগুলো2 // পাঠিয়েছিলাম তা ফেরত পাচ্ছি কিনা তা নিশ্চিত করতে)3 function toUint256(bytes memory _bytes, uint256 _start) internal pure4 returns (uint256)একটি bytes memory বাফার থেকে একটি 256 বিট ওয়ার্ড পড়ুন। এই ইউটিলিটি ফাংশনটি আমাদের যাচাই করতে দেয় যে আমরা যখন ক্যাশ ব্যবহার করে এমন একটি ফাংশন কল চালাই তখন আমরা সঠিক ফলাফল পাই।
1 {2 require(_bytes.length >= _start + 32, "toUint256_outOfBounds");3 uint256 tempUint;45 assembly {6 tempUint := mload(add(add(_bytes, 0x20), _start))7 }Yul uint256-এর বাইরের ডেটা স্ট্রাকচার সমর্থন করে না, তাই যখন আপনি আরও পরিশীলিত ডেটা স্ট্রাকচার, যেমন মেমরি বাফার _bytes-এর উল্লেখ করেন, তখন আপনি সেই স্ট্রাকচারের এডড্রেস পান। সলিডিটি bytes memory ভ্যালুগুলোকে একটি 32 বাইট ওয়ার্ড হিসেবে সংরক্ষণ করে যাতে দৈর্ঘ্য থাকে, যার পরে প্রকৃত বাইটগুলো থাকে, তাই বাইট নম্বর _start পেতে আমাদের _bytes+32+_start গণনা করতে হবে।
12 return tempUint;3 } // toUint25645 // fourParams() এর জন্য ফাংশন সিগনেচার, সৌজন্যে6 // https://www.4byte.directory/signatures/?bytes4_signature=0x3edc1e6d7 bytes4 constant FOUR_PARAMS = 0x3edc1e6d;89 // আমরা সঠিক ভ্যালু ফেরত পাচ্ছি কিনা তা দেখার জন্য কিছু কনস্ট্যান্ট ভ্যালু10 uint256 constant VAL_A = 0xDEAD60A7;11 uint256 constant VAL_B = 0xBEEF;12 uint256 constant VAL_C = 0x600D;13 uint256 constant VAL_D = 0x600D60A7;সব দেখানটেস্টিংয়ের জন্য আমাদের কিছু ধ্রুবক প্রয়োজন।
1 function testReadParam() public {fourParams() কল করুন, একটি ফাংশন যা readParams ব্যবহার করে, এটি পরীক্ষা করতে যে আমরা প্যারামিটারগুলো সঠিকভাবে পড়তে পারি।
1 address _cacheAddr = address(cache);2 bool _success;3 bytes memory _callInput;4 bytes memory _callOutput;আমরা ক্যাশ ব্যবহার করে একটি ফাংশন কল করার জন্য সাধারণ ABI মেকানিজম ব্যবহার করতে পারি না, তাই আমাদের লো লেভেল <address>.call() (opens in a new tab) মেকানিজম ব্যবহার করতে হবে। সেই মেকানিজমটি ইনপুট হিসেবে একটি bytes memory নেয় এবং আউটপুট হিসেবে সেটি (পাশাপাশি একটি বুলিয়ান ভ্যালু) রিটার্ন করে।
1 // প্রথম কল, ক্যাশ খালি আছে2 _callInput = bytes.concat(3 FOUR_PARAMS,একই কন্ট্রাক্টের জন্য ক্যাশড ফাংশন (সরাসরি লেনদেন থেকে কলের জন্য) এবং নন-ক্যাশড ফাংশন (অন্যান্য স্মার্ট কন্ট্রাক্ট থেকে কলের জন্য) উভয়ই সমর্থন করা দরকারী। এটি করার জন্য আমাদের সবকিছু একটি fallback ফাংশনে (opens in a new tab) রাখার পরিবর্তে সঠিক ফাংশনটি কল করার জন্য সলিডিটি মেকানিজমের ওপর নির্ভর করা চালিয়ে যেতে হবে। এটি করা কম্পোজেবিলিটিকে অনেক সহজ করে তোলে। বেশিরভাগ ক্ষেত্রে ফাংশনটি সনাক্ত করার জন্য একটি একক বাইটই যথেষ্ট হবে, তাই আমরা তিনটি বাইট (16*3=48 গ্যাস) নষ্ট করছি। তবে, আমি যখন এটি লিখছি তখন সেই 48 গ্যাসের দাম 0.07 সেন্ট, যা সহজ, কম বাগ প্রবণ কোডের জন্য একটি যুক্তিসঙ্গত খরচ।
1 // প্রথম ভ্যালু, এটি ক্যাশে যোগ করুন2 cache.INTO_CACHE(),3 bytes32(VAL_A),প্রথম ভ্যালু: একটি ফ্ল্যাগ যা বলে যে এটি একটি সম্পূর্ণ ভ্যালু যা ক্যাশে লেখা দরকার, যার পরে ভ্যালুটির 32 বাইট থাকে। অন্য তিনটি ভ্যালু একই রকম, তবে VAL_B ক্যাশে লেখা হয় না এবং VAL_C হলো তৃতীয় প্যারামিটার এবং চতুর্থ প্যারামিটার উভয়ই।
1 .2 .3 .4 );5 (_success, _callOutput) = _cacheAddr.call(_callInput);এখানেই আমরা আসলে Cache কন্ট্রাক্টটি কল করি।
1 assertEq(_success, true);আমরা আশা করি কলটি সফল হবে।
1 assertEq(cache.cacheRead(1), VAL_A);2 assertEq(cache.cacheRead(2), VAL_C);আমরা একটি খালি ক্যাশ দিয়ে শুরু করি এবং তারপর VAL_A যোগ করি যার পরে VAL_C থাকে। আমরা আশা করব প্রথমটির কি 1 হবে এবং দ্বিতীয়টির 2 হবে।
1 assertEq(toUint256(_callOutput,0), VAL_A);2 assertEq(toUint256(_callOutput,32), VAL_B);3 assertEq(toUint256(_callOutput,64), VAL_C);4 assertEq(toUint256(_callOutput,96), VAL_C);আউটপুট হলো চারটি প্যারামিটার। এখানে আমরা যাচাই করি যে এটি সঠিক।
1 // দ্বিতীয় কল, আমরা ক্যাশ ব্যবহার করতে পারি2 _callInput = bytes.concat(3 FOUR_PARAMS,45 // ক্যাশে প্রথম ভ্যালু6 bytes1(0x01),16-এর নিচের ক্যাশ কি-গুলো মাত্র এক বাইটের।
1 // দ্বিতীয় ভ্যালু, এটি ক্যাশে যোগ করবেন না2 cache.DONT_CACHE(),3 bytes32(VAL_B),45 // তৃতীয় এবং চতুর্থ ভ্যালু, একই ভ্যালু6 bytes1(0x02),7 bytes1(0x02)8 );9 .10 .11 .12 } // testReadParamসব দেখানকলের পরের পরীক্ষাগুলো প্রথম কলের পরের পরীক্ষাগুলোর মতোই।
1 function testEncodeVal() public {এই ফাংশনটি testReadParam-এর মতোই, তবে প্যারামিটারগুলো স্পষ্টভাবে লেখার পরিবর্তে আমরা encodeVal() ব্যবহার করি।
1 .2 .3 .4 _callInput = bytes.concat(5 FOUR_PARAMS,6 cache.encodeVal(VAL_A),7 cache.encodeVal(VAL_B),8 cache.encodeVal(VAL_C),9 cache.encodeVal(VAL_D)10 );11 .12 .13 .14 assertEq(_callInput.length, 4+1*4);15 } // testEncodeValসব দেখানtestEncodeVal()-এ একমাত্র অতিরিক্ত পরীক্ষা হলো _callInput-এর দৈর্ঘ্য সঠিক কিনা তা যাচাই করা। প্রথম কলের জন্য এটি 4+33*4। দ্বিতীয়টির জন্য, যেখানে প্রতিটি ভ্যালু আগে থেকেই ক্যাশে রয়েছে, এটি 4+1*4।
1 // কী (key) যখন একটি একক বাইটের চেয়ে বড় হয় তখন encodeVal টেস্ট করুন2 // সর্বোচ্চ তিন বাইট কারণ ক্যাশ চার বাইট পর্যন্ত পূর্ণ করতে অনেক3 // বেশি সময় লাগে।4 function testEncodeValBig() public {5 // ক্যাশে বেশ কিছু ভ্যালু রাখুন।6 // বিষয়গুলো সহজ রাখতে, ভ্যালু n এর জন্য কী (key) n ব্যবহার করুন।7 for(uint i=1; i<0x1FFF; i++) {8 cache.cacheWrite(i);9 }সব দেখানওপরের testEncodeVal ফাংশনটি ক্যাশে কেবল চারটি ভ্যালু লেখে, তাই ফাংশনের যে অংশটি মাল্টি-বাইট ভ্যালুগুলো নিয়ে কাজ করে (opens in a new tab) তা চেক করা হয় না। কিন্তু সেই কোডটি জটিল এবং ত্রুটি-প্রবণ।
এই ফাংশনের প্রথম অংশটি হলো একটি লুপ যা 1 থেকে 0x1FFF পর্যন্ত সমস্ত ভ্যালু ক্রমানুসারে ক্যাশে লেখে, যাতে আমরা সেই ভ্যালুগুলোকে এনকোড করতে পারি এবং জানতে পারি সেগুলো কোথায় যাচ্ছে।
1 .2 .3 .45 _callInput = bytes.concat(6 FOUR_PARAMS,7 cache.encodeVal(0x000F), // এক বাইট 0x0F8 cache.encodeVal(0x0010), // দুই বাইট 0x10109 cache.encodeVal(0x0100), // দুই বাইট 0x110010 cache.encodeVal(0x1000) // তিন বাইট 0x20100011 );সব দেখানএক বাইট, দুই বাইট এবং তিন বাইট ভ্যালু পরীক্ষা করুন। আমরা এর বাইরে পরীক্ষা করি না কারণ পর্যাপ্ত স্ট্যাক এন্ট্রি লিখতে খুব বেশি সময় লাগবে (অন্তত 0x10000000, প্রায় এক বিলিয়নের এক চতুর্থাংশ)।
1 .2 .3 .4 .5 } // testEncodeValBig678 // অত্যধিক ছোট বাফারের সাথে আমরা রিভার্ট (revert) পাই কিনা তা টেস্ট করুন9 function testShortCalldata() public {সব দেখানঅস্বাভাবিক ক্ষেত্রে কী ঘটে তা পরীক্ষা করুন যেখানে পর্যাপ্ত প্যারামিটার নেই।
1 .2 .3 .4 (_success, _callOutput) = _cacheAddr.call(_callInput);5 assertEq(_success, false);6 } // testShortCalldataযেহেতু এটি রিভার্ট করে, তাই আমাদের যে ফলাফল পাওয়া উচিত তা হলো false।
1 // Call with cache keys that aren't there2 function testNoCacheKey() public {3 .4 .5 .6 _callInput = bytes.concat(7 FOUR_PARAMS,89 // First value, add it to the cache10 cache.INTO_CACHE(),11 bytes32(VAL_A),1213 // Second value14 bytes1(0x0F),15 bytes2(0x1234),16 bytes11(0xA10102030405060708090A)17 );সব দেখানএই ফাংশনটি চারটি সম্পূর্ণ বৈধ প্যারামিটার পায়, তবে ক্যাশটি খালি তাই সেখানে পড়ার মতো কোনো ভ্যালু নেই।
1 .2 .3 .4 // অত্যধিক বড় বাফারের সাথে সবকিছু ঠিকঠাক কাজ করে কিনা তা টেস্ট করুন5 function testLongCalldata() public {6 address _cacheAddr = address(cache);7 bool _success;8 bytes memory _callInput;9 bytes memory _callOutput;1011 // প্রথম কল, ক্যাশ খালি আছে12 _callInput = bytes.concat(13 FOUR_PARAMS,1415 // প্রথম ভ্যালু, এটি ক্যাশে যোগ করুন16 cache.INTO_CACHE(), bytes32(VAL_A),1718 // দ্বিতীয় ভ্যালু, এটি ক্যাশে যোগ করুন19 cache.INTO_CACHE(), bytes32(VAL_B),2021 // তৃতীয় ভ্যালু, এটি ক্যাশে যোগ করুন22 cache.INTO_CACHE(), bytes32(VAL_C),2324 // চতুর্থ ভ্যালু, এটি ক্যাশে যোগ করুন25 cache.INTO_CACHE(), bytes32(VAL_D),2627 // এবং "গুড লাক"-এর জন্য আরও একটি ভ্যালু28 bytes4(0x31112233)29 );সব দেখানএই ফাংশনটি পাঁচটি ভ্যালু পাঠায়। আমরা জানি যে পঞ্চম ভ্যালুটি উপেক্ষা করা হয় কারণ এটি একটি বৈধ ক্যাশ এন্ট্রি নয়, যা অন্তর্ভুক্ত না হলে একটি রিভার্ট ঘটাত।
1 (_success, _callOutput) = _cacheAddr.call(_callInput);2 assertEq(_success, true);3 .4 .5 .6 } // testLongCalldata78} // CacheTest9সব দেখানএকটি নমুনা অ্যাপ্লিকেশন
সলিডিটিতে পরীক্ষা লেখা খুবই ভালো, কিন্তু দিনের শেষে একটি ডিএ্যাপ-কে দরকারী হওয়ার জন্য চেইনের বাইরে থেকে আসা অনুরোধগুলো প্রক্রিয়া করতে সক্ষম হতে হবে। এই নিবন্ধটি প্রদর্শন করে কীভাবে WORM-এর সাথে একটি ডিএ্যাপ-এ ক্যাশিং ব্যবহার করতে হয়, যার অর্থ "Write Once, Read Many"। যদি কোনো কি এখনো লেখা না হয়, তবে আপনি এতে একটি ভ্যালু লিখতে পারেন। যদি কি-টি আগে থেকেই লেখা থাকে, তবে আপনি একটি রিভার্ট পাবেন।
কন্ট্রাক্ট
এটি হলো কন্ট্রাক্ট (opens in a new tab)। এটি মূলত আমরা Cache এবং CacheTest-এর সাথে যা করেছি তারই পুনরাবৃত্তি করে, তাই আমরা কেবল আকর্ষণীয় অংশগুলো কভার করি।
1import "./Cache.sol";23contract WORM is Cache {Cache ব্যবহার করার সবচেয়ে সহজ উপায় হলো এটিকে আমাদের নিজস্ব কন্ট্রাক্টে ইনহেরিট করা।
1 function writeEntryCached() external {2 uint[] memory params = _readParams(2);3 writeEntry(params[0], params[1]);4 } // writeEntryCachedএই ফাংশনটি ওপরের CacheTest-এর fourParam-এর মতোই। যেহেতু আমরা ABI স্পেসিফিকেশনগুলো অনুসরণ করি না, তাই ফাংশনে কোনো প্যারামিটার ঘোষণা না করাই ভালো।
1 // আমাদের কল করা আরও সহজ করুন2 // writeEntryCached() এর জন্য ফাংশন সিগনেচার, সৌজন্যে3 // https://www.4byte.directory/signatures/?bytes4_signature=0xe4e4f2d34 bytes4 constant public WRITE_ENTRY_CACHED = 0xe4e4f2d3;যে এক্সটার্নাল কোডটি writeEntryCached কল করে তাকে worm.writeEntryCached ব্যবহার করার পরিবর্তে ম্যানুয়ালি কলডাটা তৈরি করতে হবে, কারণ আমরা ABI স্পেসিফিকেশনগুলো অনুসরণ করি না। এই ধ্রুবক ভ্যালুটি থাকা এটিকে লেখা সহজ করে তোলে।
মনে রাখবেন যে যদিও আমরা WRITE_ENTRY_CACHED-কে একটি স্টেট ভেরিয়েবল হিসেবে সংজ্ঞায়িত করি, এটিকে বাহ্যিকভাবে পড়ার জন্য এর গেটার ফাংশন, worm.WRITE_ENTRY_CACHED() ব্যবহার করা প্রয়োজন।
1 function readEntry(uint key) public view2 returns (uint _value, address _writtenBy, uint _writtenAtBlock)রিড ফাংশনটি একটি view, তাই এর জন্য কোনো লেনদেন প্রয়োজন হয় না এবং কোনো গ্যাস খরচ হয় না। ফলস্বরূপ, প্যারামিটারের জন্য ক্যাশ ব্যবহার করার কোনো সুবিধা নেই। ভিউ ফাংশনগুলোর সাথে স্ট্যান্ডার্ড মেকানিজম ব্যবহার করা সবচেয়ে ভালো যা সহজতর।
টেস্টিং কোড
এটি হলো কন্ট্রাক্টের জন্য টেস্টিং কোড (opens in a new tab)। আবার, চলুন কেবল আকর্ষণীয় বিষয়গুলোর দিকে নজর দিই।
1 function testWReadWrite() public {2 worm.writeEntry(0xDEAD, 0x60A7);34 vm.expectRevert(bytes("entry already written"));5 worm.writeEntry(0xDEAD, 0xBEEF);এভাবেই (vm.expectRevert) (opens in a new tab) আমরা একটি Foundry টেস্টে নির্দিষ্ট করি যে পরবর্তী কলটি ব্যর্থ হওয়া উচিত এবং ব্যর্থতার রিপোর্ট করা কারণটি কী। এটি তখন প্রযোজ্য হয় যখন আমরা কলডাটা তৈরি করার এবং লো লেভেল ইন্টারফেস (<contract>.call() ইত্যাদি) ব্যবহার করে কন্ট্রাক্ট কল করার পরিবর্তে <contract>.<function name>() সিনট্যাক্স ব্যবহার করি।
1 function testReadWriteCached() public {2 uint cacheGoat = worm.cacheWrite(0x60A7);এখানে আমরা এই বিষয়টি ব্যবহার করি যে cacheWrite ক্যাশ কি রিটার্ন করে। এটি এমন কিছু নয় যা আমরা প্রোডাকশনে ব্যবহার করার আশা করব, কারণ cacheWrite স্টেট পরিবর্তন করে এবং তাই কেবল একটি লেনদেনের সময় কল করা যেতে পারে। লেনদেনের কোনো রিটার্ন ভ্যালু থাকে না, যদি তাদের ফলাফল থাকে তবে সেই ফলাফলগুলো ইভেন্ট হিসেবে নির্গত হওয়ার কথা। তাই cacheWrite রিটার্ন ভ্যালুটি কেবল অনচেইন কোড থেকে অ্যাক্সেসযোগ্য এবং অনচেইন কোডের প্যারামিটার ক্যাশিংয়ের প্রয়োজন নেই।
1 (_success,) = address(worm).call(_callInput);এভাবেই আমরা সলিডিটিকে বলি যে যদিও <contract address>.call()-এর দুটি রিটার্ন ভ্যালু রয়েছে, আমরা কেবল প্রথমটি নিয়ে চিন্তা করি।
1 (_success,) = address(worm).call(_callInput);2 assertEq(_success, false);যেহেতু আমরা লো লেভেল <address>.call() ফাংশন ব্যবহার করি, তাই আমরা vm.expectRevert() ব্যবহার করতে পারি না এবং কল থেকে প্রাপ্ত বুলিয়ান সাকসেস ভ্যালুর দিকে তাকাতে হবে।
1 event EntryWritten(uint indexed key, uint indexed value);23 .4 .5 .67 _callInput = bytes.concat(8 worm.WRITE_ENTRY_CACHED(), worm.encodeVal(a), worm.encodeVal(b));9 vm.expectEmit(true, true, false, false);10 emit EntryWritten(a, b);11 (_success,) = address(worm).call(_callInput);সব দেখানএভাবেই আমরা যাচাই করি যে কোডটি Foundry-তে সঠিকভাবে একটি ইভেন্ট নির্গত করে (opens in a new tab)।
ক্লায়েন্ট
সলিডিটি টেস্টের সাথে আপনি যে জিনিসটি পান না তা হলো জাভাস্ক্রিপ্ট কোড যা আপনি কাট করে আপনার নিজস্ব অ্যাপ্লিকেশনে পেস্ট করতে পারেন। সেই কোডটি লেখার জন্য আমি WORM-কে Optimism Goerli (opens in a new tab)-তে ডিপ্লয় করেছি, যা Optimism-এর (opens in a new tab) নতুন টেস্টনেট। এটি 0xd34335b1d818cee54e3323d3246bd31d94e6a78a (opens in a new tab) এডড্রেস এ রয়েছে।
আপনি এখানে ক্লায়েন্ট এর জন্য জাভাস্ক্রিপ্ট কোড দেখতে পারেন (opens in a new tab)। এটি ব্যবহার করতে:
-
গিট রিপোজিটরিটি ক্লোন করুন:
1git clone https://github.com/qbzzt/20220915-all-you-can-cache.git -
প্রয়োজনীয় প্যাকেজগুলো ইনস্টল করুন:
1cd javascript2yarn -
কনফিগারেশন ফাইলটি কপি করুন:
1cp .env.example .env -
আপনার কনফিগারেশনের জন্য
.envসম্পাদনা করুন:প্যারামিটার ভ্যালু MNEMONIC এমন একটি একাউন্ট এর নেমোনিক (mnemonic) যার কাছে লেনদেনের জন্য অর্থ প্রদানের জন্য পর্যাপ্ত ETH রয়েছে। আপনি এখানে Optimism Goerli নেটওয়ার্ক এর জন্য বিনামূল্যে ETH পেতে পারেন (opens in a new tab)। OPTIMISM_GOERLI_URL Optimism Goerli-এর URL। পাবলিক এন্ডপয়েন্ট, https://goerli.optimism.io, রেট লিমিটেড কিন্তু আমাদের এখানে যা প্রয়োজন তার জন্য যথেষ্ট -
index.jsরান করুন।1node index.jsএই নমুনা অ্যাপ্লিকেশনটি প্রথমে WORM-এ একটি এন্ট্রি লেখে, কলডাটা এবং Etherscan-এ লেনদেনের একটি লিঙ্ক প্রদর্শন করে। তারপর এটি সেই এন্ট্রিটি আবার পড়ে এবং এটি যে কি ব্যবহার করে এবং এন্ট্রির ভ্যালুগুলো (ভ্যালু, ব্লক নম্বর এবং লেখক) প্রদর্শন করে।
ক্লায়েন্ট এর বেশিরভাগই সাধারণ ডিএ্যাপ জাভাস্ক্রিপ্ট। তাই আবার আমরা কেবল আকর্ষণীয় অংশগুলো নিয়ে আলোচনা করব।
1.2.3.4const main = async () => {5 const func = await worm.WRITE_ENTRY_CACHED()67 // প্রতিবার একটি নতুন কী (key) প্রয়োজন8 const key = await worm.encodeVal(Number(new Date()))একটি নির্দিষ্ট স্লটে কেবল একবারই লেখা যেতে পারে, তাই আমরা স্লটগুলো পুনরায় ব্যবহার না করার বিষয়টি নিশ্চিত করতে টাইমস্ট্যাম্প ব্যবহার করি।
1const val = await worm.encodeVal("0x600D")23// একটি এন্ট্রি লিখুন4const calldata = func + key.slice(2) + val.slice(2)Ethers আশা করে যে কল ডেটা একটি হেক্স স্ট্রিং হবে, 0x-এর পরে জোড় সংখ্যক হেক্সাডেসিমাল সংখ্যা থাকবে। যেহেতু key এবং val উভয়ই 0x দিয়ে শুরু হয়, তাই আমাদের সেই হেডারগুলো সরিয়ে ফেলতে হবে।
1const tx = await worm.populateTransaction.writeEntryCached()2tx.data = calldata34sentTx = await wallet.sendTransaction(tx)সলিডিটি টেস্টিং কোডের মতো, আমরা একটি ক্যাশড ফাংশনকে স্বাভাবিকভাবে কল করতে পারি না। এর পরিবর্তে, আমাদের একটি লোয়ার লেভেল মেকানিজম ব্যবহার করতে হবে।
1 .2 .3 .4 // এইমাত্র লেখা এন্ট্রিটি পড়ুন5 const realKey = '0x' + key.slice(4) // FF ফ্ল্যাগটি মুছে ফেলুন6 const entryRead = await worm.readEntry(realKey)7 .8 .9 .সব দেখানএন্ট্রিগুলো পড়ার জন্য আমরা সাধারণ মেকানিজম ব্যবহার করতে পারি। view ফাংশনগুলোর সাথে প্যারামিটার ক্যাশিং ব্যবহার করার কোনো প্রয়োজন নেই।
উপসংহার
এই নিবন্ধের কোডটি একটি প্রুফ অফ কনসেপ্ট, এর উদ্দেশ্য হলো ধারণাটি সহজে বোঝার উপযোগী করা। একটি প্রোডাকশন-রেডি সিস্টেমের জন্য আপনি কিছু অতিরিক্ত কার্যকারিতা প্রয়োগ করতে চাইতে পারেন:
-
uint256নয় এমন ভ্যালুগুলো পরিচালনা করুন। উদাহরণস্বরূপ, স্ট্রিং। -
একটি গ্লোবাল ক্যাশের পরিবর্তে, ব্যবহারকারী এবং ক্যাশগুলোর মধ্যে একটি ম্যাপিং থাকতে পারে। বিভিন্ন ব্যবহারকারী বিভিন্ন ভ্যালু ব্যবহার করে।
-
এডড্রেস এর জন্য ব্যবহৃত ভ্যালুগুলো অন্যান্য উদ্দেশ্যে ব্যবহৃত ভ্যালুগুলো থেকে আলাদা। শুধুমাত্র এডড্রেস এর জন্য একটি পৃথক ক্যাশ রাখা যৌক্তিক হতে পারে।
-
বর্তমানে, ক্যাশ কি-গুলো একটি "ফার্স্ট কাম, স্মলেস্ট কি" এ্যালগরিদম এর ওপর ভিত্তি করে কাজ করে। প্রথম ষোলটি ভ্যালু একটি একক বাইট হিসেবে পাঠানো যেতে পারে। পরবর্তী 4080টি ভ্যালু দুই বাইট হিসেবে পাঠানো যেতে পারে। পরবর্তী প্রায় এক মিলিয়ন ভ্যালু তিন বাইট ইত্যাদি। একটি প্রোডাকশন সিস্টেমের উচিত ক্যাশ এন্ট্রিগুলোতে ইউসেজ কাউন্টার রাখা এবং সেগুলোকে পুনর্গঠিত করা যাতে ষোলটি সবচেয়ে সাধারণ ভ্যালু এক বাইট হয়, পরবর্তী 4080টি সবচেয়ে সাধারণ ভ্যালু দুই বাইট হয় ইত্যাদি।
তবে, এটি একটি সম্ভাব্য বিপজ্জনক অপারেশন। ঘটনাগুলোর নিম্নলিখিত ক্রমটি কল্পনা করুন:
-
নোয়াম নাইভ (Noam Naive) যে এডড্রেস এ টোকেন পাঠাতে চায় তা এনকোড করতে
encodeValকল করে। সেই এডড্রেসটি অ্যাপ্লিকেশনে ব্যবহৃত প্রথমগুলোর মধ্যে একটি, তাই এনকোড করা ভ্যালুটি হলো 0x06। এটি একটিviewফাংশন, কোনো লেনদেন নয়, তাই এটি নোয়াম এবং সে যে নোড ব্যবহার করে তার মধ্যে সীমাবদ্ধ এবং অন্য কেউ এটি সম্পর্কে জানে না। -
ওয়েন ওনার (Owen Owner) ক্যাশ রিঅর্ডারিং অপারেশন চালায়। খুব কম লোকই আসলে সেই এডড্রেসটি ব্যবহার করে, তাই এটি এখন 0x201122 হিসেবে এনকোড করা হয়েছে। একটি ভিন্ন ভ্যালু, 1018-কে 0x06 নির্ধারণ করা হয়েছে।
-
নোয়াম নাইভ তার টোকেনগুলো 0x06-এ পাঠায়। সেগুলো
0x0000000000000000000000000de0b6b3a7640000এডড্রেস এ যায় এবং যেহেতু কেউ সেই এডড্রেস এর প্রাইভেট কি জানে না, তাই সেগুলো সেখানেই আটকে থাকে। নোয়াম খুশি নয়।
এই সমস্যাটি এবং ক্যাশ রিঅর্ডারের সময় মেমপুলে থাকা লেনদেনগুলোর সম্পর্কিত সমস্যা সমাধানের উপায় রয়েছে, তবে আপনাকে অবশ্যই এটি সম্পর্কে সচেতন হতে হবে।
-
আমি এখানে Optimism-এর সাথে ক্যাশিং প্রদর্শন করেছি, কারণ আমি একজন Optimism কর্মী এবং এটি সেই রোলআপ যা আমি সবচেয়ে ভালো জানি। তবে এটি এমন যেকোনো রোলআপ এর সাথে কাজ করা উচিত যা অভ্যন্তরীণ প্রক্রিয়াকরণের জন্য ন্যূনতম খরচ নেয়, যাতে এর তুলনায় L1-এ লেনদেনের ডেটা লেখা প্রধান ব্যয় হয়।
আমার আরও কাজের জন্য এখানে দেখুন (opens in a new tab)।
পেজ সর্বশেষ আপডেট: ২৫ ফেব্রুয়ারী, ২০২৬