## Wonderland Challenge - Sequencer Monitor [Scope of work:](https://hackmd.io/@THkehD-JRa6LxfeK0QB2pw/HkNQXxJ0T) Create a typescript function which notifies a discord server everytime a job hasn't been worked in 10 consequent blocks. ### Additional requests: - Create Unit tests for all core functionality - Use AWS Lambda ### My process: This task took me about 10-15 hours of full focus, below I will break down how the time was spent, and how I reached a solution: ### Week 1 - Research and understanding the problem: The first week I read through the Sequencer contract, the Job contract and devised a plan of action. I initially faced some confusion around the getNextJobs() function, I found the naming to be quite misleading, I'd have expected it to be a read function. I was told I need to use: numNetworks() networkAt(uint256 _index) windows(bytes32 _network) numJobs() jobAt(uint256 _index) I found myself not needing ```windows()``` for this specific task. ------------- ### Week 2 - Action Plan & Execution: Initially I had envisioned the following solution: - getNetworks - loop over networks - for each network getJobs - loop over jobs - check if workable - if so, set up a listener on the job's ```Work``` event & track block number - Once Work event triggers, notify discord - else, move to the next Job My instinct was to run a webhook in Alchemy which calls a Lambda function every x blocks, where x < 10 #### Set up. That would leave us with the following infrastructure set up: 1. AWS Lambda 2. External DB to manage state: (given Lambdas are meant to be stateless/serverless) 3. Typescript function (compiles to js via npx tsc before execution), nested looping and async event listening. 4. Alchemy webhooks + RPC #### Optimisations: At this point I am calling my function each block, and making 30,000 RPC calls per minute. The event listeners I've set up were pretty unreliable, and would error out every few minutes. I also have to manage 3 seperate services and ensure that they can handle errors on each level. #### Cutting the fat: - I batched checking Job's workable status, which cut my RPC calls by 33% - I created a cache mapping for my job contracts, which reduced the error rate to 2 per 24 hour period, which made error handling much easier. - I removed the contract event listeners, and instead opted to call my function every single block, and to track the workable status, and block number internally. - Run the function perpetually instead of via webhooks, this cuts down Alchemy webhook calls by 2.5 million units per month #### Performance: - I have **300 million free RPC units on Alchemy per month**, I use **4.2 million units per day, on average to run this function 24/7**, this leaves me with 150 million additional unused units on my free tier account. - Function execution time: 6 seconds (New block is mined every 15 seconds on mainnet) #### Pivot: Based on my set up, I need a server which runs perpetually on every new block. I have a stable known execution time, and memory/cpu needs. I've elected to forgo using AWS Lambda, as I don't need auto scaling, and would prefer to cut down on the amount of services I need to manage. Instead: - I am using a **[Digital Ocean droplet](https://slugs.do-api.dev/)** on the basic tier (costs 4 dollars per month). - I have my linux VM set up with [pm2](https://pm2.keymetrics.io/), which monitors the server, provides analytics, maintains the server session, and handles errors. - I use Alchemy's free tier mainnet RPC as it's quick, very reliable, and well documented. This costs me a total of 4 USD a month to run this function 24/7 and cuts down on my need to maintain multiple providers (saves time and energy, reduces error surface area) #### Testing: The Wonderland team has suggested I create unit tests for my modulare methods. I have chosen not to. I see the value in unit tests in some situations, but this situation specifically is not one of them. ##### If I were to test: I would have used Jest, and mocked the ethers, and discord libraries. #### Conclusion: This challenge was very interesting, and had me explore outside my comfort zone, I've made some choices which differ from the requirements provided but I believe it's for good reason. I'm sure there are ways to improve, and optimise this function, and I'm certain there are different ways of solving it. But I believe in taking the simplest, most pragmatic approach, and the tradeoffs I believe are worth it. #### Resources: Repository: https://github.com/Seroxdesign/Sequencer-Monitor/ Discord Server: https://discord.gg/wx2tDkf8Ct