ERC 4337 에 대한 개념과 설명은 글의 마지막에 첨부된 사이트이 아주 친절하게 잘 설명해주고 있기에 따로 정리를 하지 않았다. (나는 2023 11월에 클레이튼 devmeet에서 발표된 영상을 가장 먼저 접해서 개념을 파악하고 그 이후에 다른 블로그나 공식문서를 보면서 감을 잡았다.)
Bundler를 어떻게 사용하는지 궁금했고, 이 Bundler 를 사용하는 과정에서 발생한 삽질을 기록하기 위해 글을 작성하기로 했다.
github 는 https://github.com/eth-infinitism/bundler 다.
README.md 에서 설명하는 그대로 프로젝트를 clone 받고 preprocess 로 필요한거 설치(?) 해주었다.
yarn && yarn preprocess
그 다음은
yarn hardhat-deploy --network localhost
인데 실패했다.
$ yarn hardhat-deploy --network localhost
Debugger listening on ws://127.0.0.1:60962/94e8e93d-3d47-4f84-b398-988dcbde6611
For help, see: https://nodejs.org/en/docs/inspector
Debugger attached.
yarn run v1.22.18
$ lerna run hardhat-deploy --stream --no-prefix -- --network localhost
Debugger listening on ws://127.0.0.1:60965/cc277aef-7f17-40af-98e4-6228ab772147
For help, see: https://nodejs.org/en/docs/inspector
Debugger attached.
lerna notice cli v5.6.2
lerna info Executing command in 1 package: "yarn run hardhat-deploy --network localhost"
Debugger listening on ws://127.0.0.1:60967/abe2e685-4616-4ca7-8346-e796ca006d48
For help, see: https://nodejs.org/en/docs/inspector
Debugger attached.
$ hardhat deploy --network localhost
Debugger listening on ws://127.0.0.1:60972/512c0657-1340-4915-83c3-a21f5c936686
For help, see: https://nodejs.org/en/docs/inspector
Debugger attached.
Nothing to compile
No need to generate any newer typings.
failed to get chainId, falling back on net_version...
Error HH108: Cannot connect to the network localhost.
Please make sure your node is running, and check your internet connection and networks config
For more info go to https://hardhat.org/HH108 or run Hardhat with --show-stack-traces
Waiting for the debugger to disconnect...
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Waiting for the debugger to disconnect...
lerna ERR! yarn run hardhat-deploy --network localhost exited 1 in '@account-abstraction/bundler'
Waiting for the debugger to disconnect...
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
이유는 아마 --network localhost 에 맞는 블록체인 노드가 설정되어 있지않기 때문인 것 같다. 그래서
docker run --rm -ti --name geth -p 8545:8545 ethereum/client-go:v1.10.26 \
--miner.gaslimit 12000000 \
--http --http.api personal,eth,net,web3,debug \
--http.vhosts '*,localhost,host.docker.internal' --http.addr "0.0.0.0" \
--ignore-legacy-receipts --allow-insecure-unlock --rpc.allow-unprotected-txs \
--dev \
--verbosity 2 \
--nodiscover --maxpeers 0 --mine --miner.threads 1 \
--networkid 1337
로 로컬에서 노드를 띄워주고
다시 hardhat-deploy 해주면 이번엔 성공했다.
$ yarn hardhat-deploy --network localhost
Debugger listening on ws://127.0.0.1:61101/32a37af5-9ca7-439c-af3b-2bd30aa8b6c2
For help, see: https://nodejs.org/en/docs/inspector
Debugger attached.
yarn run v1.22.18
$ lerna run hardhat-deploy --stream --no-prefix -- --network localhost
Debugger listening on ws://127.0.0.1:61103/8841ab5a-089d-4c85-aabf-2c73028d682e
For help, see: https://nodejs.org/en/docs/inspector
Debugger attached.
lerna notice cli v5.6.2
lerna info Executing command in 1 package: "yarn run hardhat-deploy --network localhost"
Debugger listening on ws://127.0.0.1:61106/abe09073-3e08-4773-80ec-36d8729432f5
For help, see: https://nodejs.org/en/docs/inspector
Debugger attached.
$ hardhat deploy --network localhost
Debugger listening on ws://127.0.0.1:61108/522b9ae3-eee8-4f90-96aa-90592266c971
For help, see: https://nodejs.org/en/docs/inspector
Debugger attached.
Nothing to compile
No need to generate any newer typings.
Deployed EntryPoint at 0x0000000071727De22E5E9d8BAf0edAc6f37da032
funding hardhat account 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Waiting for the debugger to disconnect...
Waiting for the debugger to disconnect...
lerna success run Ran npm script 'hardhat-deploy' in 1 package in 2.5s:
lerna success - @account-abstraction/bundler
Waiting for the debugger to disconnect...
✨ Done in 3.17s.
(5/12 추가: 이거 로컬에 노드를 띄우고(도커로 한게 노드 띄운거), 그리고 거기에 entryPoint contract 를 deploy 한 거임)
그리고 번들러 실행
$ yarn run bundler
$ yarn run bundler
Debugger listening on ws://127.0.0.1:61153/07de6a10-1650-4304-9126-03523a19daa9
For help, see: https://nodejs.org/en/docs/inspector
Debugger attached.
yarn run v1.22.18
$ yarn --cwd packages/bundler bundler
Debugger listening on ws://127.0.0.1:61157/1e6ed0c7-f1b3-4ba4-8661-02f5fc0edf8d
For help, see: https://nodejs.org/en/docs/inspector
Debugger attached.
$ ts-node ./src/exec.ts --config ./localconfig/bundler.config.json
Debugger listening on ws://127.0.0.1:61159/4a36bb0f-dbc1-4ce0-9400-b1420999f5c1
For help, see: https://nodejs.org/en/docs/inspector
Debugger attached.
command-line arguments: { config: './localconfig/bundler.config.json', auto: false }
Merged configuration: {"port":"3000","entryPoint":"0x0000000071727De22E5E9d8BAf0edAc6f37da032","unsafe":false,"conditionalRpc":false,"minStake":"1","minUnstakeDelay":0,"gasFactor":"1","network":"http://127.0.0.1:8545","beneficiary":"0xd21934eD8eAf27a67f0A70042Af50A1D6d195E81","minBalance":"1","mnemonic":"./localconfig/mnemonic.txt","maxBundleGas":5000000,"autoBundleInterval":3,"autoBundleMempoolSize":10}
url= http://127.0.0.1:8545
== debugrpc was undefined
deployed EntryPoint at 0x0000000071727De22E5E9d8BAf0edAc6f37da032
url= http://127.0.0.1:8545
signer 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 balance 1.0
Bundle interval (seconds) 3
connected to network { name: 'unknown', chainId: 1337 }
running on http://localhost:3000/rpc
이까지 성공했다면
your bundler is active on local url http://localhost:3000/rpc
=> 내 번들러가 활성화되어 있다고 한다.
잘되는지 테스트는
$ yarn run runop --deployFactory --network http://localhost:8545/ --entryPoint 0x0000000071727De22E5E9d8BAf0edAc6f37da032
하면
$ yarn run runop --deployFactory --network http://localhost:8545/ --entryPoint 0x0000000071727De22E5E9d8BAf0edAc6f37da032
Debugger listening on ws://127.0.0.1:61509/8dcd733d-e87b-4bbd-9b8c-3d68894c8b29
For help, see: https://nodejs.org/en/docs/inspector
Debugger attached.
yarn run v1.22.18
$ yarn --cwd packages/bundler runop --deployFactory --network http://localhost:8545/ --entryPoint 0x0000000071727De22E5E9d8BAf0edAc6f37da032
Debugger listening on ws://127.0.0.1:61512/84e6ca4c-2e4b-4496-aab2-a77840bf4943
For help, see: https://nodejs.org/en/docs/inspector
Debugger attached.
$ ts-node ./src/runner/runop.ts --deployFactory --network http://localhost:8545/ --entryPoint 0x0000000071727De22E5E9d8BAf0edAc6f37da032
Debugger listening on ws://127.0.0.1:61515/6d56ecd6-9461-48f9-a6d1-790ab08bba5c
For help, see: https://nodejs.org/en/docs/inspector
Debugger attached.
url= http://localhost:8545/
using account index= 1715445953021
account address 0x0F61AED128d3E6a2f58123A49774935AF5742497 deployed= false bal= 0.0
funding account to 399179113200000000
data= 0xb0d691fe
reqId 0x3ceb99d87850ed9ac8400f97fc8ca9e8b3ccf8a4dcf4daa6d87ef420210b9cef txid= 0x32834ea4efbc15a737847db1338ebe8b8b6a88b6aeae4cbed059fcaafb637924
after run1
reqId 0xfd7c28b551f91a2733da16f2fa022d8a8c1473ac6a1e9fe4113d05005376a18b txid= 0xdf2a866b052ac6dcd2a05816dedc04285728463f61e5a8508fcbc807a82eaed2
after run2
이라고 한다. 이때 아까 도커로 띄워놓은 노드에서는 이런 로그가 올라왔다.
WARN [05-11|16:45:53.490] Served eth_call conn=172.17.0.1:37126 reqid=269 duration="198.417µs" err="execution reverted" errdata=0x091cd005a1b9a8ceb62b7ff0190c72c8327ef30713996459410bfc7faf5ef0dd20ce69b6
WARN [05-11|16:45:55.995] Served eth_call conn=172.17.0.1:37270 reqid=281 duration="999.417µs" err="execution reverted" errdata=0x091cd005a1b9a8ceb62b7ff0190c72c8327ef30713996459410bfc7faf5ef0dd20ce69b6
WARN [05-11|16:45:56.209] Block sealing failed err="sealing paused while waiting for transactions"
WARN [05-11|16:45:56.224] Served eth_call conn=172.17.0.1:37426 reqid=296 duration="210.292µs" err="execution reverted" errdata=0x11e6f5a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000013ceb99d87850ed9ac8400f97fc8ca9e8b3ccf8a4dcf4daa6d87ef420210b9cef
WARN [05-11|16:45:58.723] Served eth_call conn=172.17.0.1:37556 reqid=299 duration="135.75µs" err="execution reverted" errdata=0x091cd005227a0a76833e34014a79f5c1afe91c8c003d8999ebbb842f506df8db1345a792
WARN [05-11|16:45:59.000] Served eth_call conn=172.17.0.1:37658 reqid=307 duration="400.667µs" err="execution reverted" errdata=0x091cd005227a0a76833e34014a79f5c1afe91c8c003d8999ebbb842f506df8db1345a792
WARN [05-11|16:45:59.142] Block sealing failed err="sealing paused while waiting for transactions"
WARN [05-11|16:45:59.158] Served eth_call conn=172.17.0.1:37804 reqid=322 duration="274.583µs" err="execution reverted" errdata=0x11e6f5a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001fd7c28b551f91a2733da16f2fa022d8a8c1473ac6a1e9fe4113d05005376a18b
그리고 내 번들로 로그에는
UserOperation: Sender=0x0F61AED128d3E6a2f58123A49774935AF5742497 Nonce=0 EntryPoint=0x0000000071727De22E5E9d8BAf0edAc6f37da032 Paymaster=
low balance. using 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 as beneficiary instead of 0xd21934eD8eAf27a67f0A70042Af50A1D6d195E81
UserOperation: Sender=0x0F61AED128d3E6a2f58123A49774935AF5742497 Nonce=1 EntryPoint=0x0000000071727De22E5E9d8BAf0edAc6f37da032 Paymaster=
이렇게 떴다.
질 모르겠지만 일단 README대로 하니깐 뭔가 되는것 같으니 이제 커맨드랑 로그를 보면서 어떤 것들이 발생했고, 어떻게 업무에 적용시킬 지 연구하면서 이 글의 완성도를 높여야 겠다.
<5/12 삽질>
- mnemonic.txt 수정하면 beneficiary (가스비 대납해줄 주소 혹은 컨트랙트)를 수정할 수 있음.
- /bundler/packages/bundler/file.txt 에 mnemonic 을 넣어두고
yarn run runop --deployFactory --network sepolia --entryPoint 0x0000000071727De22E5E9d8BAf0edAc6f37da032b --mnemonic file.txt
이렇게 실행하면 userOp를 만들어서 entryPoint contract에 쏠 수 있을 것 같은데 안됨
$ yarn run runop --deployFactory --network sepolia --entryPoint 0x0000000071727De22E5E9d8BAf0edAc6f37da032b --mnemonic file.txt
Debugger listening on ws:/127.0.0.1:58166/fec59492-ca16-499a-96a4-b6fa082e34f7
For help, see: https:/nodejs.org/en/docs/inspector
Debugger attached.
yarn run v1.22.18
$ yarn --cwd packages/bundler runop --deployFactory --network sepolia --entryPoint 0x0000000071727De22E5E9d8BAf0edAc6f37da032b --mnemonic file.txt
Debugger listening on ws:/127.0.0.1:58171/fbb43342-5ab7-44b7-b74e-b5be1eef7f71
For help, see: https:/nodejs.org/en/docs/inspector
Debugger attached.
$ ts-node ./src/runner/runop.ts --deployFactory --network sepolia --entryPoint 0x0000000071727De22E5E9d8BAf0edAc6f37da032b --mnemonic file.txt
Debugger listening on ws:/127.0.0.1:58173/4ce3d217-e36c-4ba7-82ad-0e84dcc83151
For help, see: https:/nodejs.org/en/docs/inspector
Debugger attached.
url= https:/ethereum-sepolia-rpc.publicnode.com
using account index= 1715525893137
Error: invalid address (argument="address", value="0x0000000071727De22E5E9d8BAf0edAc6f37da032b", code=INVALID_ARGUMENT, version=address/5.7.0) (argument="_entryPoint", value="0x0000000071727De22E5E9d8BAf0edAc6f37da032b", code=INVALID_ARGUMENT, version=abi/5.7.0)
at Logger.makeError (/Users/Project/bundler/node_modules/@ethersproject/logger/src.ts/index.ts:269:28)
at Logger.throwError (/Users/Project/bundler/node_modules/@ethersproject/logger/src.ts/index.ts:281:20)
at Logger.throwArgumentError (/Users/Project/bundler/node_modules/@ethersproject/logger/src.ts/index.ts:285:21)
at AddressCoder.Coder._throwError (/Users/Project/bundler/node_modules/@ethersproject/abi/src.ts/coders/abstract-coder.ts:68:16)
at AddressCoder.encode (/Users/Project/bundler/node_modules/@ethersproject/abi/src.ts/coders/address.ts:22:18)
at /Users/Project/bundler/node_modules/@ethersproject/abi/src.ts/coders/array.ts:71:19
at Array.forEach (<anonymous>)
at pack (/Users/Project/bundler/node_modules/@ethersproject/abi/src.ts/coders/array.ts:54:12)
at TupleCoder.encode (/Users/Project/bundler/node_modules/@ethersproject/abi/src.ts/coders/tuple.ts:54:20)
at AbiCoder.encode (/Users/Project/bundler/node_modules/@ethersproject/abi/src.ts/abi-coder.ts:111:15) {
reason: 'invalid address (argument="address", value="0x0000000071727De22E5E9d8BAf0edAc6f37da032b", code=INVALID_ARGUMENT, version=address/5.7.0)',
code: 'INVALID_ARGUMENT',
argument: '_entryPoint',
value: '0x0000000071727De22E5E9d8BAf0edAc6f37da032b'
}
Waiting for the debugger to disconnect...
error Command failed with exit code 1.
info Visit https:/yarnpkg.com/en/docs/cli/run for documentation about this command.
Waiting for the debugger to disconnect...
error Command failed with exit code 1.
info Visit https:/yarnpkg.com/en/docs/cli/run for documentation about this command.
Waiting for the debugger to disconnect...
위의 삽질 내용은 말그대로 삽질 기록용이었고, 아래부터가 repo 뜯으면서 분석한 내용이다. 일하다가 좀더 깊게 이해가 되면 설명을 추가할 예정이다.
사실상 본문 시작
코드를 분석하면서 느낀건데 lint를 안쓰는지 코드 포맷이 제각각 이었고 환경변수로 빼서 한 곳에서 관리해서 써야 하는 것을 하드코딩으로 여기저기 해둔 곳이 많아서 수정할 부분이 참 많다고 느꼈다.
우선 bundler 프로젝트는 크게 package 랑 submodule 로 나뉘어져 있다. submodule 은 별도의 repo로 구성되어있고, bundler 의 메인 코드들은 /packages/bundler에 있다.
주로 사용되는 두 명령어인 bundler
와 runop
의 스크립트를 보면 packages/bundler
에서 시작된다.
"bundler": "yarn --cwd packages/bundler bundler",
"runop": "yarn --cwd packages/bundler runop",
그리고 /packages/bundler의 rupop랑 bundler 를 보면 각각 runop.ts 랑 exec.ts 에서 프로그램이 실행된다.
"runop": "ts-node ./src/runner/runop.ts",
"bundler": "ts-node ./src/exec.ts --config ./localconfig/bundler.config.json",
UserOp를 만들어서 Bundler 서버로 전송
runop.ts
파일이름 그대로 UserOp를 만들어서 Bundler 서버로 전송하는 부분이다.
main 코드는 단순히 설명하자면
- 실행 스크립트의 command-line options (또는 flag) 을 파싱해서 네트워크, 니모닉, bundler url 등의 정보를 가져와서
- UserOp를 만들어서 Bundler 서버로 전송한다.
순서로 실행된다.
Bundler를 커스텀해서 자사 서비스에서 활용하는데 있어 UserOp를 생성하는 코드를 굳이 수정할 필요는 없지만 원활한 테스트를 위해 MNEMONIC 정보를 txt 파일이 아닌 .env 로부터 가져오게 수정하고, /packages/bundler/src/Config.ts
의 getNetworkProvider
에 klaytn 체인을 추가하였다.
UserOp를 번들러 서버로 전송하는 로직은 runUserOp
에 있는데
SignedUserOp는 BaseAccountAPI.ts
의 createSignedUserOp
에서 생성하고 SignedUserOp를 번들러 서버로 전송하는 것은 HttpRpcClient.ts
의 sendUserOpToBundler
에서 실행된다.
(UnsignedUserOp는 createUnsignedUserOp
에서 생성되고 signUserOp
에서 서명된다)
Bundler 서버
yarn run bundler --network baobab --unsafe
Bundler 서버는 전송받은 UserOp를 모아서 Bundle로 뭉쳐서 Tx로 만들고 체인에 전송한다. 대충 흐름에 대한 설명은 아래와 같다.
Bundler 서버는 exec.ts
에서 시작하고, 별다른 코드 수정없이 위의 커맨드로 서버를 실행시켰다면 /packages/bundler/localconfig/bundler.config.json
파일을 기반으로 번들러 서버가 돌아간다.
로컬 기준 Bundler 는 http://localhost:3000/rpc
이다.
runBundler.ts
Bundler 서버가 실행되는 곳이다. runOp.ts
처럼 command-line 을 읽어서 적절히 세팅을 하는 부분이 여기서 이루어 진다.
Tx에 서명하는 signer 를 설정하는 부분이 이 코드의 resolveConfiguration
에서 이루어지는데 별다른 수정을 하지 않으면 signer 가 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
로 설정된다. 처음에는 귀찮아서 signer 설정을 따로 안하고 기본값을 썼는데 klay을 채워줄때마다 누가 자꾸 돈을 빼돌려서 내 주소로 바꿨다. (이 주소의 MNEMONIC이 공개되어 있어서 누구나 접근 가능하다.)
BundlerServer.ts
번들러 서버와 통신할 수 있는 API 는 여기있다. post method 로 Bundler 서버에 UserOp를 전달할 수 있고, Body Params의 method
값에 따라서 eth_sendUserOperation
등의 기능을 지원한다. eth_getUserOperationByHash
를 사용하면 UserOpHash로 UserOp가 어떤 블럭, 어떤 tx에 담겼는지도 조회가 가능하다.
UserOpMethodHandler.ts
method에 UserOperation을 생성, 검증, 전송, 조회하는 작업을 지원하는 부분이다.
sendUserOperation
UserOp를 mempool에 추가하기 전에 validate(검증)하고, executionManager.sendUserOperation
로 UserOp를 보내고, entryPointContract 를 이용하여 userOpHash를 조회한 다음 이를 client 로 return 한다.
ExecutionManager.ts
UserOp를 관리하고 실행하는 부분이다. 실제로 실행하는 것은 BundleManager.ts
에서 실행되고 여기서는 UserOp를 검증하고, 메모리 풀(mempool)에 추가하며, 일정한 조건이 충족되면 이를 번들로 묶어 전송하는 작업을 수행한다.
번들이 실행되는 조건은 코드를 수정하지않았다면 3초마다 실행되는데 이 조건은 /bundler/packages/bundler/localconfig/bundler.config.json
의 autoBundlerInterval
로 수정이 가능하다.
sendUserOperation
UserOpMethodHander.ts
에서 전달받은 UserOp를 이곳에서 다시 검증하고, UserOpHash를 만든다음에 mempool에 추가한다. 따로 코드를 수정하지 않았다면 mempool에 추가된 UserOp는 바로 BundlerManager로 인해 Tx에 실린다.
BundlerManager.ts
Bundle을 컨트롤하는 부분이다. mempool에서 userOp을 수집하고, 이를 검증한 후, 조건에 맞게 번들링하여 전송하는 작업을 수행한다.
sendBundle
mempool에서 UserOp를 꺼내서 Bundle로 만들어 Tx로 만들고 signer의 서명과 함께 트랜잭션을 실행하는 곳이다. Tx를 실행하는 과정에서 특정 UserOp에 문제가 있다면 실패한 이유에 따라 ReputationManager
를 통해 re-bundle 하거나 mempool에서 UserOp를 제거한다.
참고
국문
- https://xangle.io/research/detail/1457
- https://medium.com/curg/aa-series-2-account-abstraction-how-to-work-bcaee6afd61d
- https://velog.io/@hnleee/AAAccount-Abstraction
- https://www.youtube.com/watch?v=5ef2-JTAmvU&t=3529s
-
'Blockchain' 카테고리의 다른 글
Hardhat verify 작동 원리에 대하여 (0) | 2024.02.24 |
---|---|
Polygon zkEVM 관련 메모 (0) | 2024.02.16 |
[Klaytn] Klaytn EN 무작정 띄워보기 (0) | 2022.09.01 |