মূল কন্টেন্টে যান

ওয়েব3 অ্যাপের জন্য সার্ভার কম্পোনেন্ট এবং এজেন্ট

এজেন্ট
সার্ভার
অফচেইন
ডিএ্যাপস
শিক্ষানবিস
ওরি পোমেরান্টজ
১৫ জুলাই, ২০২৪
8 মিনিট পড়া

ভূমিকা

বেশিরভাগ ক্ষেত্রে, একটি ডিসেন্ট্রালাইজড এপ্লিকেশন সফটওয়্যার ডিস্ট্রিবিউট করার জন্য একটি সার্ভার ব্যবহার করে, তবে সমস্ত প্রকৃত ইন্টারঅ্যাকশন ক্লায়েন্ট (সাধারণত, ওয়েব ব্রাউজার) এবং ব্লকচেইনের মধ্যে ঘটে।

ওয়েব সার্ভার, ক্লায়েন্ট এবং ব্লকচেইনের মধ্যে স্বাভাবিক ইন্টারঅ্যাকশন

তবে, এমন কিছু ক্ষেত্র রয়েছে যেখানে একটি অ্যাপ্লিকেশন স্বাধীনভাবে চলা সার্ভার কম্পোনেন্ট থেকে উপকৃত হতে পারে। এই ধরনের সার্ভার ইভেন্টগুলোতে এবং অন্যান্য সোর্স (যেমন একটি API) থেকে আসা রিকোয়েস্টগুলোতে লেনদেন ইস্যু করার মাধ্যমে সাড়া দিতে সক্ষম হবে।

একটি সার্ভার যুক্ত করার সাথে ইন্টারঅ্যাকশন

এই ধরনের সার্ভার বেশ কয়েকটি সম্ভাব্য কাজ পূরণ করতে পারে।

  • গোপন স্টেটের ধারক। গেমিংয়ের ক্ষেত্রে প্রায়শই গেমের জানা সমস্ত তথ্য খেলোয়াড়দের কাছে উপলব্ধ না রাখা দরকারী। তবে, ব্লকচেইনে কোনো গোপনীয়তা নেই, ব্লকচেইনে থাকা যেকোনো তথ্য যে কারো পক্ষে বের করা সহজ। অতএব, যদি গেম স্টেটের কোনো অংশ গোপন রাখতে হয়, তবে এটি অন্য কোথাও সংরক্ষণ করতে হবে (এবং সম্ভবত জিরো-নলেজ প্রুফ ব্যবহার করে সেই স্টেটের প্রভাবগুলো যাচাই করতে হবে)।

  • সেন্ট্রালাইজড ওরাকল। যদি ঝুঁকি যথেষ্ট কম হয়, তবে একটি এক্সটার্নাল সার্ভার যা অনলাইনে কিছু তথ্য পড়ে এবং তারপর চেইনে পোস্ট করে, তা একটি ওরাকল হিসেবে ব্যবহার করার জন্য যথেষ্ট ভালো হতে পারে।

  • এজেন্ট। ব্লকচেইনে কোনো লেনদেন ছাড়া কিছুই সক্রিয় হয় না। সুযোগ এলে একজন ব্যবহারকারীর পক্ষে আর্বিট্রেজ-এর মতো কাজগুলো সম্পাদন করতে একটি সার্ভার এজেন্ট হিসেবে কাজ করতে পারে।

নমুনা প্রোগ্রাম

আপনি github-এ (opens in a new tab) একটি নমুনা সার্ভার দেখতে পারেন। এই সার্ভারটি এই কন্ট্রাক্ট (opens in a new tab) থেকে আসা ইভেন্টগুলো শোনে, যা Hardhat-এর Greeter-এর একটি পরিবর্তিত সংস্করণ। যখন গ্রিটিং (greeting) পরিবর্তন করা হয়, তখন এটি আবার আগের অবস্থায় ফিরিয়ে আনে।

এটি রান করতে:

  1. রিপোজিটরিটি ক্লোন করুন।

    1git clone https://github.com/qbzzt/20240715-server-component.git
    2cd 20240715-server-component
  2. প্রয়োজনীয় প্যাকেজগুলো ইনস্টল করুন। যদি আপনার কাছে এটি আগে থেকে না থাকে, তবে প্রথমে Node ইনস্টল করুন (opens in a new tab)

    1npm install
  3. Holesky টেস্টনেট-এ ETH আছে এমন একটি একাউন্টের প্রাইভেট কি নির্দিষ্ট করতে .env এডিট করুন। যদি আপনার Holesky-তে ETH না থাকে, তবে আপনি এই ফাসেট ব্যবহার করতে পারেন (opens in a new tab)

    1PRIVATE_KEY=0x <private key goes here>
  4. সার্ভারটি চালু করুন।

    1npm start
  5. একটি ব্লক এক্সপ্লোরার (opens in a new tab)-এ যান, এবং প্রাইভেট কি থাকা এডড্রেসটি ছাড়া অন্য একটি এডড্রেস ব্যবহার করে গ্রিটিং পরিবর্তন করুন। দেখুন যে গ্রিটিংটি স্বয়ংক্রিয়ভাবে আগের অবস্থায় ফিরে গেছে।

এটি কীভাবে কাজ করে?

কীভাবে একটি সার্ভার কম্পোনেন্ট লিখতে হয় তা বোঝার সবচেয়ে সহজ উপায় হলো নমুনাটি লাইন বাই লাইন পর্যালোচনা করা।

src/app.ts

প্রোগ্রামের বেশিরভাগ অংশ src/app.ts (opens in a new tab)-এ রয়েছে।

পূর্বশর্ত অবজেক্টগুলো তৈরি করা
1import {
2 createPublicClient,
3 createWalletClient,
4 getContract,
5 http,
6 Address,
7} from "viem"

এগুলো হলো আমাদের প্রয়োজনীয় Viem (opens in a new tab) এনটিটি, ফাংশন এবং Address টাইপ (opens in a new tab)। এই সার্ভারটি TypeScript (opens in a new tab)-এ লেখা হয়েছে, যা JavaScript-এর একটি এক্সটেনশন এবং এটিকে স্ট্রংলি টাইপড (strongly typed) (opens in a new tab) করে তোলে।

1import { privateKeyToAccount } from "viem/accounts"

এই ফাংশনটি (opens in a new tab) আমাদের একটি প্রাইভেট কি-এর সাথে সম্পর্কিত এডড্রেসসহ ওয়ালেট তথ্য তৈরি করতে দেয়।

1import { holesky } from "viem/chains"

Viem-এ একটি ব্লকচেইন ব্যবহার করতে আপনাকে এর ডেফিনিশন ইমপোর্ট করতে হবে। এই ক্ষেত্রে, আমরা Holesky (opens in a new tab) টেস্ট ব্লকচেইনের সাথে কানেক্ট করতে চাই।

1// এভাবেই আমরা .env-এ থাকা ডেফিনিশনগুলো process.env-এ যোগ করি।
2import * as dotenv from "dotenv"
3dotenv.config()

এভাবেই আমরা এনভায়রনমেন্টে .env রিড করি। প্রাইভেট কি-এর জন্য আমাদের এটি প্রয়োজন (পরে দেখুন)।

1const greeterAddress : Address = "0xB8f6460Dc30c44401Be26B0d6eD250873d8a50A6"
2const greeterABI = [
3 {
4 "inputs": [
5 {
6 "internalType": "string",
7 "name": "_greeting",
8 "type": "string"
9 }
10 ],
11 "stateMutability": "nonpayable",
12 "type": "constructor"
13 },
14 .
15 .
16 .
17 {
18 "inputs": [
19 {
20 "internalType": "string",
21 "name": "_greeting",
22 "type": "string"
23 }
24 ],
25 "name": "setGreeting",
26 "outputs": [],
27 "stateMutability": "nonpayable",
28 "type": "function"
29 }
30] as const
সব দেখান

একটি কন্ট্রাক্ট ব্যবহার করতে আমাদের এর এডড্রেস এবং এর জন্য প্রয়োজন। আমরা এখানে উভয়ই প্রদান করেছি।

JavaScript-এ (এবং সেই কারণে TypeScript-এ) আপনি একটি কনস্ট্যান্টে নতুন ভ্যালু অ্যাসাইন করতে পারবেন না, তবে আপনি এতে সংরক্ষিত অবজেক্টটি পরিবর্তন করতে পারেনas const সাফিক্স ব্যবহার করে আমরা TypeScript-কে বলছি যে তালিকাটি নিজেই কনস্ট্যান্ট এবং এটি পরিবর্তন করা যাবে না।

1const publicClient = createPublicClient({
2 chain: holesky,
3 transport: http(),
4})

একটি Viem পাবলিক ক্লায়েন্ট (opens in a new tab) তৈরি করুন। পাবলিক ক্লায়েন্টগুলোর সাথে কোনো প্রাইভেট কি যুক্ত থাকে না, এবং তাই তারা লেনদেন পাঠাতে পারে না। তারা view ফাংশনগুলো (opens in a new tab) কল করতে পারে, একাউন্ট ব্যালেন্স পড়তে পারে ইত্যাদি।

1const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)

এনভায়রনমেন্ট ভেরিয়েবলগুলো process.env (opens in a new tab)-এ উপলব্ধ। তবে, TypeScript হলো স্ট্রংলি টাইপড। একটি এনভায়রনমেন্ট ভেরিয়েবল যেকোনো স্ট্রিং বা খালি হতে পারে, তাই একটি এনভায়রনমেন্ট ভেরিয়েবলের টাইপ হলো string | undefined। তবে, Viem-এ একটি কি 0x${string} (0x-এর পরে একটি স্ট্রিং) হিসেবে সংজ্ঞায়িত করা হয়। এখানে আমরা TypeScript-কে বলছি যে PRIVATE_KEY এনভায়রনমেন্ট ভেরিয়েবলটি সেই টাইপের হবে। যদি তা না হয়, তবে আমরা একটি রানটাইম এরর পাব।

এরপর privateKeyToAccount (opens in a new tab) ফাংশনটি একটি সম্পূর্ণ একাউন্ট অবজেক্ট তৈরি করতে এই প্রাইভেট কি ব্যবহার করে।

1const walletClient = createWalletClient({
2 account,
3 chain: holesky,
4 transport: http(),
5})

এরপর, আমরা একটি ওয়ালেট ক্লায়েন্ট (opens in a new tab) তৈরি করতে একাউন্ট অবজেক্টটি ব্যবহার করি। এই ক্লায়েন্টের একটি প্রাইভেট কি এবং একটি এডড্রেস রয়েছে, তাই এটি লেনদেন পাঠাতে ব্যবহার করা যেতে পারে।

1const greeter = getContract({
2 address: greeterAddress,
3 abi: greeterABI,
4 client: { public: publicClient, wallet: walletClient },
5})

যেহেতু আমাদের কাছে সমস্ত পূর্বশর্ত রয়েছে, আমরা অবশেষে একটি কন্ট্রাক্ট ইনস্ট্যান্স (opens in a new tab) তৈরি করতে পারি। আমরা অনচেইন কন্ট্রাক্টের সাথে যোগাযোগ করতে এই কন্ট্রাক্ট ইনস্ট্যান্সটি ব্যবহার করব।

ব্লকচেইন থেকে রিড করা
1console.log(`Current greeting:`, await greeter.read.greet())

যে কন্ট্রাক্ট ফাংশনগুলো শুধুমাত্র রিড-অনলি (view (opens in a new tab) এবং pure (opens in a new tab)) সেগুলো read-এর অধীনে উপলব্ধ। এই ক্ষেত্রে, আমরা এটি ব্যবহার করে greet (opens in a new tab) ফাংশনটি অ্যাক্সেস করি, যা গ্রিটিং রিটার্ন করে।

JavaScript হলো সিঙ্গেল-থ্রেডেড, তাই যখন আমরা একটি দীর্ঘমেয়াদী প্রসেস চালু করি তখন আমাদের নির্দিষ্ট করতে হবে যে আমরা এটি অ্যাসিনক্রোনাসভাবে (asynchronously) করছি (opens in a new tab)। ব্লকচেইনকে কল করার জন্য, এমনকি একটি রিড-অনলি অপারেশনের জন্যও, কম্পিউটার এবং একটি ব্লকচেইন নোডের মধ্যে একটি রাউন্ড-ট্রিপ প্রয়োজন। এই কারণেই আমরা এখানে নির্দিষ্ট করি যে কোডটিকে ফলাফলের জন্য await করতে হবে।

এটি কীভাবে কাজ করে সে সম্পর্কে আপনি আগ্রহী হলে এখানে পড়তে পারেন (opens in a new tab), তবে ব্যবহারিক ক্ষেত্রে আপনার শুধু এটুকুই জানা দরকার যে আপনি যদি এমন কোনো অপারেশন শুরু করেন যা দীর্ঘ সময় নেয় তবে আপনাকে ফলাফলের জন্য await করতে হবে, এবং যে কোনো ফাংশন যা এটি করে তাকে async হিসেবে ডিক্লেয়ার করতে হবে।

লেনদেন ইস্যু করা
1const setGreeting = async (greeting: string): Promise<any> => {

গ্রিটিং পরিবর্তন করে এমন একটি লেনদেন ইস্যু করতে আপনি এই ফাংশনটি কল করবেন। যেহেতু এটি একটি দীর্ঘ অপারেশন, তাই ফাংশনটিকে async হিসেবে ডিক্লেয়ার করা হয়েছে। ইন্টারনাল ইমপ্লিমেন্টেশনের কারণে, যেকোনো async ফাংশনকে একটি Promise অবজেক্ট রিটার্ন করতে হয়। এই ক্ষেত্রে, Promise<any> এর অর্থ হলো আমরা নির্দিষ্ট করছি না যে Promise-এ ঠিক কী রিটার্ন করা হবে।

1const txHash = await greeter.write.setGreeting([greeting])

কন্ট্রাক্ট ইনস্ট্যান্সের write ফিল্ডে এমন সমস্ত ফাংশন রয়েছে যা ব্লকচেইন স্টেটে রাইট করে (যেগুলোর জন্য একটি লেনদেন পাঠানো প্রয়োজন), যেমন setGreeting (opens in a new tab)। প্যারামিটারগুলো, যদি থাকে, একটি তালিকা হিসেবে প্রদান করা হয় এবং ফাংশনটি লেনদেনের হ্যাস রিটার্ন করে।

1 console.log(`Working on a fix, see https://eth-holesky.blockscout.com/tx/${txHash}`)
2
3 return txHash
4}

লেনদেনের হ্যাস রিপোর্ট করুন (এটি দেখার জন্য ব্লক এক্সপ্লোরারের একটি URL-এর অংশ হিসেবে) এবং এটি রিটার্ন করুন।

ইভেন্টগুলোতে সাড়া দেওয়া
1greeter.watchEvent.SetGreeting({

watchEvent ফাংশনটি (opens in a new tab) আপনাকে নির্দিষ্ট করতে দেয় যে একটি ইভেন্ট এমিট (emit) হলে একটি ফাংশন রান করবে। আপনি যদি শুধুমাত্র এক ধরনের ইভেন্ট নিয়ে কাজ করতে চান (এই ক্ষেত্রে, SetGreeting), তবে আপনি নিজেকে সেই ইভেন্ট টাইপের মধ্যে সীমাবদ্ধ রাখতে এই সিনট্যাক্সটি ব্যবহার করতে পারেন।

1 onLogs: logs => {

লগ এন্ট্রি থাকলে onLogs ফাংশনটি কল করা হয়। ইথিরিয়ামে "লগ" এবং "ইভেন্ট" সাধারণত একে অপরের পরিপূরক হিসেবে ব্যবহৃত হয়।

1console.log(
2 `Address ${logs[0].args.sender} changed the greeting to ${logs[0].args.greeting}`
3)

একাধিক ইভেন্ট থাকতে পারে, তবে সরলতার জন্য আমরা শুধুমাত্র প্রথমটি নিয়ে কাজ করি। logs[0].args হলো ইভেন্টের আর্গুমেন্ট, এই ক্ষেত্রে sender এবং greeting

1 if (logs[0].args.sender != account.address)
2 setGreeting(`${account.address} insists on it being Hello!`)
3 }
4})

যদি সেন্ডার এই সার্ভার না হয়, তবে গ্রিটিং পরিবর্তন করতে setGreeting ব্যবহার করুন।

package.json

এই ফাইলটি (opens in a new tab) Node.js (opens in a new tab) কনফিগারেশন নিয়ন্ত্রণ করে। এই আর্টিকেলে শুধুমাত্র গুরুত্বপূর্ণ ডেফিনিশনগুলো ব্যাখ্যা করা হয়েছে।

1{
2 "main": "dist/index.js",

এই ডেফিনিশনটি নির্দিষ্ট করে যে কোন JavaScript ফাইলটি রান করতে হবে।

1 "scripts": {
2 "start": "tsc && node dist/app.js",
3 },

স্ক্রিপ্টগুলো হলো বিভিন্ন অ্যাপ্লিকেশন অ্যাকশন। এই ক্ষেত্রে, আমাদের কাছে শুধুমাত্র start রয়েছে, যা কম্পাইল করে এবং তারপর সার্ভারটি রান করে। tsc কমান্ডটি typescript প্যাকেজের অংশ এবং এটি TypeScript-কে JavaScript-এ কম্পাইল করে। আপনি যদি এটি ম্যানুয়ালি রান করতে চান, তবে এটি node_modules/.bin-এ অবস্থিত। দ্বিতীয় কমান্ডটি সার্ভার রান করে।

1 "type": "module",

একাধিক ধরনের JavaScript নোড অ্যাপ্লিকেশন রয়েছে। module টাইপটি আমাদের টপ লেভেল কোডে await ব্যবহার করতে দেয়, যা ধীরগতির (এবং অ্যাসিনক্রোনাস) অপারেশন করার সময় গুরুত্বপূর্ণ।

1 "devDependencies": {
2 "@types/node": "^20.14.2",
3 "typescript": "^5.4.5"
4 },

এগুলো এমন প্যাকেজ যা শুধুমাত্র ডেভেলপমেন্টের জন্য প্রয়োজন। এখানে আমাদের typescript প্রয়োজন এবং যেহেতু আমরা এটি Node.js-এর সাথে ব্যবহার করছি, তাই আমরা নোড ভেরিয়েবল এবং অবজেক্টগুলোর (যেমন process) টাইপও পাচ্ছি। ^<version> নোটেশন (opens in a new tab)-এর অর্থ হলো সেই সংস্করণ বা তার চেয়ে উচ্চতর সংস্করণ যাতে কোনো ব্রেকিং চেঞ্জ নেই। সংস্করণ নম্বরগুলোর অর্থ সম্পর্কে আরও তথ্যের জন্য এখানে (opens in a new tab) দেখুন।

1 "dependencies": {
2 "dotenv": "^16.4.5",
3 "viem": "2.14.1"
4 }
5}

এগুলো এমন প্যাকেজ যা রানটাইমে প্রয়োজন, যখন dist/app.js রান করা হয়।

উপসংহার

আমরা এখানে যে সেন্ট্রালাইজড সার্ভারটি তৈরি করেছি তা তার কাজ করে, যা হলো একজন ব্যবহারকারীর জন্য এজেন্ট হিসেবে কাজ করা। অন্য যে কেউ যদি চায় যে ডিএ্যাপটি কাজ করতে থাকুক এবং গ্যাস খরচ করতে ইচ্ছুক হয়, তবে তারা তাদের নিজস্ব এডড্রেস দিয়ে সার্ভারের একটি নতুন ইনস্ট্যান্স রান করতে পারে।

তবে, এটি শুধুমাত্র তখনই কাজ করে যখন সেন্ট্রালাইজড সার্ভারের কাজগুলো সহজেই যাচাই করা যায়। যদি সেন্ট্রালাইজড সার্ভারে কোনো গোপন স্টেট তথ্য থাকে, বা কঠিন ক্যালকুলেশন চালায়, তবে এটি এমন একটি সেন্ট্রালাইজড এনটিটি হয়ে যায় যাকে অ্যাপ্লিকেশনটি ব্যবহার করার জন্য আপনার বিশ্বাস করতে হবে, যা ব্লকচেইনগুলো এড়াতে চেষ্টা করে। ভবিষ্যতের একটি আর্টিকেলে আমি দেখানোর পরিকল্পনা করছি কীভাবে এই সমস্যাটি কাটিয়ে উঠতে জিরো-নলেজ প্রুফ ব্যবহার করা যায়।

আমার আরও কাজের জন্য এখানে দেখুন (opens in a new tab)

পেজ সর্বশেষ আপডেট: ৩ মার্চ, ২০২৬

এই টিউটোরিয়ালটি কি সহায়ক ছিল?