Skip to content

Conversation

technophile-04
Copy link
Member

Description:

Added the basic tables required for #11 and tried getting the flow correct.

  1. User submit the challenge by signing the message
  2. Backend verifies the challenge, call autograder, create userChallenge row for that particular user and challengeID
  3. frontend if succes pushed the user to /builders/[address] page, where we show all the challenges submited and their status.

For now we have mockAutograding which returns response to main autograder we can swap that later by making request to actual autograder.

Demo

Screen.Recording.2025-02-18.at.5.23.33.PM.mov

To clean up the DB(might occur while testing both #20 and this PR):

  1. Delete data dir from root of the repo
  2. run docker compose down
  3. run docker compose up -d
  4. run yarn drizzle-kit push

Copy link
Member

@carletex carletex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good Shiv!! Thank you <3

Didn't test it, but left a first round of reviews.

P.S. Let's review #20 and coordinate with Pablo. But I think we could merge this first, back-merge into 20 and then merge 20?

@technophile-04
Copy link
Member Author

Let's review #20 and coordinate with Pablo. But I think we could merge this first, back-merge into 20 and then merge 20?

Ohh yeah we could merge and I could help back-merge changes in #20 since this seems ready or we could do vice-versa too if we want too 🙌

Copy link
Member

@Pabl0cks Pabl0cks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GREAT job Shiv! 🙌🔥

It's working nicely, just a couple of comments, we could review them in a future PR:

  • Sometimes when I submit a challenge, I can't see the challenge that I've just submitted in the builders page that opens, seems pretty random. After refreshing the page it appears.

  • Something strange with the upsert behaviour in the user_challenges table. Seems like it increases the id on update. It may be some default drizzle behaviour when there is a serialized id, need to investigate a bit more. If it's so, maybe we can change database schema and just delete that autonumeric id, since userAddress + challengeCode could be a good primary key.

    image

@technophile-04
Copy link
Member Author

Sometimes when I submit a challenge, I can't see the challenge that I've just submitted in the builders page that opens, seems pretty random. After refreshing the page it appears.

Ohh actually NextJs seems to cache the dynamic route in client when doing client side navigation ref. Your described could be nicely see if we yarn next:build and cd packages && yarn next start

Pushed an update at 4521936 so that after submiting the form the refetch the builders page so that we get updated data always when we move from challenge page => builders profile page after submitting the challenge.

Something strange with the upsert behaviour in the user_challenges table. Seems like it increases the id on update. It may be some default drizzle behaviour when there is a serialized id, need to investigate a bit more. If it's so, maybe we can change database schema and just delete that autonumeric id, since userAddress + challengeCode could be a good primary key.

Woahh yeah nice catch! Yeah thats the behviour of serial id function and postgresSQL. Whenever we do onConflictDoUpdate (upsert) the postgres serial counter automatically updates it.

Here is the reson why postgresSQL have that design choice

PostgreSQL increments the sequence counter during onConflictDoUpdate because of how it processes the upsert operation internally. Here's the step-by-step explanation:

  1. When PostgreSQL receives an INSERT statement (even one with ON CONFLICT), it first prepares for a potential insert by:

    • Obtaining a new value from the sequence (for serial/identity columns)
    • Checking the unique constraints/indexes
  2. The sequence value is reserved BEFORE PostgreSQL checks whether there will be a conflict. This happens because:

    • PostgreSQL needs to have the value ready in case the insert succeeds
    • Sequences are designed for high-performance concurrent access
    • Rolling back a sequence number is more expensive than letting it increment
  3. Even if a conflict is detected and the UPDATE path is taken:

    • The sequence value has already been obtained
    • For performance reasons, PostgreSQL doesn't "return" this value to the sequence
    • The gap in sequence numbers is considered acceptable by design

This behavior is a trade-off between:

  • Performance (avoiding sequence contention)
  • Simplicity (not having to manage sequence rollbacks)
  • Consistency (ensuring unique values even under concurrent operations)

It's worth noting that sequence gaps can occur in many scenarios, not just with onConflictDoUpdate:

  • Rolled back transactions
  • Failed inserts
  • Concurrent operations
  • Explicit sequence calls that aren't used

This is why sequence numbers in PostgreSQL should never be relied upon to be strictly consecutive - they're guaranteed to be unique but not gap-free.

So yeah lol we should be vigilant and not rely on serial id always being consecutive, sometime gap might be created in DB which is normal and reasons can be multiple not just onConflictDoUpdate.

Updated at 61334f8 to have composite key based on userAddress and challengeCode since that make sense! Thanks 🙌

Copy link
Member

@Pabl0cks Pabl0cks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woke up and found a great research! TYSM @technophile-04

The builder page is looking great and new primary key is working nicely too 🙌

Comment on lines 27 to 70
const { mutate: submit, isPending } = useMutation({
mutationFn: async () => {
if (!address) throw new Error("Wallet not connected");
try {
new URL(frontendUrl);
const etherscan = new URL(etherscanUrl);

if (!VALID_BLOCK_EXPLORER_HOSTS.includes(etherscan.host)) {
throw new Error("Please use a valid Etherscan testnet URL (Sepolia)");
}
} catch (e) {
throw new Error("Please enter valid URLs");
}

const message = {
...EIP_712_TYPED_DATA__CHALLENGE_SUBMIT.message,
challengeId,
frontendUrl,
contractUrl: etherscanUrl,
};

const signature = await signTypedDataAsync({
...EIP_712_TYPED_DATA__CHALLENGE_SUBMIT,
message,
});

return submitChallenge({
challengeId,
userAddress: address,
frontendUrl,
contractUrl: etherscanUrl,
signature,
});
},
onSuccess: () => {
notification.success("Challenge submitted successfully!");
router.push(`/builders/${address}`);
router.refresh();
closeModal();
},
onError: (error: Error) => {
notification.error(error.message);
},
});
Copy link
Member

@carletex carletex Feb 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we talked in #21 (comment)

Would it make sense to move all the heavy data/logic into a hook?

Without thinking too much:

const { submitChallenge, isPending } = useSubmitChallenge({
  onSuccess: closeModal,
});

// and then onClick

submitChallenge({
    challengeId,
    frontendUrl,
    etherscanUrl,
  });

Or if we wanted to give the hook more control

 const {
      frontendUrl,
      setFrontendUrl,
      etherscanUrl,
      setEtherscanUrl,
      submit,
      isPending,
      isValid,
    } = useSubmitChallenge({
      challengeId,
      onSuccess: closeModal,
    });

But probably Option 1 makes more sense?

What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pushed at ed3d8e7

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love it like this! Everything feels cleaner.

Thank you!

Copy link
Member

@carletex carletex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing stuff Shiv, thank you.

Just pushed ce814ce with a ToDo. Something I've been thinking and we need to take into consideration.

Merging this!!

@carletex carletex merged commit 69ad621 into main Feb 19, 2025
1 check passed
@carletex carletex deleted the basic-challenge-submission branch February 19, 2025 15:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants