From f5a1010849130e491a07710dedc49aaf6b34e77a Mon Sep 17 00:00:00 2001 From: Ben Smith Date: Tue, 20 Jun 2023 10:57:01 +0200 Subject: [PATCH] Revert "Delete More Stuff (#52)" This reverts commit fb7dff851d568d84aac24331fd1e702977545454. --- .env.sample | 14 ++ .github/workflows/deploy.yaml | 34 ++++ .github/workflows/pull-request.yaml | 26 +++ Dockerfile | 10 + requirements/dev.txt | 3 +- requirements/prod.txt | 6 +- seed_data.zip | Bin 0 -> 168556 bytes src/dune_queries.py | 44 ++++ src/environment.py | 1 + src/fetch/__init__.py | 0 src/fetch/dune.py | 85 ++++++++ src/fetch/ipfs.py | 154 ++++++++++++++ src/fetch/orderbook.py | 111 ++++++++++ src/fetch/postgres.py | 54 +++++ src/main.py | 95 +++++++++ src/models/__init__.py | 0 src/models/app_data_content.py | 59 ++++++ src/models/batch_rewards_schema.py | 41 ++++ src/models/block_range.py | 31 +++ src/models/order_rewards_schema.py | 31 +++ src/models/tables.py | 19 ++ src/models/token_imbalance_schema.py | 27 +++ src/post/__init__.py | 0 src/{ => post}/aws.py | 59 ++---- src/record_handler.py | 107 ---------- src/scripts/__init__.py | 0 src/scripts/download_file.py | 26 +++ src/scripts/empty_bucket.py | 33 +++ src/sql/app_hash_latest_block.sql | 4 + src/sql/app_hashes.sql | 21 ++ src/sql/orderbook/batch_rewards.sql | 97 +++++++++ src/sql/orderbook/latest_block.sql | 3 + src/sql/orderbook/order_rewards.sql | 33 +++ src/sql/warehouse/latest_block.sql | 2 + src/sql/warehouse/token_imbalances.sql | 8 + src/sync/__init__.py | 2 + src/sync/app_data.py | 154 ++++++++++++++ src/sync/common.py | 20 ++ src/sync/config.py | 30 +++ src/sync/order_rewards.py | 125 ++++++++++++ src/sync/record_handler.py | 45 ++++ src/sync/token_imbalance.py | 47 +++++ src/sync/upload_handler.py | 76 +++++++ src/text_io.py | 76 ------- src/utils.py | 15 ++ tests/e2e/test_sync_app_data.py | 80 ++++++++ tests/integration/test_aws.py | 2 +- tests/integration/test_fetch_orderbook.py | 130 ++++++++++++ tests/integration/test_warehouse_fetcher.py | 42 ++++ tests/unit/__init__.py | 0 tests/unit/test_batch_rewards_schema.py | 101 +++++++++ tests/unit/test_ipfs.py | 215 ++++++++++++++++++++ tests/unit/test_order_rewards_schema.py | 59 ++++++ tests/unit/test_token_imbalance_schema.py | 46 +++++ 54 files changed, 2276 insertions(+), 227 deletions(-) create mode 100644 .github/workflows/deploy.yaml create mode 100644 Dockerfile create mode 100644 seed_data.zip create mode 100644 src/dune_queries.py create mode 100644 src/fetch/__init__.py create mode 100644 src/fetch/dune.py create mode 100644 src/fetch/ipfs.py create mode 100644 src/fetch/orderbook.py create mode 100644 src/fetch/postgres.py create mode 100644 src/main.py create mode 100644 src/models/__init__.py create mode 100644 src/models/app_data_content.py create mode 100644 src/models/batch_rewards_schema.py create mode 100644 src/models/block_range.py create mode 100644 src/models/order_rewards_schema.py create mode 100644 src/models/tables.py create mode 100644 src/models/token_imbalance_schema.py create mode 100644 src/post/__init__.py rename src/{ => post}/aws.py (82%) delete mode 100644 src/record_handler.py create mode 100644 src/scripts/__init__.py create mode 100644 src/scripts/download_file.py create mode 100644 src/scripts/empty_bucket.py create mode 100644 src/sql/app_hash_latest_block.sql create mode 100644 src/sql/app_hashes.sql create mode 100644 src/sql/orderbook/batch_rewards.sql create mode 100644 src/sql/orderbook/latest_block.sql create mode 100644 src/sql/orderbook/order_rewards.sql create mode 100644 src/sql/warehouse/latest_block.sql create mode 100644 src/sql/warehouse/token_imbalances.sql create mode 100644 src/sync/__init__.py create mode 100644 src/sync/app_data.py create mode 100644 src/sync/common.py create mode 100644 src/sync/config.py create mode 100644 src/sync/order_rewards.py create mode 100644 src/sync/record_handler.py create mode 100644 src/sync/token_imbalance.py create mode 100644 src/sync/upload_handler.py delete mode 100644 src/text_io.py create mode 100644 src/utils.py create mode 100644 tests/e2e/test_sync_app_data.py create mode 100644 tests/integration/test_fetch_orderbook.py create mode 100644 tests/integration/test_warehouse_fetcher.py create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/test_batch_rewards_schema.py create mode 100644 tests/unit/test_ipfs.py create mode 100644 tests/unit/test_order_rewards_schema.py create mode 100644 tests/unit/test_token_imbalance_schema.py diff --git a/.env.sample b/.env.sample index c913a932..2360f6d5 100644 --- a/.env.sample +++ b/.env.sample @@ -1,3 +1,10 @@ +VOLUME_PATH=data +APP_DATA_MAX_RETRIES=3 +APP_DATA_GIVE_UP_THRESHOLD=100 + +# Dune credentials +DUNE_API_KEY= + # AWS Credentials AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= @@ -6,3 +13,10 @@ AWS_INTERNAL_ROLE= AWS_EXTERNAL_ROLE= AWS_EXTERNAL_ID= AWS_BUCKET= + +#Orderbook DB Credentials +BARN_DB_URL={user}:{password}@{host}:{port}/{database} +PROD_DB_URL={user}:{password}@{host}:{port}/{database} + +# IPFS Gateway +IPFS_ACCESS_TOKEN= diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 00000000..ddf5d275 --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,34 @@ +name: deploy + +on: + push: + branches: [main] + tags: [v*] + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v2 + + - uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - id: meta + uses: docker/metadata-action@v3 + with: + images: ghcr.io/${{ github.repository }} + - uses: docker/build-push-action@v2 + with: + context: . + file: Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 8c03d1f2..1d796116 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -24,3 +24,29 @@ jobs: run: black --check ./ - name: Type Check (mypy) run: mypy src --strict + + tests: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11"] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Requirements + run: pip install -r requirements/dev.txt + - name: Unit Tests + run: python -m pytest tests/unit + env: + IPFS_ACCESS_KEY: ${{ secrets.IPFS_ACCESS_KEY }} + - name: Integration Tests + run: python -m pytest tests/integration + env: + PROD_DB_URL: ${{ secrets.PROD_DB_URL }} + BARN_DB_URL: ${{ secrets.BARN_DB_URL }} + WAREHOUSE_URL: ${{ secrets.WAREHOUSE_URL }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..335b2081 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.10 + +WORKDIR /app + +COPY requirements/* requirements/ +RUN pip install -r requirements/prod.txt +COPY ./src ./src +COPY logging.conf . + +ENTRYPOINT [ "python3", "-m" , "src.main"] diff --git a/requirements/dev.txt b/requirements/dev.txt index ee2d2d1f..1c31125c 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -5,4 +5,5 @@ black>=22.6.0 mypy==1.3.0 mypy-extensions==1.0.0 pylint>=2.14.4 -pytest>=7.1.2 \ No newline at end of file +pytest>=7.1.2 +sqlalchemy-stubs>=0.4 \ No newline at end of file diff --git a/requirements/prod.txt b/requirements/prod.txt index 495afc14..16b34982 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -1,5 +1,9 @@ dune-client>=0.3.0 +psycopg2-binary>=2.9.3 python-dotenv>=0.20.0 requests>=2.28.1 pandas>=1.5.0 -boto3>=1.26.12 \ No newline at end of file +ndjson>=0.3.1 +py-multiformats-cid>=0.4.4 +boto3>=1.26.12 +SQLAlchemy<2.0 \ No newline at end of file diff --git a/seed_data.zip b/seed_data.zip new file mode 100644 index 0000000000000000000000000000000000000000..33d7e1f32579cd4aa38a7f750a7435ccf8b86bbc GIT binary patch literal 168556 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%0D2&~vj z?_)^R^nLf2E)8Tq+Y(?qF4ln#O-+Up&r~eq|*Uf;h;>mIorA= zg=`PHdIzh_fS`loL7f=vH7(jafe3|%XEusgFE?o~Tq+h0ctu^Mdck|utr)Xr_w^WZ zr+D}auc=&OrJt@N)Jr%vASC_LdYu>yFOfs)LX7%F1iQgE^;QU zx90z!=H6{bvLs0o`=KSErgl?%Jl7xK6Cdr0C9og|Sb*P85g!5zZkGGznMlu`>aOmp zsHg}(cGOfw%v43ND=bX!P-S_W@ zkItYYLeG*=L=zH5sB#~zRtI*^el8@8EoSAR)6cj}BKj!c`W<98#L5k~QZ%eXij}8N zxn>Yaw-Rr8!4j0+tbNKFKD+n!M(X{qZ1;OJ=s&%!qQGe`jzilrCU%Hl)D;c1d$lw{ z9Fw)levq;74W(JsU{kHQG_w1oaY^gKZ|@Wu&mvcp40bCAJuKw#5|4Za?C(&#lk)&f zde9a!8gF!SoxtwEp555=8UQ}qthG+J%$CEF2+$h7LFt-Ma_c~Dt@jVz%?Rx8%V_!9 zEfpSAr3I)B;0_|15SBt6=!DJ8YM>8_i>DY+3Lcq)!bTKyaoCXTa%Mwe;E4ygSt^by zjsXClZ4#t&*^o&-Oi1u1?dx3-oQOv(<9)J=nHr4P;goPK8Xf%r4G@0pPlt+-19Z-b zOggG>qpOk1b>1bFma(ULEioc>0>{smoo{`_5*HHA`vpOE{(giU8t}w&8D7^7jsUKX zOy`bZD1NDyvK@MaC$g{sq)4;_WDVA*R+pcB0j?VezB4yt>q`9)%BK_nc_h=4sRqim z@$Y->T;P1gpIMM+?Sl}R+`WwE z>&ze2vOW0n*9Uyh+&oat)sAvpHIB3+_U<(4f-&M?&U8)oCn7c!?qs>I$YSbp8ZXbF z#_rHIGOz{kr_UbQXLd(VGJ24)Q56f4J_&PR8Nc6dekdg|WwjiR0X~{Gyt7g(QdB8J z&xrV)B2Q$FfI`4Q697|O0Ti`Hz)9Z)P#n1+^Z;1p((ACddvPUaPLaUL@ny+8`P7_g%cipfS1l^hWEoDnYksAk93ZR#I>gF$ptlz( zk+<=mk<=gVfwCXAKv4y~wgsSVSc*b<Mp=I8_ zeBh;6t*`Hq%wYil>}stX(<~;pt0p_A=j}IYX!GNq4-Wg(n*+NAV(@|BsHRL&k^pA0 zh^Q@Oiftm~ktlqtWzDWt=0I44_P!^KH_pf$Uxy9@*x^EDjA0$I>2}`Pt=*l2rk$t* zCqCnGKggn5v|w4SRPCOMh_o)IIxAAetZo*-6b{_xCG73(9V-KRy1I>kuYG93jy`^0 zPOy!}3*Tc$JRYECRx)7hYjc2i zAYn*HR#~g@o&|86ts$+xKe+(9N#7U1s@e70x(g+xBQ0=-bVx+G+Z5o~b!34MwkP-W z;dQx;e{21_eTx?mEu#>dV3|P1I<2u?qQm3#V~iIDtqas*NssTna`?wDX#0z^gyg}) z*an7}z6#1h404SQ;;;fZypQHi@BTx&5kE`QwvdTpYBcQ1j-DQ_0O?`H0nVc%xdGFe zREDqEgN1g-6c$oow;bMks`A$EFKB3kv;cP%fEAf%upR7OpgT}s9PbZ@B7W#EQ09ltn4&Y@ zv<=_32Hp|&I~*4P+{qDD@O8E?{8XTja>&%;Q~xpo!vg~)=8i%xMJITGNN)2r1|3ow zE{p|yhxNiPbu+xrN`-nyUq4`0YkD4rbkz5Es@ZP+Oj@ouu-Kgd}S0=uj)aaKK6$A^!<)CTiXGqQm{181)4rM_ zQ~LR#l{sZi{vqW3KPH=$KZeCW{)GSE<;=B*+7~dZ@8`9FB$iyliAO4P<+P=1ZEHn+ z17|o>r$f~N<2xR^wH}c>ed|~yH#;H$@Bk$qQ0675t-Q&zo`g)`!^mxke_={p=7ELp zp*jywPR}pP3wFsE|Mv5 z>eSa@xqy`8p+B6aa&3Rfq(3_|!6&;?+m8Y`KD?%|b?OuX0(djiCkuI|m3py`CCOn3*kP-~ruu+w zWA-}CkAU#L%`)iM2%`L6=vE8a({E*pKf!zaoq6=PG{zbW$lHEe09VaB4}cIRz+vax z+x^eH=ZEK0$S`U)^P({?XV0+UvUu@_00PvNnR-HcYK{_vAoP?scEJ0~GJ7cAC*weK zlCKjV-GHS)VrTK!g~a?)bUpQ`ZN@DqGItG|f8coNbLLgJb0M+u1K>wW0 z3g?GCqDg(Kt-ha3pOq=8c8tgz1tSRV`+o5KKa+FUL+u*Rg+9U7lHYp8$m^^36{y^robBI4sp z*LLrAj_oLB*j=$4;i`GhPgl3cC-K>e~zAEw~dK+}kPr#NVYMg6*@m8ywb02@L`L4_zkjZwhPcwJpDj zmmVXnV#xqQdmW6HIfLjLNFjX%AL}9$h;u1>pi%KZjQfRR--mXaQn2mixOL2IN6V7& z$^3P`WMP?x^}fcF!5Ut7QJicV#d|d6HJYtrt=1Z94whD4ib6A5YiqqMx126e&qRO3 z4_hY3&(m#xCaEH09+TsW5)jW7uTE9wW%QZb+|_K?@MJ-#emvNUhrJTPi;Q-hqLFH&NM)cJ7V@u;a+D#3Nc%k zZDqgpi7G3yYEzz#x8%VDbhR-!Ay6`9#_IxJ^evayun}eM2V!PeVSg`j@CWS6)}O!E zjP_Fl(V>Y`cU>v3381&k8&XA`B!?A?5RDSgpvJ9W4Wea-eI)dC2QCTkFF#Ogfye*T zk>htIC8Vl`2ShT^l>tD(`B0+V%LYEEiSyNo<82v8VZiR2B7#(Es4@1wwIE`P{ZdlC zcn7rK4lH9s;?}lH-3|E6A;J%&*mC(xH@WoxQx*QpT&N1gv1?4cu~g&Oc_jzuEH;Xh zb_LUn07o@wtRg^t6z+7gCWz^Km#Tm~_P=nA_#-)J_ei{2t~us5Lfvx_l_MV5A~M*! zG?#!k|9%c?E0KTCRvdDg0owXTg^e1AA@w-k?IvnvFISVp5)6y4!@K}cIX+!YtGV=b z*%E|Vpa$~Uu4^7QK5%b#FYo2nAw4*}%i!z*uxmaglqH~+svtkU^IB{w2ZKv1Q&P4S zfbd#Q21*UY3&O7y*o>&`jQgWlV4*1 zcSIl;6lug?rFM3T%>$x|tWnL+fTEb=1hD%sv4v{>c^&@9se#+`?eB{M1IZkAav5c- zbcGR5!ww7h*xQ4nI#i|s08a&Iw~C{=wbk140Pt@`k}GT}pE3d>LwvzkD^h|}ohA48 zM!P%_PQT3K{H*P*c9jdbyE`CQ@L$>hOg5AQ&y>IlUV%aoT~*l3AKV4(g+muL^dDI*N*dyx^MU@RGiw z11CkMI;aKpiQS5s(i0r{?gi$v)^V6Dg2dbRi;bwnA}cWRz6=hylbHAiu5w=4h_7bvjQLXbSATUl6tQ zi}LEvq9Kj6`qe8tW)Y=P?skBd2szD${UYhX)BN80u%bM6(GrvrzI zv$#r2^ir!<6bZthNhd1p9~3@Hw9lrKf22QE2A~YkpKzwfdz9MH86<9_Mt4L~6ESK0u63lLYj=!YuRa%Wz&-P0cI0kq+H6gbtO z%~o7(fNtd>Lfv&bTqmRwyb2yQVE1Gy-4>;Gtj-ArQQzxoXY+qS@&t_DuZB7+LAJP$ zsn|M~Yn=^zz@rgZoi+aV7X**lP_VIO=0wo|PN*eYryWRG>N!o?g)`_gpz;7vWCQAv zGVuD5R6gVo66mkr5E)yg>&DZM;=xf{q!wg3dK`Y7Zof{8yF6+NYtaJg1v2U!;CPf~ zuz?>vqy>%O)x8uI4nr)IBXCJPsW@BFI$c!W=MZp*?>AtlZ9A^yX{sV8(%%@e_3Wq} zBxNCqU9)UQeZUua>$9ZHOvkEjw4q3u)=6Cmhm#>(cy210y`$JPSv!hPu3$^*-wK{|o6&=+x3%kbXiHQe+uY%AJ?|(lB`N%H4rd+f&BkO} zq*pNX74Vk?gi>c;;{AsPLMK+fexk6~RZ(+Sb$8xr@<|EHgnA2l=H;*m{OdTn_9C-_mr0L*8|Ql>h=$lr(fm zS0Fy6^NNEfRf8I{?|~WM>@NiR&o^e=`Eb(r*4(uLk|p4eof^^Qs>`dRWMa=xh7WB{ zYz}%OA{Kn*UE*vqLv0lW7Ev?(NQNDSIGTOL5hyub#DG^O62;pSg=7fYk4G&3<3!42lfxGvq!&yq8ZSQ1n@ZC=CnSMLnY&rz4s-cJdo-4)jQo*FT5@ulvd!-6QvfJ z4bT(Wf3ACrhc}f68FR!sJa#*ZbJwU~YXDWQ)3<&|pBy;N5;Dks@PV4$*0Rgq(L{O9 z@j-bHEAsv0uzZ#*>dP795jeFSOPq3VV4wmD5z4Kv!M6ve^Z`H5`3gh>9;--`A>^L0 zF1^gkp`D2w31C)SM!FdbL>0hN*^FQ5ONekAol8K|kdS-@Dy7d@oy9fQSI#SYhjIlrJwRgKCRNp7 zf4oWmi`^ql%}Y?^P3o!WOEZBgFiB8SLCpjxgXq(B9f&=EIVPC0R^gE3d&L_z+2yaF zaTNNy+FrU3J91r7unT`&WgLEm9iQJ-8ZSIJC#B#^?op7X*{0>dhX?*d$&IBa@b=st zkCIVtXQNZFbB>e6Y#_ilyhWd2|GX6c&s%NZI=ooy!D(g>@Um%%t&P4T5n(AB5@&lI zCC~IyH&BF1#ts>csr7?7E027>ucgibth~u}gCc}HDLd+r|N5OapYT@wRWEsDrG{*m zd(AdWDKA1g#G&2>Ku>A2huBem<8X)|{DO*-KGMq{hN^4+gXKjs{^+l>lLQQOAiz!Y zg~S%L`Ko7Uchshr%d7aUY!BM4bQMK%aOz&QCF3Uvs%zr^hDPn1igb+#@nEvr1HB4Y z4kl|wu*3H~6Ap0fmvi(#z98e0IIN%a1Vp6t6OR!`@@Foa*0XD6#(QGUN{`(L3akM{ zN2@e!5zu-EpbGfaK~l#8I4`Y%+13SAx^hWl%n}E(@%tkmLm4VxKP3&=-tCZd<~4TU z9e=v(^gi5nnv`_ZGfiaI2RAL2#Htf%u{Z1lx(c{t;PD9wwz*PkuXPSu9iyO6{K{ce zv!v%?)BL_Em~MOSH|g4+xB!lp@Xqu$ae_A$HgUjAoYah{G+Vw}WPW@j==op1>ndKw z4f_AS#JNDKX^xFLwic%mmEyQV5oqRl@>uh7k{ZpVIh?5P?<&lReP1W`5ZUg~S63jt z{L0$+Lwa&mywAg@(j~?RjXEnV1q%jbiA{@|p>~5UB1Lz@^RF1h^C&(@Ly)7UNBmx_ zO_k}8s@`{j7D?C!Uq70Uj_Kk6Bn6q>kJRpn{W6Y3Y=b%`K3wT|unT=$zUGlb1R(^l zS59gVu))CxF37jZIOxw4hB;BostfdV>LUCMA6#M`&B(rw^yE5REqmRAm3i$*pR{0z z7rm#BSuG7YuLlA}RQX^NMC8HAVpsm8yEvhLSd`NByaT4PPV786^q|bMd*mmHsAF#{ zInolC0J`Lwi-4+2jAJL_x`)&`|At({8lzLi71!Kh7N>o3W zkD12_)XxP)Qe-BN$6qDUGsa$;CpF~{hB?M5?ys|=eFnC2OiroEHFs?7IPPP0AII)% zxLO8^j}LBs>ncpYo(+B|cU6&;8rA~fsDcGy1>9;HvEs4@5g&31O}fFVooM_JA-QDx z{y6O(x9iZGZ`DSgI?~07_FTuL=-V73XLJu}zyc*&3+zWh>+)QM&B1O2KiCFq$PpbC zOO`YPE#>7mpaNJF%~1g2-X5sXSkk_KuXk-op<@T;JPc4(P6vd#tuxrMG#_{Nn^nI* zxC&=O1fahU92zJdpo9KZNED8}7tt+V1XnsFrqPpV3AOhKs#tjb7CxZfAol$uH$d;? zS2673T%54Fq?q=oP6|zqY*00JJs8Q?7C{sni(}*LTL?+>;s{)xlS1_oyxIIqvA&B# zkhnG_gD)U~*aw?>FlVswuix4v1nnmIR=0b=Fd8E+i|t0mO#69ZQCxOUrVZwxd;~8D zBU8dy3CJtHhOpyubFQigkT8*0Yh+u0qDgFv=B8mCKO|L2DFh7OPU0cw(`E-#L#%rT*2@zz&m+by~vV zoc!U~9tAIb{T^eSF=TwKc}?I2l!ZD}uj&vY@jmGTGy$vqgQiW%(P)(kz9MeTMd4jX zT~T~FrPAnTFih}&JdQ(Z&B9iyz}`-5*Y}5!nf)|hFS}PSuFJK&PgPR9^Q6bo?L&_@ zDsBn%5j)@Z3;ff~FVYULxGP9x!x16V&;E z)n#BJ|NWa^6hYgX-8%x(DV@@8(B|78lxD%_oe9b1xPE^@fY+&|oTh-@Hdn^fm8kjR zkQMp!ECp9OQb(akdmLe)%9{sBvphcaIdin{`wrao-j0i!xED5i0Ic@-E63wPlt{lX zpBn)8gDN9nY8}fw{i3=qg>KSplQW(@YVI9)E%!D;L|#Q@_8#Of2nbE_CBIiQ56Z)p z?I{o^ait zID88swwms;Ktqt_?ppeJxNyfVz|5Xwn+1RW2C{zl@U5@XJhu?ziuAa zE{10p3A2jqd$M9DOH5uFDZ7-5W-I4|LhezTDF|0fPQ3YuV_^X}BpF}QARJBRVRoFN zsnH1!AP>KzuQf|}k8Fz2q~Pm?d>w9K>z&P2D}Lt=l%o`XUNPR+EO}qy91rT2l@@5C z5V(1tHAXR)d;-$6|IiNo{VL^%ZNC;F%2Y6?iF93Le0|`)abo_(TvR74!f+US(C|$E4 z=fqIvC-o(VLi&3R3BP+-pW_Vc$fKar-W#2goo*YobHQyl(Diy7O8A?H|L3p%x@CZC zQDab@n@hEMdKzLSlwwmTft(@qvqJ`rmvV+4M;}86F*L z(Ze~{0-^avP?u)svV0z0BuC*gw=`~U`?b0pmxAsrQ|ni zj{@o8W4JLt^wv}1>-$0<)I^;}kKu^hKsi80-0?gymkP*t%?zTuxpz_Xk5}w|ly$C`Zo9N!lnrbglHi(VJ%@8u6bOv~3+P;UBN&O1LbI4c236To?lT7evj zB-*@BivRb@!+hUer!}L|(FdD^%C$L6F42hce)Jgm#eZ%T{-a%9`cp%n;TFoR`q=4MjF)!Yp0fSl?%cpZnmG$NM=+B5o;b0D1pdUJjdO!Z0sLJPTfc{|HI>%X6()VyDD z-@qM1I^?RRhd(qu@l~z7xJ+U+5FM#1_~MMMfir5V*gyDZrOo0554eVR3pvH{2(B8i z{wE!WB)`ZO{P_jB0KpPa83I`SS)-50_i1+yIiH4j-XG!a53aoUjEOFh92Bm(6KT(B zoV3lnDH$>I1ffbCuC>I`-~r^0jgTpm^GO^Bi}-ckycJMaORml{lyXw%DIHOajg5t! zzZ+h#JyK1YNQ;OxV^dp|G*C&?@~EYeqW?&>zK`U^d~ZcM4r z!Opx?KQf0#M1{5md!jdtZ2(fBcjah#mGI!coU>qcj!s61N}7Q{d0P(l*bo8B0$`wL zgYs~~<9xx==ahX9aTI+ox{TO$zqX40`7X#`T+)6zn4w_I)KrLm0+f1g9P>b6e$lji z&|o>Nkbq1TByXVlc6uCvp)6Fj^~q1vkaxZvA*cf@rj7T1yraEz2dq42@8^aW8n054 z1L1gZ!-FRmU$P~->~^ainJ|^%Xeb#ce!5bfr~&C&OsR1gr{XsQzfnY?PEO z5Q;>s&6HYsA5`;Ni`tmh-b41nzM$_nRt%*^kas0t0iaCmj6pXK+=K!^n=^!ea}UO0 zQ4NN(3b1QHqzI6Opjc^!Nl}u}m#sX5pzsvR^8vonQ*XKk-d~n{7{m38 zyazl|!a`8blJEUY_kGuXYdv|o9v&#jdW2f4%Z0R-CaEcbjs?eFKAp(2v_7hVHX_nE z$`q&t(K+&ngfhl^7Y*HyvFB z>1ZM#d;0QM(b`%B2mW+sNFVU8hejkvN~^~GWUd1wUBIktq=Uby8hxmQ~ZFZGEIPck1|pX5^SMX&?T+-X<4OtUuM;Kif82^ATvl z6PHh5TYAjSLrb8OJ)DcOMRH#L9X9*w$^Dpw`HM&Mt}`R0{1Qc61*IUm67j`YE7=~h z)a-~oy6nBW-byX+6EvNFzHf6x4=^`mqhUD+dI)9i5?rTfqsE&f!+R=EJkkY-1wj;z z{&cG#W(j_~fhLyHdoBPloYDtV$Q;}Eu?4??cqm5M>IXSDahDS93+1q3F-1E?y1HqZRwZU^*A{P%g$;lZ56abV}_j(XfNShGCG zUQ;`hl{@D60OX}9xnV7k$zobXsg;`gKy(ZcgUG2$aoU_(@tvb&iPZob$S+zcd^nd) zK+j)4myNj~S%Ia*e(0$xA_q4ANpr|@y4(WrIo|Gr2qBUMA9`I*RUTW5(R6dAHXA)W zurXZoypkROIzWC2xuGJrlo8#Bt`+JCy00H4NC6%XC-YGP-WW7A*|86C{eQ^YgJ0+TR@TI$rQq=*P-ph-Hy6nhckY7 z(gt`@(iIN?3>)Xn_~>q%b6mxzU?p?z^XZk^nZ0@%|L8OBZ8$vwkW>1w>?lzkMpZ;V<-8$F$L` zdm6Id23RHl@q=CGkr&j#cbmG$Cs!I8>H^{bjU>LHrRD?;ayh+E7p;s%Am~}w7tYe^< zu?tzNyuZ>guzLMEbm!=*BUp-bFv*-qx+hN-0=Qq=%iiK1{1cVnl);m!JdKq_c1@G& zuN(^s&b$VMA-HV`04obH&`SaHqkkuzwB*BNvZrUVex3dU3~l#{I-K7SC)Ze8M1>p^ z=m3A;LreqJ;sO27t5H3AC?X8GA+GvzK&y*=_^Fo}`NQ)|0yLx6c7Qjq!JIQtVfxf# zl9K!Wc}OC9RjvNA;{;|nra!t|UC*Ynuj4c!T<~t$y!-=$V!8ulIgG zJ2UivSF|QrgD$Ii(P5~}*^#foKaiM!)y5Nf)89jniH z-xv6VfQkc$0fB8U#*;GvAzeqDz?euT56%o}yGwjUC>kR^gmkLVjaJ#-Jf-tCCoBbd z1XO>06EbR|>ZN@0)ybR_`+Cy2&$tV+ljeQ8Fs@VcX%+a|WSvNLp7NvVdIC+Y<^~w& z2Agf#Ku)f+(H_Kg#06G8_Go#q`wHl_j#*l&BEYT<4Errs=N>Eek#&%Y|+{f?`8U#Q%2F`phlm8bK?o6Al45; zVd-yx$ZZd7NNoiW4V3f(!@wid%Y|Z=MAPsbwRung2Kd87<*V$v9I;F1v?3)EfGs^x z104ojkzQwaA%&SZ%Lz18j8~fPRRRzP>9d{3pH&G6KNg}4%jRNsPn0_vVdeV21io{3BtZamA>ONeSPz(9%u~Cz*QcwxcAwelM!`9RDF4E4>fqdjn z@pcb%7eU|$M;0@fRA7<^DyYO?22z^hnxGq&U-r{9es@!NdlwXN0{_DMTF6U<{MqXM zkDw)opbk-y>vk(xh>ExCPM+B!HOVM6LrC`EdLQ*IBS2CSp9<1k;}Iqvh5koId)R@! zgymV-la+mBFF{4Z>C!nH|2}&-v0s|l|NMfa%Qf$&lY_En38a{o;qIvAc5!!!c6Z^4 zT38<<3wU&Tc05O9Q?Xfr{16*pUB;z$!3|^runc1-jInYg7ec{X_^=@HFC1gt+pcb| zOXKc$&tZ7IP{Kycd7RU(aDg3WeSj6l!d}RN+%aer20RTSO4V~0=wQEAo}6k;)U?IA zM0`g?wON5fo&Np2B3_iszg(w%ZyL?1CaU@m+gkvZcKO7|w&UXHsb_sW+*akyh|que zbf^7Oe2T)nT*K!Z#`9^ASS@4Fm^ipLXt|4`wW4|K&k{gm)$Zz&4}liXjp zu|!;))V9XH=xGPP^S%c0Z^c{-gt=QBXU$N z*qtt6`wekv1VG48t3#OuqJEq2$+SVGzPYSnM&9U;!Srfb<3kPA{%6_(iy_sFdD)#KtcyBT44QTfJ`k_7? zUCzEXnnmsVQFT|)oD2UMD1Ha<%|h*apw;a>7%-c?u!a?0chkPV~SxIk-B&JAhy^YdcbNHSC@Ulr8V|WvOiGzHdP*ZkXwL zU~Aa!wv_5IXADQXA|P-pu52NfCsTedzVx$9z_ZE#Tn2ENX(sD{PN^BY8MD!KYtmk0 zaN9u%9$gX5>}$OJGPDKszCcRC{ujDfr$bE z@p}CImcfGP_i4CV>Zjg!-iGr&?{&-O59BtK%^9~<+FqqSF|IS~4Nq%*w%*qQ!t=oT zlO6V1rUi#TvgFCT(P-IWbwqfCi&z07`EbuCC^6sfE#JY`P967kyJdN5XlLX)w95^t}RVgqxOLWhTyB4CR7o(j0Zhiffx z^zTcLNp0>}*O4xnfMgNzUGTf_;PhU+h013IK6|3pH`;JRRzzG-Hdt2yphnIFk_T1| zp7X#V(;Y55I?XRc2#9m<*kA6GI2~tvzMfRA99M~KfDl&{o#1v=|k0TkQbz0*zu7Z#fK!5V_s1z%69_lalTC{;3N zcJXu<=%_#FfZGL0Y;y<7WPfnZlq2d%4C;{?+87SOfSfv7;Na~HuYN#8NuVlXj@@cQ zstqmoG(l$Cd$j?dCN2(FWGyUXOdUkG6(cW10mlBP?KpEvYePau z-32nUh_u35iv00m9kTmHY5be(AV0k_qdbt%bRD~)PW&&bNe2jZ(PWCCF+pFA+7m$ zcgX+z)qm0{R80;Qhtw3X9t4|ANCu)M{C?Bf==4+IluHzQC2_Sy`1F zv4=9sRp}rd>9CZJB%l&MS6fhX8^|CmkLtSRGG1eK$DW$=a6_?9OfhRF_b*hSt#YuosYr;Qz z50`Ut`u#&05Sh9OZI^c)+wBkKl*P86;{q9+hp#~QPg((-jtO#^eCo8xa%}OK$%8b! zW)^&{u7*kBD}22zY*9&EA!?U#^7QR3WX6hq-!Wj@{L2Ax(YZWQz~mNWlZbU_H|Q{y zqwL*1AXKjLdJ*J95hHbt*)mpVWlhRK(2P*<6|0sL<<4vAQ5uS1vO=#Y|6#JWh<*PY zlLjbm)dFPVW8Q#~LoC+1_r1Gq6j9iBl?fLOYzv7W?nUgv$6b%SraLm!+{ z;jxg-STn5#5QiC4tytcIj1hC2fg-(#o56M|!_r_O3Lu0oS7!O3fIz?O??(uz<&iTb z)f$gI{C>p)MK{i)`OP)ma@p2=Fwqia`GKvs7CqEzV{e&x*A2BvjDBf>liy)9kW1Xm2+vcW6e(2@-ORNBZy}MOme=B9sX{TKIdUEE872UuVIO}xLUzqrC}JX$ zPBb@lvam3h*foy`iktcjmpsTJC?j!3`U}oPsgTi7eRJdpZEFS+?h_~B@S}ke0`>vm z^U4DpE02!a`u^AstmFHm#>F9L?0axcg3fdXaN~HR0NfYSx&<)adY(_pE>^Hr?@_r@ z1xw++$RYO9(-7oolFB}uo9b2DME~jhPS&+8HLYx9iA& zP`A5#YIF@zw@sb#!Lc3fTO)w}YJC{*m1Flk;9(IpH1Y-AQe{^w3RngqFChKNRfGsY zW}6>m+(Po-@6qffT?qvcss0%E9&{7-9oeMvTIR>Cif#1ZlxpO)Y9m|)9%-drXH-+$ z);$D56Da~BEd&$=5kiL$lu!grXrYJ#(v^-VJ%BWkfYN)96bXWgLR30ZqzQ;p6%s({ zAmvgt5O{FMyZ8CN^8UPa#u{tyG0vHD&9(Oaan6r@26I4-x);}_bc2sEMdjtGiaF}j zclX=FtYIcqOEQf%iK*fWdsNA)NR?F?Yv5oPwFQOKKkJ#ynU<&Yq#b@c6N<&83b0?K zSCp&$aZ|D*sZue|*Su_@@48R@ZOs$J;Xo~2p~`pa1#CJ8qq5{C+x-0VKVQ*^|M+5j zncZLM#63iz1@|bXdqIei4MS*6X*abYEJt{yNZas4S8z5LanYA3i+oHdoM&;oit;U~ zHi{(eTo!MctCArJtqiD^xby8XNp~%gh$U4D4Jkq{JtRw-2=MHldG%#DE4%P+KkFK~ zrvI*9>aXlszLQKtf}KyCn2uX@?Qo1#!0!Y*zf^4KN7+*zinkUtC8{o%M>JrxUdVmr zSpM+qi<93{Es|FbzgVQbpsv5lXDlcO#a@nT(TIV2hGBMa+ywk~&1VA7U?)tctJ!n8 z^ADlwT)8->M(a07&cGHC1`qicy^E~CYo=EyBCcKC-eo_AopX0eWEu!yWkQ{f%1g-@ zmm8hNzh`xlF=v)x-%91e91Km>86OoEO&kdUUz3 zQBP5|KB~Qar9IeRuiVox=%)X^+(ts+nr)Ex?V;u}r#QTQ1XZpDgoJcEYJv;d;B>+p zTl^L@okkPM_C+00`Y8tZEKNnPN#C_(!K~(0Hf@tQN&bs(FR8DlyA^*5`3W8*ClMa- zKaohUbiN{6&h2j2Y^_OB$JeX<0EZ6$q)IGJuhO>3JfC-)s$gV7V;m2#Zrh7391|BI z;~zouPR;PRbdmTNp*a`S$VL2rBUJW3!kuy1*Rx_}7$ED=Jd8R@%M*VwWVrko0Kr%p{tV z%yl(odFg|Ph31oAUeSiPc2~d{@O($zWAGFnNQQ@ISl`caK^{U;|+=w|x(@wianv9j- zGB^Xi`>nt4s*?4{shQvd@HI9oRRcjJUO1pOp0^YnLjO1x2xIKLdVK=jQpyUo=lTMv z{yO;l+UCw4=*+?H*?VH>9HI}|{OrIZxf-t@J%a6s^b8+vv{Nv<4*>&x3?G7W3NXv4 zhCPt5eRo$ud?yF(K}oevR)F%FW<2eBhPgqp?ahyvj1H!{;$IdiWq8fJy53oC9dTS) zQRv&fuq)jQE_W#R!;LWm(eLR=H2eB4*nT4qp`fni!y!O-oX$CrG`5uN*LRE?u1+N$L-@U1qS9~a3`->pTXedocK(q*_`00ZTM)k&1*6QA9-P58*1AQr+ z{U(}hBFs>D?B|GUTn1^zJNu`xAIKvhkY({EefK)gx8Z>(j~p=ZTk;n>as)0wIZyF3r5%#_iid-TSr;Kj6% z*qZt=DE1N^XQeAux;wmMx3S~_#gE;WwIH~tI(zPvIq7)Jt~>&U4(Tz}BQsB*J-v{T z_41|BeqBzPU$RPhYzxYSNu|Em)8i&3&dAMKWRZ!L1jAjAC!hQEOG~(z$e0lGu2CDu z?@@AV*g@3ArIf=Y#6FaJe+bfjgQ4X%zA z%&Ei2Yxh;iD*lbFYRB5dL($6J;-W02r?VZ>>XjEtkk8Jn8Cm*VXUe^e$6C5>wTjY0 zEA~YY)H%%Nuv`eQXV{peZH85b8e$TWP--yLTjS%tPSYQIAxLB}={+JeM%-|O;XI=x zc>~l7K8x3ay(3NX(o?ZIo{&l%l1;qH{CHzu+YR{1{F>a&0$>feD+Kq*eG{Yp+G2!A z3|3t@nP`E%a*S~(yfjL6{S?hR?Oux2f(%0aIXy8@Om>+Qh9rukHtBkQB2~SAHtv@^ z!ZP$a9s8=Gd+T!+t6ghI)yqhk0^2Uhi#T??($jf*oh$4YI)SHK;Sd_#ve{C`6Syq` z_c|aODe~FXgPTKcGqS{-PVI9)l@j{eL77@@*R<_?Su)Ve?TW76s1gjm?GBN!OWLz8 z(}<74M@cB8w>O^?luW)PU;RpSd&$Sysx2-^C0tO>`iWy(<*bJwPp0L9j;QOF)v3$U1opO;I!WL?z*6O$0Sabd=^uF-P zZ7sKLZf3%?|1zNdAJ|$0wq?+Fv;MSW`}gy&u0I z_<6T7I*YRbx!pXmh=O}&K;$30JS{qvj3jW@f3&;&D-V$8wK22YvE zeYE&(SwiD3NWUoIZ2JaCwu_}P`lUTxw|z>$voPdpg0Zm%6D7!ZyCd>WJ1wsEyudEj zS5R>(kq6~ks!C->QD2V+9-qXdT4q+X5Q8Yi^>1Ww^^rewaIFWM+3u#qt76S*&DU;u zKI|B~IcAEyQ5bh0w|2!ziWUJTPl=~jR|;He5W;;U8c_-m=1nRJl&UDePjK*2yg+_B zU9pW-u0l>{?5!>k&2E5rRVgm_dXH_7PJ*wk$E8R8x4fG+Pw%&C?70+86i6|K97pE? zU~ne@yZ2ExllT7ccfM$vyicWNuHM77XV)e?Cup(IhfT0^5yczTxyxHkLkH!j(ffw| z2MI1VJm+$&S-Q9ahP653pblot$;BmZjx+dkjE(QLTJxS=8~TBU6K|~e@+gW3w?ir zI;OGLWQ1m5+#=ZCipGI}fpjLo2u(}J1u!07Cc!>tz(0-uFbV)rz|qdf4rb@&W%K92 zJJ8d?#@_w5gPW9tx1X8mae!f81?vDHuntGjH{cjRL$^f(0EdVG&~xR2R1EC-2eg5H zjA3_|Vc)E*Jh8g6T3U7%bV$pw$tyq#})OSyXA_WYAlL None: + self.name = name + self.filepath = filename + self.query = Query(query_id, name) + + def with_params(self, params: list[QueryParameter]) -> Query: + """ + Copies the query and adds parameters to it, returning the copy. + """ + # We currently default to the V1 Queries, soon to switch them out. + query_copy = copy(self.query) + query_copy.params = params + return query_copy + + +QUERIES = { + "APP_HASHES": QueryData( + query_id=1610025, name="Unique App Hashes", filename="app_hashes.sql" + ), + "LATEST_APP_HASH_BLOCK": QueryData( + query_id=1615490, + name="Latest Possible App Hash Block", + filename="app_hash_latest_block.sql", + ), +} diff --git a/src/environment.py b/src/environment.py index 3c87b3c1..f78290eb 100644 --- a/src/environment.py +++ b/src/environment.py @@ -2,4 +2,5 @@ from pathlib import Path PROJECT_ROOT = Path(__file__).parent.parent +QUERY_PATH = PROJECT_ROOT / Path("src/sql") LOG_CONFIG_FILE = PROJECT_ROOT / Path("logging.conf") diff --git a/src/fetch/__init__.py b/src/fetch/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/fetch/dune.py b/src/fetch/dune.py new file mode 100644 index 00000000..7565399a --- /dev/null +++ b/src/fetch/dune.py @@ -0,0 +1,85 @@ +""" +All Dune Query executions should be routed through this file. +TODO - Move reusable components into dune-client: + https://github.com/cowprotocol/dune-bridge/issues/40 +""" +import asyncio +import sys + +from dune_client.client import DuneClient +from dune_client.query import Query +from dune_client.types import DuneRecord +from requests import HTTPError + +from src.dune_queries import QUERIES +from src.logger import set_log +from src.models.block_range import BlockRange + +log = set_log(__name__) + + +class DuneFetcher: + """ + Class containing, DuneClient, FileIO and a logger for convenient Dune Fetching. + """ + + def __init__( + self, + api_key: str, + ) -> None: + """ + Class constructor. + Builds DuneClient from `api_key` along with a logger and FileIO object. + """ + self.dune = DuneClient(api_key) + + async def fetch(self, query: Query) -> list[DuneRecord]: + """Async Dune Fetcher with some exception handling.""" + log.debug(f"Executing {query}") + + try: + # Tried to use the AsyncDuneClient, without success: + # https://github.com/cowprotocol/dune-client/pull/31#issuecomment-1316045313 + response = await asyncio.to_thread( + self.dune.refresh, query, ping_frequency=10 + ) + if response.state.is_complete(): + response_rows = response.get_rows() + log.debug( + f"Got {len(response_rows)} results for execution {response.execution_id}" + ) + return response_rows + + message = ( + f"query execution {response.execution_id} incomplete {response.state}" + ) + log.error(message) + raise RuntimeError(f"no results for {message}") + except HTTPError as err: + log.error(f"Got {err} - Exiting") + sys.exit() + + async def latest_app_hash_block(self) -> int: + """ + Block Range is used to app hash fetcher where to find the new records. + block_from: read from file `fname` as a loaded singleton. + - uses genesis block is no file exists (should only ever happen once) + - raises RuntimeError if column specified does not exist. + block_to: fetched from Dune as the last indexed block for "GPv2Settlement_call_settle" + """ + return int( + # KeyError here means the query has been modified and column no longer exists + # IndexError means no results were returned from query (which is unlikely). + (await self.fetch(QUERIES["LATEST_APP_HASH_BLOCK"].query))[0][ + "latest_block" + ] + ) + + async def get_app_hashes(self, block_range: BlockRange) -> list[DuneRecord]: + """ + Executes APP_HASHES query for the given `block_range` and returns the results + """ + app_hash_query = QUERIES["APP_HASHES"].with_params( + block_range.as_query_params() + ) + return await self.fetch(app_hash_query) diff --git a/src/fetch/ipfs.py b/src/fetch/ipfs.py new file mode 100644 index 00000000..2f7566e8 --- /dev/null +++ b/src/fetch/ipfs.py @@ -0,0 +1,154 @@ +"""IPFS CID (de)serialization""" +from __future__ import annotations + +import asyncio +from typing import Any, Optional + +import aiohttp +import requests +from aiohttp import ClientSession +from multiformats_cid.cid import from_bytes + +from src.logger import set_log +from src.models.app_data_content import FoundContent, NotFoundContent + +log = set_log(__name__) + +OLD_PREFIX = bytearray([1, 112, 18, 32]) +# https://github.com/cowprotocol/services/blob/2db800aa38824e32fb542c9e2387d77ca6349676/crates/app-data-hash/src/lib.rs#L41-L44 +NEW_PREFIX = bytearray([1, 0x55, 0x1B, 32]) + + +class Cid: + """Holds logic for constructing and converting various representations of a Delegation ID""" + + def __init__(self, hex_str: str, prefix: bytearray = NEW_PREFIX) -> None: + """Builds Object (bytes as base representation) from hex string.""" + stripped_hex = hex_str.replace("0x", "") + # Anatomy of a CID: https://proto.school/anatomy-of-a-cid/04 + self.bytes = bytes(prefix + bytes.fromhex(stripped_hex)) + + @classmethod + def old_schema(cls, hex_str: str) -> Cid: + """Constructor of old CID format (with different prefix)""" + return cls(hex_str, OLD_PREFIX) + + @property + def hex(self) -> str: + """Returns hex representation""" + without_prefix = self.bytes[4:] + return "0x" + without_prefix.hex() + + def __str__(self) -> str: + """Returns string representation""" + return str(from_bytes(self.bytes)) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Cid): + return False + return self.bytes == other.bytes + + def url(self) -> str: + """IPFS URL where content can be recovered""" + return f"https://ipfs.cow.fi/ipfs/{self}" + + def get_content(self, access_token: str, max_retries: int = 3) -> Optional[Any]: + """ + Attempts to fetch content at cid with a timeout of 1 second. + Trys `max_retries` times and otherwise returns None` + """ + attempts = 0 + while attempts < max_retries: + try: + response = requests.get( + self.url(), + timeout=1, + headers={"x-pinata-gateway-token": access_token}, + ) + return response.json() + except requests.exceptions.ReadTimeout: + attempts += 1 + except requests.exceptions.JSONDecodeError as err: + attempts += 1 + log.warning(f"unexpected error {err} retrying...") + return None + + @classmethod + async def fetch_many( # pylint: disable=too-many-locals + cls, missing_rows: list[dict[str, str]], access_token: str, max_retries: int = 3 + ) -> tuple[list[FoundContent], list[NotFoundContent]]: + """Async AppData Fetching""" + found, not_found = [], [] + async with aiohttp.ClientSession( + headers={"x-pinata-gateway-token": access_token} + ) as session: + while missing_rows: + row = missing_rows.pop() + app_hash = row["app_hash"] + + previous_attempts = int(row.get("attempts", 0)) + cid = cls(app_hash) + + first_seen_block = int(row["first_seen_block"]) + result = await cid.fetch_content( + max_retries, previous_attempts, session, first_seen_block + ) + if isinstance(result, NotFoundContent): + # Try Fetching the old format + result = await cls.old_schema(app_hash).fetch_content( + max_retries, previous_attempts, session, first_seen_block + ) + + if isinstance(result, FoundContent): + found.append(result) + else: + assert isinstance(result, NotFoundContent) + not_found.append(result) + + return found, not_found + + async def fetch_content( + self, + max_retries: int, + previous_attempts: int, + session: ClientSession, + first_seen_block: int, + ) -> FoundContent | NotFoundContent: + """Asynchronous content fetching""" + attempts = 0 + while attempts < max_retries: + try: + async with session.get(self.url(), timeout=1) as response: + content = await response.json() + if previous_attempts: + log.debug( + f"Found previously missing content hash {self.hex} at CID {self}" + ) + else: + log.debug( + f"Found content for {self.hex} at CID {self} ({attempts + 1} trys)" + ) + return FoundContent( + app_hash=self.hex, + first_seen_block=first_seen_block, + content=content, + ) + except asyncio.TimeoutError: + attempts += 1 + except aiohttp.ContentTypeError as err: + log.warning(f"failed to parse response {response} with {err}") + attempts += 1 + + # Content Not Found. + total_attempts = previous_attempts + max_retries + base_message = f"no content found for {self.hex} at CID {self} after" + if previous_attempts: + log.debug(f"still {base_message} {total_attempts} attempts") + else: + log.debug(f"{base_message} {max_retries} retries") + + return NotFoundContent( + app_hash=self.hex, + first_seen_block=first_seen_block, + attempts=total_attempts, + ) diff --git a/src/fetch/orderbook.py b/src/fetch/orderbook.py new file mode 100644 index 00000000..06f65603 --- /dev/null +++ b/src/fetch/orderbook.py @@ -0,0 +1,111 @@ +"""Basic client for connecting to postgres database with login credentials""" +from __future__ import annotations + +import os +from dataclasses import dataclass +from enum import Enum +from typing import Optional + +import pandas as pd +from dotenv import load_dotenv +from pandas import DataFrame +from sqlalchemy import create_engine +from sqlalchemy.engine import Engine + +from src.models.block_range import BlockRange +from src.utils import open_query + +REORG_THRESHOLD = 65 + + +class OrderbookEnv(Enum): + """ + Enum for distinguishing between CoW Protocol's staging and production environment + """ + + BARN = "BARN" + PROD = "PROD" + + def __str__(self) -> str: + return str(self.value) + + +@dataclass +class OrderbookFetcher: + """ + A pair of Dataframes primarily intended to store query results + from production and staging orderbook databases + """ + + @staticmethod + def _pg_engine(db_env: OrderbookEnv) -> Engine: + """Returns a connection to postgres database""" + load_dotenv() + db_url = os.environ[f"{db_env}_DB_URL"] + db_string = f"postgresql+psycopg2://{db_url}" + return create_engine(db_string) + + @classmethod + def _read_query_for_env( + cls, query: str, env: OrderbookEnv, data_types: Optional[dict[str, str]] = None + ) -> DataFrame: + return pd.read_sql_query(query, con=cls._pg_engine(env), dtype=data_types) + + @classmethod + def _query_both_dbs( + cls, query: str, data_types: Optional[dict[str, str]] = None + ) -> tuple[DataFrame, DataFrame]: + barn = cls._read_query_for_env(query, OrderbookEnv.BARN, data_types) + prod = cls._read_query_for_env(query, OrderbookEnv.PROD, data_types) + return barn, prod + + @classmethod + def get_latest_block(cls) -> int: + """ + Fetches the latest mutually synced block from orderbook databases (with REORG protection) + """ + data_types = {"latest": "int64"} + barn, prod = cls._query_both_dbs( + open_query("orderbook/latest_block.sql"), data_types + ) + assert len(barn) == 1 == len(prod), "Expecting single record" + return min(int(barn["latest"][0]), int(prod["latest"][0])) - REORG_THRESHOLD + + @classmethod + def get_order_rewards(cls, block_range: BlockRange) -> DataFrame: + """ + Fetches and validates Order Reward DataFrame as concatenation from Prod and Staging DB + """ + cow_reward_query = ( + open_query("orderbook/order_rewards.sql") + .replace("{{start_block}}", str(block_range.block_from)) + .replace("{{end_block}}", str(block_range.block_to)) + ) + data_types = {"block_number": "int64", "amount": "float64"} + barn, prod = cls._query_both_dbs(cow_reward_query, data_types) + + # Solvers do not appear in both environments! + assert set(prod.solver).isdisjoint(set(barn.solver)), "solver overlap!" + return pd.concat([prod, barn]) + + @classmethod + def get_batch_rewards(cls, block_range: BlockRange) -> DataFrame: + """ + Fetches and validates Batch Rewards DataFrame as concatenation from Prod and Staging DB + """ + cow_reward_query = ( + open_query("orderbook/batch_rewards.sql") + .replace("{{start_block}}", str(block_range.block_from)) + .replace("{{end_block}}", str(block_range.block_to)) + ) + data_types = { + # According to this: https://stackoverflow.com/a/11548224 + # capitalized int64 means `Optional` and it appears to work. + "block_number": "Int64", + "block_deadline": "int64", + } + barn, prod = cls._query_both_dbs(cow_reward_query, data_types) + + # Solvers do not appear in both environments! + assert set(prod.solver).isdisjoint(set(barn.solver)), "solver overlap!" + return pd.concat([prod, barn]) diff --git a/src/fetch/postgres.py b/src/fetch/postgres.py new file mode 100644 index 00000000..3364c519 --- /dev/null +++ b/src/fetch/postgres.py @@ -0,0 +1,54 @@ +"""Generic Postgres Adapter for executing queries on a postgres DB.""" +from dataclasses import dataclass +from typing import Optional + +import pandas as pd +from pandas import DataFrame +from sqlalchemy import create_engine +from sqlalchemy.engine import Engine + +from src.fetch.orderbook import REORG_THRESHOLD +from src.models.block_range import BlockRange +from src.utils import open_query + + +@dataclass +class PostgresFetcher: + """ + Basic Postgres interface + """ + + engine: Engine + + def __init__(self, db_url: str): + db_string = f"postgresql+psycopg2://{db_url}" + + self.engine = create_engine(db_string) + + def _read_query( + self, query: str, data_types: Optional[dict[str, str]] = None + ) -> DataFrame: + return pd.read_sql_query(query, con=self.engine, dtype=data_types) + + def get_latest_block(self) -> int: + """ + Fetches the latest mutually synced block from orderbook databases (with REORG protection) + """ + data_types = {"latest": "int64"} + res = self._read_query(open_query("warehouse/latest_block.sql"), data_types) + assert len(res) == 1, "Expecting single record" + return int(res["latest"][0]) - REORG_THRESHOLD + + def get_internal_imbalances(self, block_range: BlockRange) -> DataFrame: + """ + Fetches and validates Internal Token Imbalances + """ + cow_reward_query = ( + open_query("warehouse/token_imbalances.sql") + .replace("{{start_block}}", str(block_range.block_from)) + .replace("{{end_block}}", str(block_range.block_to)) + ) + data_types = { + "block_number": "int64", + } + return self._read_query(cow_reward_query, data_types) diff --git a/src/main.py b/src/main.py new file mode 100644 index 00000000..834ca3c1 --- /dev/null +++ b/src/main.py @@ -0,0 +1,95 @@ +"""Main Entry point for app_hash sync""" +import argparse +import asyncio +import os +from dataclasses import dataclass +from pathlib import Path + +from dotenv import load_dotenv + +from src.fetch.dune import DuneFetcher +from src.fetch.orderbook import OrderbookFetcher +from src.fetch.postgres import PostgresFetcher +from src.logger import set_log +from src.models.tables import SyncTable +from src.post.aws import AWSClient +from src.sync import sync_app_data +from src.sync.config import SyncConfig, AppDataSyncConfig +from src.sync.order_rewards import sync_order_rewards, sync_batch_rewards +from src.sync.token_imbalance import sync_internal_imbalance + +log = set_log(__name__) + + +@dataclass +class ScriptArgs: + """Runtime arguments' parser/initializer""" + + dry_run: bool + sync_table: SyncTable + + def __init__(self) -> None: + parser = argparse.ArgumentParser("Dune Community Sources Sync") + parser.add_argument( + "--sync-table", + type=SyncTable, + required=True, + choices=list(SyncTable), + ) + parser.add_argument( + "--dry-run", + type=bool, + help="Flag indicating whether script should not post files to AWS or not", + default=False, + ) + arguments, _ = parser.parse_known_args() + self.sync_table: SyncTable = arguments.sync_table + self.dry_run: bool = arguments.dry_run + + +if __name__ == "__main__": + load_dotenv() + volume_path = Path(os.environ["VOLUME_PATH"]) + args = ScriptArgs() + aws = AWSClient.new_from_environment() + + if args.sync_table == SyncTable.APP_DATA: + asyncio.run( + sync_app_data( + aws, + dune=DuneFetcher(os.environ["DUNE_API_KEY"]), + config=AppDataSyncConfig( + volume_path=volume_path, + missing_files_name="missing_app_hashes.json", + max_retries=int(os.environ.get("APP_DATA_MAX_RETRIES", 3)), + give_up_threshold=int( + os.environ.get("APP_DATA_GIVE_UP_THRESHOLD", 100) + ), + ), + ipfs_access_key=os.environ["IPFS_ACCESS_KEY"], + dry_run=args.dry_run, + ) + ) + elif args.sync_table == SyncTable.ORDER_REWARDS: + sync_order_rewards( + aws, + config=SyncConfig(volume_path), + fetcher=OrderbookFetcher(), + dry_run=args.dry_run, + ) + elif args.sync_table == SyncTable.BATCH_REWARDS: + sync_batch_rewards( + aws, + config=SyncConfig(volume_path), + fetcher=OrderbookFetcher(), + dry_run=args.dry_run, + ) + elif args.sync_table == SyncTable.INTERNAL_IMBALANCE: + sync_internal_imbalance( + aws, + config=SyncConfig(volume_path), + fetcher=PostgresFetcher(os.environ["WAREHOUSE_URL"]), + dry_run=args.dry_run, + ) + else: + log.error(f"unsupported sync_table '{args.sync_table}'") diff --git a/src/models/__init__.py b/src/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/models/app_data_content.py b/src/models/app_data_content.py new file mode 100644 index 00000000..20c3055a --- /dev/null +++ b/src/models/app_data_content.py @@ -0,0 +1,59 @@ +"""Models for Found and Not Found App Data Content. Also, responsible for type conversion""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + + +@dataclass +class FoundContent: + """Representation of AppData with Content""" + + app_hash: str + first_seen_block: int + content: dict[str, Any] + + @classmethod + def from_dict(cls, row: dict[str, Any]) -> FoundContent: + """Constructor from dictionary""" + return cls( + app_hash=row["app_hash"], + first_seen_block=int(row["first_seen_block"]), + content=row["content"], + ) + + def as_dune_record(self) -> dict[str, Any]: + """Converts to DuneRecord type""" + return { + "app_hash": self.app_hash, + "first_seen_block": self.first_seen_block, + "content": self.content, + } + + +@dataclass +class NotFoundContent: + """ + Representation of AppData with unknown content. + Records also number of attempts made to recover the content""" + + app_hash: str + first_seen_block: int + attempts: int + + @classmethod + def from_dict(cls, row: dict[str, Any]) -> NotFoundContent: + """Constructor from dictionary""" + return cls( + app_hash=row["app_hash"], + first_seen_block=int(row["first_seen_block"]), + attempts=int(row["attempts"]), + ) + + def as_dune_record(self) -> dict[str, Any]: + """Converts to DuneRecord type""" + return { + "app_hash": self.app_hash, + "first_seen_block": self.first_seen_block, + "attempts": self.attempts, + } diff --git a/src/models/batch_rewards_schema.py b/src/models/batch_rewards_schema.py new file mode 100644 index 00000000..a3bc5bab --- /dev/null +++ b/src/models/batch_rewards_schema.py @@ -0,0 +1,41 @@ +"""Model for Batch Rewards Data""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +import pandas +from pandas import DataFrame + + +@dataclass +class BatchRewards: + """ + This class provides a transformation interface for the Dataframe we fetch from the orderbook + """ + + @classmethod + def from_pdf_to_dune_records(cls, rewards_df: DataFrame) -> list[dict[str, Any]]: + """Converts Pandas DataFrame into the expected stream type for Dune""" + return [ + { + "block_number": int(row["block_number"]) + if not pandas.isna(row["block_number"]) + else None, + "tx_hash": row["tx_hash"], + "solver": row["solver"], + "block_deadline": int(row["block_deadline"]), + "data": { + # All the following values are in WEI. + "uncapped_payment_eth": int(row["uncapped_payment_eth"]), + "capped_payment": int(row["capped_payment"]), + "execution_cost": int(row["execution_cost"]), + "surplus": int(row["surplus"]), + "fee": int(row["fee"]), + "winning_score": int(row["winning_score"]), + "reference_score": int(row["reference_score"]), + "participating_solvers": row["participating_solvers"], + }, + } + for row in rewards_df.to_dict(orient="records") + ] diff --git a/src/models/block_range.py b/src/models/block_range.py new file mode 100644 index 00000000..25f7a89b --- /dev/null +++ b/src/models/block_range.py @@ -0,0 +1,31 @@ +""" +BlockRange Model is just a data class for left and right bounds +""" +from dataclasses import dataclass + +from dune_client.types import QueryParameter + + +@dataclass +class BlockRange: + """ + Basic dataclass for an Ethereum block range with some Dune compatibility methods. + TODO (easy) - this data class could probably live in dune-client. + https://github.com/cowprotocol/dune-bridge/issues/40 + """ + + block_from: int + block_to: int + + def __str__(self) -> str: + return f"BlockRange(from={self.block_from}, to={self.block_to})" + + def __repr__(self) -> str: + return str(self) + + def as_query_params(self) -> list[QueryParameter]: + """Returns self as Dune QueryParameters""" + return [ + QueryParameter.number_type("BlockFrom", self.block_from), + QueryParameter.number_type("BlockTo", self.block_to), + ] diff --git a/src/models/order_rewards_schema.py b/src/models/order_rewards_schema.py new file mode 100644 index 00000000..2b7734a2 --- /dev/null +++ b/src/models/order_rewards_schema.py @@ -0,0 +1,31 @@ +"""Model for Order Rewards Data""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from pandas import DataFrame + + +@dataclass +class OrderRewards: + """ + This class provides a transformation interface for the Dataframe we fetch from the orderbook + """ + + @classmethod + def from_pdf_to_dune_records(cls, rewards_df: DataFrame) -> list[dict[str, Any]]: + """Converts Pandas DataFrame into the expected stream type for Dune""" + return [ + { + "block_number": int(row["block_number"]), + "order_uid": row["order_uid"], + "tx_hash": row["tx_hash"], + "solver": row["solver"], + "data": { + "surplus_fee": str(row["surplus_fee"]), + "amount": float(row["amount"]), + }, + } + for row in rewards_df.to_dict(orient="records") + ] diff --git a/src/models/tables.py b/src/models/tables.py new file mode 100644 index 00000000..147f217a --- /dev/null +++ b/src/models/tables.py @@ -0,0 +1,19 @@ +"""Data structure containing the supported sync tables""" +from enum import Enum + + +class SyncTable(Enum): + """Enum for Deployment Supported Table Sync""" + + APP_DATA = "app_data" + ORDER_REWARDS = "order_rewards" + BATCH_REWARDS = "batch_rewards" + INTERNAL_IMBALANCE = "internal_imbalance" + + def __str__(self) -> str: + return str(self.value) + + @staticmethod + def supported_tables() -> list[str]: + """Returns a list of supported tables (i.e. valid object contructors).""" + return [str(t) for t in list(SyncTable)] diff --git a/src/models/token_imbalance_schema.py b/src/models/token_imbalance_schema.py new file mode 100644 index 00000000..e7053cd4 --- /dev/null +++ b/src/models/token_imbalance_schema.py @@ -0,0 +1,27 @@ +"""Model for Batch Rewards Data""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from pandas import DataFrame + + +@dataclass +class TokenImbalance: + """ + This class provides a transformation interface (to JSON) for Dataframe + """ + + @classmethod + def from_pdf_to_dune_records(cls, frame: DataFrame) -> list[dict[str, Any]]: + """Converts Pandas DataFrame into the expected stream type for Dune""" + return [ + { + "block_number": int(row["block_number"]), + "tx_hash": row["tx_hash"], + "token": row["token"], + "amount": str(row["amount"]), + } + for row in frame.to_dict(orient="records") + ] diff --git a/src/post/__init__.py b/src/post/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/aws.py b/src/post/aws.py similarity index 82% rename from src/aws.py rename to src/post/aws.py index f9cb08e5..3e8c455e 100644 --- a/src/aws.py +++ b/src/post/aws.py @@ -1,7 +1,6 @@ """Aws S3 Bucket functionality (namely upload_file)""" from __future__ import annotations -import json import os from collections import defaultdict from dataclasses import dataclass @@ -14,7 +13,7 @@ from dotenv import load_dotenv from src.logger import set_log -from src.text_io import BytesIteratorIO +from src.models.tables import SyncTable log = set_log(__name__) @@ -79,17 +78,21 @@ def from_bucket_collection(cls, bucket_objects: Any) -> BucketStructure: object_key = bucket_obj.key path, _ = object_key.split("/") grouped_files[path].append(BucketFileObject.from_key(object_key)) + if path not in SyncTable.supported_tables(): + # Catches any unrecognized filepath. + log.warning(f"Found unexpected file {object_key}") log.debug(f"loaded bucket filesystem: {grouped_files.keys()}") return cls(files=grouped_files) - def get(self, table: str) -> list[BucketFileObject]: + def get(self, table: SyncTable | str) -> list[BucketFileObject]: """ Returns the list of files under `table` - returns empty list if none available. """ - return self.files.get(table, []) + table_str = str(table) if isinstance(table, SyncTable) else table + return self.files.get(table_str, []) class AWSClient: @@ -128,22 +131,16 @@ def _assume_role(self) -> ServiceResource: """ sts_client = boto3.client("sts") - # TODO - assume that the internal role is already assumed. and use get session_token - # sts_client.get_session_token() internal_assumed_role_object = sts_client.assume_role( RoleArn=self.internal_role, RoleSessionName="InternalSession", ) credentials = internal_assumed_role_object["Credentials"] - # sts_client.get_session_token() - sts_client = boto3.client( "sts", - aws_access_key_id=credentials["AccessKeyId"], # AWS_ACCESS_KEY_ID - aws_secret_access_key=credentials[ - "SecretAccessKey" - ], # AWS_SECRET_ACCESS_KEY - aws_session_token=credentials["SessionToken"], # AWS_SESSION_TOKEN + aws_access_key_id=credentials["AccessKeyId"], + aws_secret_access_key=credentials["SecretAccessKey"], + aws_session_token=credentials["SessionToken"], ) external_assumed_role_object = sts_client.assume_role( @@ -179,30 +176,6 @@ def upload_file(self, filename: str, object_key: str) -> bool: log.debug(f"uploaded {filename} to {self.bucket}") return True - def put_object(self, data_set: list[dict[str, Any]], object_key: str) -> bool: - """Upload a file to an S3 bucket - - :param data_list: Data to upload. Should be a full path to file. - :param object_key: S3 object key. For our purposes, this would - be f"{table_name}/cow_{latest_block_number}.json" - :return: True if file was uploaded, else raises - """ - - file_object = BytesIteratorIO( - f"{json.dumps(row)}\n".encode("utf-8") for row in data_set - ) - - s3_client = self._get_s3_client(self._assume_role()) - - s3_client.upload_fileobj( - file_object, - bucket=self.bucket, - key=object_key, - extra_args={"ACL": "bucket-owner-full-control"}, - ) - log.debug(f"uploaded {object_key} to {self.bucket}") - return True - def delete_file(self, object_key: str) -> bool: """Delete a file from an S3 bucket @@ -210,6 +183,7 @@ def delete_file(self, object_key: str) -> bool: be f"{table_name}/cow_{latest_block_number}.json" :return: True if file was deleted, else raises """ + # TODO - types! error: "BaseClient" has no attribute "delete_object" s3_client = self._get_s3_client(self._assume_role()) s3_client.delete_object( # type: ignore Bucket=self.bucket, @@ -246,13 +220,14 @@ def existing_files(self) -> BucketStructure: bucket_objects = bucket.objects.all() return BucketStructure.from_bucket_collection(bucket_objects) - def last_sync_block(self, table: str) -> int: + def last_sync_block(self, table: SyncTable | str) -> int: """ Based on the existing bucket files, the last sync block is uniquely determined from the file names. """ + table_str = str(table) if isinstance(table, SyncTable) else table try: - table_files = self.existing_files().get(table) + table_files = self.existing_files().get(table_str) return max(file_obj.block for file_obj in table_files if file_obj.block) except ValueError as err: # Raised when table_files = [] @@ -260,7 +235,7 @@ def last_sync_block(self, table: str) -> int: f"Could not determine last sync block for {table} files. No files." ) from err - def delete_all(self, table: str) -> None: + def delete_all(self, table: SyncTable | str) -> None: """Deletes all files within the supported tables directory""" log.info(f"Emptying Bucket {table}") try: @@ -270,4 +245,6 @@ def delete_all(self, table: str) -> None: log.info(f"Deleting file {file_data.object_key}") self.delete_file(file_data.object_key) except KeyError as err: - raise ValueError(f"invalid table name {table}") from err + raise ValueError( + f"Invalid table_name {table}, please chose from {SyncTable.supported_tables()}" + ) from err diff --git a/src/record_handler.py b/src/record_handler.py deleted file mode 100644 index 718e6252..00000000 --- a/src/record_handler.py +++ /dev/null @@ -1,107 +0,0 @@ -""" -Abstraction for New Content Handling -provides a framework for writing new content to disk and posting to AWS -""" -import sys -from typing import Any - -from s3transfer import S3UploadFailedError -from src.aws import AWSClient - -from src.logger import set_log - -log = set_log(__name__) - - -def last_sync_block(aws: AWSClient, table: str, genesis_block: int = 0) -> int: - """Attempts to get last sync block from AWS Bucket files, otherwise uses genesis""" - try: - block_from = aws.last_sync_block(table) - except FileNotFoundError: - log.warning( - f"last sync could not be evaluated from AWS, using genesis block {genesis_block}" - ) - block_from = genesis_block - - return block_from - - -class RecordHandler: - - """ - This class is responsible for consuming new dune records and missing values from previous runs - it attempts to fetch content for them and filters them into "found" and "not found" as necessary - """ - - def __init__( - self, - file_index: int, - file_prefix: str, - table: str, - data_set: list[dict[str, Any]], - ): - self.file_index = file_index - self.file_prefix = file_prefix - self.table = table - self.data_set = data_set - - def num_records(self) -> int: - """Returns number of records to handle""" - return len(self.data_set) - - @property - def content_filename(self) -> str: - """returns filename""" - return f"{self.file_prefix}_{self.file_index}.json" - - @property - def object_key(self) -> str: - """returns object key""" - return f"{self.table}/{self.content_filename}" - - def _aws_login_and_upload( - self, aws: AWSClient, data_set: list[dict[str, Any]] - ) -> bool: - """Creates AWS client session and attempts to upload file""" - try: - return aws.put_object( - data_set, - object_key=self.object_key, - ) - except S3UploadFailedError as err: - log.error(err) - sys.exit(1) - - def write_and_upload_content(self, aws: AWSClient, dry_run: bool) -> None: - """ - - Writes record handlers content to persistent volume, - - attempts to upload to AWS and - - records last sync block on volume. - When dryrun flag is enabled, does not upload to IPFS. - """ - count = self.num_records() - if count > 0: - log.info( - f"posting {count} new {self.table} records for file index {self.file_index}" - ) - if dry_run: - log.info("DRY-RUN-ENABLED: new records not posted to AWS.") - else: - try: - aws.put_object( - data_set=self.data_set, - object_key=self.object_key, - ) - log.info( - f"{self.table} sync for file index {self.file_index} complete: " - f"synced {count} records" - ) - return - except S3UploadFailedError as err: - log.error(err) - sys.exit(1) - - else: - log.info( - f"No new {self.table} for file index {self.file_index}: no sync necessary" - ) diff --git a/src/scripts/__init__.py b/src/scripts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/scripts/download_file.py b/src/scripts/download_file.py new file mode 100644 index 00000000..66100521 --- /dev/null +++ b/src/scripts/download_file.py @@ -0,0 +1,26 @@ +"""Downloads file from AWS to PWD""" +import argparse + +from dotenv import load_dotenv + +from src.post.aws import AWSClient + + +def download_file(aws: AWSClient, object_key: str) -> None: + """Download file (`object_key`) from AWS""" + aws.download_file( + filename=object_key.split("/")[1], + object_key=object_key, + ) + + +if __name__ == "__main__": + load_dotenv() + parser = argparse.ArgumentParser("Download File from Bucket") + parser.add_argument( + "filename", + type=str, + required=True, + ) + args, _ = parser.parse_known_args() + download_file(aws=AWSClient.new_from_environment(), object_key=args.object_key) diff --git a/src/scripts/empty_bucket.py b/src/scripts/empty_bucket.py new file mode 100644 index 00000000..b36a4b56 --- /dev/null +++ b/src/scripts/empty_bucket.py @@ -0,0 +1,33 @@ +""" +Script to empty AWS bucket. +Used for re-deployments involving schema change. +""" +import os +import shutil +from pathlib import Path + +from dotenv import load_dotenv + +from src.main import ScriptArgs +from src.models.tables import SyncTable +from src.post.aws import AWSClient + + +def empty_bucket(table: SyncTable, aws: AWSClient, volume_path: Path) -> None: + """ + Empties the bucket for `table` + and deletes backup from mounted volume + """ + # Drop Data from AWS bucket + aws.delete_all(table) + # drop backup data from volume path + shutil.rmtree(volume_path / str(table)) + + +if __name__ == "__main__": + load_dotenv() + empty_bucket( + table=ScriptArgs().sync_table, + aws=AWSClient.new_from_environment(), + volume_path=Path(os.environ["VOLUME_PATH"]), + ) diff --git a/src/sql/app_hash_latest_block.sql b/src/sql/app_hash_latest_block.sql new file mode 100644 index 00000000..571fce55 --- /dev/null +++ b/src/sql/app_hash_latest_block.sql @@ -0,0 +1,4 @@ +-- https://dune.com/queries/1615490 +select + max(call_block_number) as latest_block +from gnosis_protocol_v2_ethereum.GPv2Settlement_call_settle \ No newline at end of file diff --git a/src/sql/app_hashes.sql b/src/sql/app_hashes.sql new file mode 100644 index 00000000..daef7452 --- /dev/null +++ b/src/sql/app_hashes.sql @@ -0,0 +1,21 @@ +-- App Hashes: https://dune.com/queries/1610025 +-- MIN(first_block_seen) = 12153263 +-- Nov 16, 2022: Query takes 4 seconds to run for on full block range +with +app_hashes as ( + select + min(call_block_number) first_seen_block, + get_json_object(trade, '$.appData') as app_hash + from gnosis_protocol_v2_ethereum.GPv2Settlement_call_settle + lateral view explode(trades) as trade + group by app_hash +) +select + app_hash, + first_seen_block +from app_hashes +where first_seen_block > '{{BlockFrom}}' +and first_seen_block <= '{{BlockTo}}' + +-- For some additional stats, +-- on this data see https://dune.com/queries/1608286 \ No newline at end of file diff --git a/src/sql/orderbook/batch_rewards.sql b/src/sql/orderbook/batch_rewards.sql new file mode 100644 index 00000000..affa000b --- /dev/null +++ b/src/sql/orderbook/batch_rewards.sql @@ -0,0 +1,97 @@ +WITH observed_settlements AS (SELECT + -- settlement + tx_hash, + solver, + s.block_number, + -- settlement_observations + effective_gas_price * gas_used AS execution_cost, + surplus, + fee, + -- auction_transaction + at.auction_id + FROM settlement_observations so + JOIN settlements s + ON s.block_number = so.block_number + AND s.log_index = so.log_index + JOIN auction_transaction at + ON s.tx_from = at.tx_from + AND s.tx_nonce = at.tx_nonce + JOIN settlement_scores ss + ON at.auction_id = ss.auction_id + WHERE ss.block_deadline > {{start_block}} + AND ss.block_deadline <= {{end_block}}), + + auction_participation as (SELECT ss.auction_id, + array_agg( + concat('0x', encode(participant, 'hex')) ORDER BY participant + ) as participating_solvers + FROM auction_participants + JOIN settlement_scores ss + ON auction_participants.auction_id = ss.auction_id + WHERE block_deadline > {{start_block}} + AND block_deadline <= {{end_block}} + GROUP BY ss.auction_id), + reward_data AS (SELECT + -- observations + tx_hash, + ss.auction_id, + -- TODO - Assuming that `solver == winner` when both not null + -- We will need to monitor that `solver == winner`! + coalesce(solver, winner) as solver, + block_number as settlement_block, + block_deadline, + case + when block_number is not null and block_number > block_deadline then 0 + else coalesce(execution_cost, 0) end as execution_cost, + case + when block_number is not null and block_number > block_deadline then 0 + else coalesce(surplus, 0) end as surplus, + case + when block_number is not null and block_number > block_deadline then 0 + else coalesce(fee, 0) end as fee, + -- scores + winning_score, + reference_score, + -- auction_participation + participating_solvers + FROM settlement_scores ss + -- If there are reported scores, + -- there will always be a record of auction participants + JOIN auction_participation ap + ON ss.auction_id = ap.auction_id + -- outer joins made in order to capture non-existent settlements. + LEFT OUTER JOIN observed_settlements os + ON os.auction_id = ss.auction_id), + reward_per_auction as (SELECT tx_hash, + settlement_block, + block_deadline, + solver, + execution_cost, + surplus, + fee, + surplus + fee - reference_score as uncapped_payment_eth, + -- Uncapped Reward = CLAMP_[-E, E + exec_cost](uncapped_payment_eth) + LEAST(GREATEST(-10000000000000000, surplus + fee - reference_score), + 10000000000000000 + execution_cost) as capped_payment, + winning_score, + reference_score, + participating_solvers + FROM reward_data) + + +SELECT settlement_block as block_number, + block_deadline, + case + when tx_hash is NULL then NULL + else concat('0x', encode(tx_hash, 'hex')) + end as tx_hash, + concat('0x', encode(solver, 'hex')) as solver, + execution_cost::text as execution_cost, + surplus::text as surplus, + fee::text as fee, + uncapped_payment_eth::text as uncapped_payment_eth, + capped_payment::text as capped_payment, + winning_score::text as winning_score, + reference_score::text as reference_score, + participating_solvers +FROM reward_per_auction diff --git a/src/sql/orderbook/latest_block.sql b/src/sql/orderbook/latest_block.sql new file mode 100644 index 00000000..acc3a5d4 --- /dev/null +++ b/src/sql/orderbook/latest_block.sql @@ -0,0 +1,3 @@ +select min(block_number) latest +from settlements +where tx_from is null; \ No newline at end of file diff --git a/src/sql/orderbook/order_rewards.sql b/src/sql/orderbook/order_rewards.sql new file mode 100644 index 00000000..d80666f5 --- /dev/null +++ b/src/sql/orderbook/order_rewards.sql @@ -0,0 +1,33 @@ +with trade_hashes as (SELECT solver, + block_number, + order_uid, + fee_amount, + settlement.tx_hash, + auction_id + FROM trades t + LEFT OUTER JOIN LATERAL ( + SELECT tx_hash, solver, tx_nonce, tx_from + FROM settlements s + WHERE s.block_number = t.block_number + AND s.log_index > t.log_index + ORDER BY s.log_index ASC + LIMIT 1 + ) AS settlement ON true + join auction_transaction + -- This join also eliminates overlapping + -- trades & settlements between barn and prod DB + on settlement.tx_from = auction_transaction.tx_from + and settlement.tx_nonce = auction_transaction.tx_nonce + where block_number > {{start_block}} and block_number <= {{end_block}}) + +-- Most efficient column order for sorting would be having tx_hash or order_uid first +select block_number, + concat('0x', encode(trade_hashes.order_uid, 'hex')) as order_uid, + concat('0x', encode(solver, 'hex')) as solver, + concat('0x', encode(tx_hash, 'hex')) as tx_hash, + coalesce(surplus_fee, 0)::text as surplus_fee, + coalesce(reward, 0.0) as amount +from trade_hashes + left outer join order_execution o + on trade_hashes.order_uid = o.order_uid + and trade_hashes.auction_id = o.auction_id; diff --git a/src/sql/warehouse/latest_block.sql b/src/sql/warehouse/latest_block.sql new file mode 100644 index 00000000..609534b6 --- /dev/null +++ b/src/sql/warehouse/latest_block.sql @@ -0,0 +1,2 @@ +select max(block_number) as latest +from settlements; diff --git a/src/sql/warehouse/token_imbalances.sql b/src/sql/warehouse/token_imbalances.sql new file mode 100644 index 00000000..0b81f9ba --- /dev/null +++ b/src/sql/warehouse/token_imbalances.sql @@ -0,0 +1,8 @@ +select block_number, + concat('0x', encode(s.tx_hash, 'hex')) as tx_hash, + concat('0x', encode(token, 'hex')) as token, + amount::text as amount +from internalized_imbalances + join settlements s + on internalized_imbalances.tx_hash = s.tx_hash +where block_number > {{start_block}} and block_number <= {{end_block}}; diff --git a/src/sync/__init__.py b/src/sync/__init__.py new file mode 100644 index 00000000..37b723b3 --- /dev/null +++ b/src/sync/__init__.py @@ -0,0 +1,2 @@ +"""Re-exported sync methods.""" +from .app_data import sync_app_data diff --git a/src/sync/app_data.py b/src/sync/app_data.py new file mode 100644 index 00000000..8f5f143d --- /dev/null +++ b/src/sync/app_data.py @@ -0,0 +1,154 @@ +"""Main Entry point for app_hash sync""" + +from dune_client.file.interface import FileIO +from dune_client.types import DuneRecord + +from src.fetch.dune import DuneFetcher +from src.fetch.ipfs import Cid +from src.logger import set_log +from src.models.app_data_content import FoundContent, NotFoundContent +from src.models.block_range import BlockRange +from src.models.tables import SyncTable +from src.post.aws import AWSClient +from src.sync.common import last_sync_block +from src.sync.config import SyncConfig, AppDataSyncConfig +from src.sync.record_handler import RecordHandler +from src.sync.upload_handler import UploadHandler + +log = set_log(__name__) + + +SYNC_TABLE = SyncTable.APP_DATA + + +class AppDataHandler(RecordHandler): # pylint:disable=too-many-instance-attributes + """ + This class is responsible for consuming new dune records and missing values from previous runs + it attempts to fetch content for them and filters them into "found" and "not found" as necessary + """ + + def __init__( # pylint:disable=too-many-arguments + self, + file_manager: FileIO, + new_rows: list[DuneRecord], + block_range: BlockRange, + config: SyncConfig, + ipfs_access_key: str, + missing_file_name: str, + ): + super().__init__(block_range, SYNC_TABLE, config) + self.file_manager = file_manager + self.ipfs_access_key = ipfs_access_key + + self._found: list[FoundContent] = [] + self._not_found: list[NotFoundContent] = [] + + self.new_rows = new_rows + self.missing_file_name = missing_file_name + try: + self.missing_values = self.file_manager.load_ndjson(missing_file_name) + except FileNotFoundError: + self.missing_values = [] + + def num_records(self) -> int: + assert len(self.new_rows) == 0, ( + "this function call is not allowed until self.new_rows have been processed! " + "call fetch_content_and_filter first" + ) + return len(self._found) + + async def _handle_new_records(self, max_retries: int) -> None: + # Drain the dune_results into "found" and "not found" categories + self._found, self._not_found = await Cid.fetch_many( + self.new_rows, self.ipfs_access_key, max_retries + ) + + async def _handle_missing_records( + self, max_retries: int, give_up_threshold: int + ) -> None: + found, not_found = await Cid.fetch_many( + self.missing_values, self.ipfs_access_key, max_retries + ) + while found: + self._found.append(found.pop()) + while not_found: + row = not_found.pop() + app_hash, attempts = row.app_hash, row.attempts + if attempts > give_up_threshold: + log.debug( + f"No content found after {attempts} attempts for {app_hash} assuming NULL." + ) + self._found.append( + FoundContent( + app_hash=app_hash, + first_seen_block=row.first_seen_block, + content={}, + ) + ) + else: + self._not_found.append(row) + + def write_found_content(self) -> None: + assert len(self.new_rows) == 0, "Must call _handle_new_records first!" + self.file_manager.write_ndjson( + data=[x.as_dune_record() for x in self._found], name=self.content_filename + ) + # When not_found is empty, we want to overwrite the file (hence skip_empty=False) + # This happens when number of attempts exceeds GIVE_UP_THRESHOLD + self.file_manager.write_ndjson( + data=[x.as_dune_record() for x in self._not_found], + name=self.missing_file_name, + skip_empty=False, + ) + + def write_sync_data(self) -> None: + # Only write these if upload was successful. + self.file_manager.write_csv( + data=[{self.config.sync_column: str(self.block_range.block_to)}], + name=self.config.sync_file, + ) + + async def fetch_content_and_filter( + self, max_retries: int, give_up_threshold: int + ) -> None: + """ + Run loop fetching app_data for hashes, + separates into (found and not found), returning the pair. + """ + await self._handle_new_records(max_retries) + log.info( + f"Attempting to recover missing {len(self.missing_values)} records from previous run" + ) + await self._handle_missing_records(max_retries, give_up_threshold) + + +async def sync_app_data( + aws: AWSClient, + dune: DuneFetcher, + config: AppDataSyncConfig, + ipfs_access_key: str, + dry_run: bool, +) -> None: + """App Data Sync Logic""" + block_range = BlockRange( + block_from=last_sync_block( + aws, + table=SYNC_TABLE, + genesis_block=12153262, # First App Hash Block + ), + block_to=await dune.latest_app_hash_block(), + ) + + data_handler = AppDataHandler( + file_manager=FileIO(config.volume_path / str(SYNC_TABLE)), + new_rows=await dune.get_app_hashes(block_range), + block_range=block_range, + config=config, + ipfs_access_key=ipfs_access_key, + missing_file_name=config.missing_files_name, + ) + await data_handler.fetch_content_and_filter( + max_retries=config.max_retries, give_up_threshold=config.give_up_threshold + ) + UploadHandler(aws, data_handler, table=SYNC_TABLE).write_and_upload_content(dry_run) + log.info("app_data sync run completed successfully") diff --git a/src/sync/common.py b/src/sync/common.py new file mode 100644 index 00000000..c3a11195 --- /dev/null +++ b/src/sync/common.py @@ -0,0 +1,20 @@ +"""Shared methods between both sync scripts.""" + +from src.logger import set_log +from src.models.tables import SyncTable +from src.post.aws import AWSClient + +log = set_log(__name__) + + +def last_sync_block(aws: AWSClient, table: SyncTable, genesis_block: int = 0) -> int: + """Attempts to get last sync block from AWS Bucket files, otherwise uses genesis""" + try: + block_from = aws.last_sync_block(table) + except FileNotFoundError: + log.warning( + f"last sync could not be evaluated from AWS, using genesis block {genesis_block}" + ) + block_from = genesis_block + + return block_from diff --git a/src/sync/config.py b/src/sync/config.py new file mode 100644 index 00000000..bbd663c6 --- /dev/null +++ b/src/sync/config.py @@ -0,0 +1,30 @@ +"""Configuration details for sync jobs""" +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path + + +@dataclass +class SyncConfig: + """ + This data class contains all the credentials and volume paths + required to sync with both a persistent volume and Dune's S3 Buckets. + """ + + volume_path: Path + # File System + sync_file: str = "sync_block.csv" + sync_column: str = "last_synced_block" + + +@dataclass +class AppDataSyncConfig(SyncConfig): + """Additional data field for app data sync.""" + + # Maximum number of retries on a single run + max_retries: int = 3 + # Total number of accumulated attempts before we assume no content + give_up_threshold: int = 100 + # Persisted file where we store the missing results and number of attempts. + missing_files_name: str = "missing_app_hashes.json" diff --git a/src/sync/order_rewards.py b/src/sync/order_rewards.py new file mode 100644 index 00000000..5c2e2596 --- /dev/null +++ b/src/sync/order_rewards.py @@ -0,0 +1,125 @@ +"""Main Entry point for app_hash sync""" +from typing import Any + +from dune_client.file.interface import FileIO + +from src.fetch.orderbook import OrderbookFetcher +from src.logger import set_log +from src.models.batch_rewards_schema import BatchRewards +from src.models.block_range import BlockRange +from src.models.order_rewards_schema import OrderRewards +from src.models.tables import SyncTable +from src.post.aws import AWSClient +from src.sync.common import last_sync_block +from src.sync.config import SyncConfig +from src.sync.record_handler import RecordHandler +from src.sync.upload_handler import UploadHandler + +log = set_log(__name__) + + +class OrderbookDataHandler( + RecordHandler +): # pylint:disable=too-few-public-methods,too-many-arguments + """ + This class is responsible for consuming new dune records and missing values from previous runs + it attempts to fetch content for them and filters them into "found" and "not found" as necessary + """ + + def __init__( + self, + file_manager: FileIO, + block_range: BlockRange, + sync_table: SyncTable, + config: SyncConfig, + data_list: list[dict[str, Any]], + ): + super().__init__(block_range, sync_table, config) + log.info(f"Handling {len(data_list)} new records") + self.file_manager = file_manager + self.data_list = data_list + + def num_records(self) -> int: + return len(self.data_list) + + def write_found_content(self) -> None: + self.file_manager.write_ndjson(data=self.data_list, name=self.content_filename) + + def write_sync_data(self) -> None: + # Only write these if upload was successful. + self.file_manager.write_csv( + data=[{self.config.sync_column: str(self.block_range.block_to)}], + name=self.config.sync_file, + ) + + +def sync_order_rewards( + aws: AWSClient, fetcher: OrderbookFetcher, config: SyncConfig, dry_run: bool +) -> None: + """Order Rewards Data Sync Logic""" + sync_table = SyncTable.ORDER_REWARDS + block_range = BlockRange( + block_from=last_sync_block( + aws, + table=sync_table, + genesis_block=15719994, # First Recorded Order Reward block + ), + block_to=fetcher.get_latest_block(), + ) + sync_orderbook_data( + aws, + block_range, + config, + dry_run, + sync_table=sync_table, + data_list=OrderRewards.from_pdf_to_dune_records( + fetcher.get_order_rewards(block_range) + ), + ) + + +def sync_batch_rewards( + aws: AWSClient, fetcher: OrderbookFetcher, config: SyncConfig, dry_run: bool +) -> None: + """Batch Reward Sync Logic""" + sync_table = SyncTable.BATCH_REWARDS + block_range = BlockRange( + block_from=last_sync_block( + aws, + table=sync_table, + genesis_block=16862919, # First Recorded Batch Reward block + ), + block_to=fetcher.get_latest_block(), + ) + sync_orderbook_data( + aws, + block_range, + config, + dry_run, + sync_table, + data_list=BatchRewards.from_pdf_to_dune_records( + fetcher.get_batch_rewards(block_range) + ), + ) + + +def sync_orderbook_data( # pylint:disable=too-many-arguments + aws: AWSClient, + block_range: BlockRange, + config: SyncConfig, + dry_run: bool, + sync_table: SyncTable, + data_list: list[dict[str, Any]], +) -> None: + """Generic Orderbook Sync Logic""" + record_handler = OrderbookDataHandler( + file_manager=FileIO(config.volume_path / str(sync_table)), + block_range=block_range, + config=config, + data_list=data_list, + sync_table=sync_table, + ) + UploadHandler(aws, record_handler, table=sync_table).write_and_upload_content( + dry_run + ) + log.info(f"{sync_table} sync run completed successfully") diff --git a/src/sync/record_handler.py b/src/sync/record_handler.py new file mode 100644 index 00000000..cca9e573 --- /dev/null +++ b/src/sync/record_handler.py @@ -0,0 +1,45 @@ +""" +Abstraction for New Content Handling +provides a framework for writing new content to disk and posting to AWS +""" +from abc import ABC, abstractmethod + +from src.logger import set_log +from src.models.block_range import BlockRange +from src.models.tables import SyncTable +from src.sync.config import SyncConfig + +log = set_log(__name__) + + +class RecordHandler(ABC): # pylint: disable=too-few-public-methods + + """ + This class is responsible for consuming new dune records and missing values from previous runs + it attempts to fetch content for them and filters them into "found" and "not found" as necessary + """ + + def __init__( + self, + block_range: BlockRange, + table: SyncTable, + config: SyncConfig, + ): + self.config = config + self.block_range = block_range + + self.name = str(table) + self.file_path = config.volume_path / self.name + self.content_filename = f"cow_{block_range.block_to}.json" + + @abstractmethod + def num_records(self) -> int: + """Returns number of records to handle""" + + @abstractmethod + def write_found_content(self) -> None: + """Writes content to disk""" + + @abstractmethod + def write_sync_data(self) -> None: + """Records last synced content file""" diff --git a/src/sync/token_imbalance.py b/src/sync/token_imbalance.py new file mode 100644 index 00000000..fe0ca4f3 --- /dev/null +++ b/src/sync/token_imbalance.py @@ -0,0 +1,47 @@ +"""Main Entry point for token_imbalance sync""" +from dune_client.file.interface import FileIO + +from src.fetch.postgres import PostgresFetcher +from src.logger import set_log +from src.models.block_range import BlockRange +from src.models.tables import SyncTable +from src.models.token_imbalance_schema import TokenImbalance +from src.post.aws import AWSClient +from src.sync.common import last_sync_block +from src.sync.config import SyncConfig +from src.sync.order_rewards import OrderbookDataHandler +from src.sync.upload_handler import UploadHandler + +log = set_log(__name__) + + +def sync_internal_imbalance( + aws: AWSClient, fetcher: PostgresFetcher, config: SyncConfig, dry_run: bool +) -> None: + """Token Imbalance Sync Logic""" + sync_table = SyncTable.INTERNAL_IMBALANCE + block_range = BlockRange( + block_from=last_sync_block( + aws, + table=sync_table, + # The first block for which solver competitions + # are available in production orderbook: + # select * from solver_competitions where id = 1; + genesis_block=15173540, + ), + block_to=fetcher.get_latest_block(), + ) + # TODO - Gap Detection (find missing txHashes and ensure they are accounted for!) + record_handler = OrderbookDataHandler( + file_manager=FileIO(config.volume_path / str(sync_table)), + block_range=block_range, + config=config, + data_list=TokenImbalance.from_pdf_to_dune_records( + fetcher.get_internal_imbalances(block_range) + ), + sync_table=sync_table, + ) + UploadHandler(aws, record_handler, table=sync_table).write_and_upload_content( + dry_run + ) + log.info(f"{sync_table} sync run completed successfully") diff --git a/src/sync/upload_handler.py b/src/sync/upload_handler.py new file mode 100644 index 00000000..498d042d --- /dev/null +++ b/src/sync/upload_handler.py @@ -0,0 +1,76 @@ +"""Upload handler responsible for local file updates and aws uploads""" +import os +import sys + +from s3transfer import S3UploadFailedError + +from src.logger import set_log +from src.models.tables import SyncTable +from src.post.aws import AWSClient +from src.sync.record_handler import RecordHandler + +log = set_log(__name__) + + +class UploadHandler: # pylint: disable=too-few-public-methods + """ + Given an instance of Record handler, ensures that + - files are written locally, + - uploaded to AWS and + - sync block is recorded + in the appropriate order. + """ + + def __init__(self, aws: AWSClient, record_handler: RecordHandler, table: SyncTable): + self.aws = aws + self.record_handler = record_handler + self.table = str(table) + + def _aws_login_and_upload(self) -> bool: + """Creates AWS client session and attempts to upload file""" + path, filename = ( + self.record_handler.file_path, + self.record_handler.content_filename, + ) + try: + return self.aws.upload_file( + filename=os.path.join(path, filename), + object_key=f"{self.table}/{filename}", + ) + except S3UploadFailedError as err: + log.error(err) + sys.exit(1) + + def write_and_upload_content(self, dry_run: bool) -> None: + """ + - Writes record handlers content to persistent volume, + - attempts to upload to AWS and + - records last sync block on volume. + When dryrun flag is enabled, does not upload to IPFS. + """ + record_handler = self.record_handler + num_records, block_range, name = ( + record_handler.num_records(), + record_handler.block_range, + record_handler.name, + ) + + record_handler.write_found_content() + if num_records > 0: + log.info( + f"attempting to post {num_records} new {name} records for block range {block_range}" + ) + if dry_run: + log.info( + "DRY-RUN-ENABLED: New records written to volume, but not posted to AWS." + ) + else: + self._aws_login_and_upload() + log.info( + f"{name} sync for block range {block_range} complete: " + f"synced {num_records} records" + ) + else: + log.info(f"No new {name} for block range {block_range}: no sync necessary") + + record_handler.write_sync_data() diff --git a/src/text_io.py b/src/text_io.py deleted file mode 100644 index 0f602e63..00000000 --- a/src/text_io.py +++ /dev/null @@ -1,76 +0,0 @@ -"""This is taken from https://hakibenita.com/fast-load-data-python-postgresql""" -# pylint: skip-file -import io -from typing import Iterator, Optional - - -class StringIteratorIO(io.TextIOBase): - def __init__(self, iter: Iterator[str]): - self._iter = iter - self._buff = "" - - def readable(self) -> bool: - return True - - def _read1(self, n: Optional[int] = None) -> str: - while not self._buff: - try: - self._buff = next(self._iter) - except StopIteration: - break - ret = self._buff[:n] - self._buff = self._buff[len(ret) :] - return ret - - def read(self, n: Optional[int] = None) -> str: - line = [] - if n is None or n < 0: - while True: - m = self._read1() - if not m: - break - line.append(m) - else: - while n > 0: - m = self._read1(n) - if not m: - break - n -= len(m) - line.append(m) - return "".join(line) - - -class BytesIteratorIO(io.BufferedIOBase): - def __init__(self, iter: Iterator[bytes]): - self._iter = iter - self._buff = b"" - - def readable(self) -> bool: - return True - - def _read1(self, n: Optional[int] = None) -> bytes: - while not self._buff: - try: - self._buff = next(self._iter) - except StopIteration: - break - ret = self._buff[:n] - self._buff = self._buff[len(ret) :] - return ret - - def read(self, n: Optional[int] = None) -> bytes: - line = [] - if n is None or n < 0: - while True: - m = self._read1() - if not m: - break - line.append(m) - else: - while n > 0: - m = self._read1(n) - if not m: - break - n -= len(m) - line.append(m) - return b"".join(line) diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 00000000..a601e8c9 --- /dev/null +++ b/src/utils.py @@ -0,0 +1,15 @@ +"""Basic reusable utility functions""" +import os + +from src.environment import QUERY_PATH + + +def open_query(filename: str) -> str: + """Opens `filename` and returns as string""" + with open(query_file(filename), "r", encoding="utf-8") as file: + return file.read() + + +def query_file(filename: str) -> str: + """Returns proper path for filename in QUERY_PATH""" + return os.path.join(QUERY_PATH, filename) diff --git a/tests/e2e/test_sync_app_data.py b/tests/e2e/test_sync_app_data.py new file mode 100644 index 00000000..e6aab234 --- /dev/null +++ b/tests/e2e/test_sync_app_data.py @@ -0,0 +1,80 @@ +import os +import shutil +import unittest +from pathlib import Path +from unittest import IsolatedAsyncioTestCase + +from dotenv import load_dotenv +from dune_client.file.interface import FileIO + +from src.fetch.dune import DuneFetcher +from src.models.block_range import BlockRange +from src.sync.app_data import AppDataHandler, SYNC_TABLE +from src.sync.config import AppDataSyncConfig + + +class TestSyncAppData(IsolatedAsyncioTestCase): + def setUp(self) -> None: + load_dotenv() + self.dune = DuneFetcher(os.environ["DUNE_API_KEY"]) + self.config = AppDataSyncConfig( + volume_path=Path(os.environ["VOLUME_PATH"]), + missing_files_name="missing_app_hashes.json", + max_retries=2, + give_up_threshold=3, + ) + self.file_manager = FileIO(self.config.volume_path / str(SYNC_TABLE)) + + def tearDown(self) -> None: + shutil.rmtree(self.config.volume_path) + + async def test_fetch_content_and_filter(self): + retries = self.config.max_retries + give_up = self.config.give_up_threshold + missing_files = self.config.missing_files_name + # block numbers + a, b, c = 15582187, 16082187, 16100000 + self.assertTrue(a < b < c) + + block_range_1 = BlockRange( + block_from=a, + block_to=b, + ) + data_handler = AppDataHandler( + file_manager=self.file_manager, + new_rows=await self.dune.get_app_hashes(block_range_1), + block_range=block_range_1, + config=self.config, + missing_file_name=missing_files, + ipfs_access_key=os.environ["IPFS_ACCESS_KEY"], + ) + + print(f"Beginning Content Fetching on {len(data_handler.new_rows)} records") + await data_handler.fetch_content_and_filter(retries, give_up) + data_handler.write_found_content() + self.assertEqual(0, len(data_handler.new_rows)) + + block_range_2 = BlockRange( + block_from=b, + block_to=c, + ) + data_handler = AppDataHandler( + file_manager=self.file_manager, + new_rows=await self.dune.get_app_hashes(block_range_2), + block_range=block_range_2, + config=self.config, + missing_file_name=missing_files, + ) + print( + f"Beginning Second Run Content Fetching on {len(data_handler.new_rows)} records" + ) + await data_handler.fetch_content_and_filter(retries, give_up) + data_handler.write_found_content() + + self.assertEqual(0, len(data_handler.new_rows)) + # Two runs with retries = 2 and give_up = 3 implies no more missing records. + self.assertEqual(0, len(data_handler._not_found)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/integration/test_aws.py b/tests/integration/test_aws.py index 222d09f8..c4598902 100644 --- a/tests/integration/test_aws.py +++ b/tests/integration/test_aws.py @@ -6,7 +6,7 @@ import pytest from dotenv import load_dotenv -from src.aws import AWSClient, BucketFileObject +from src.post.aws import AWSClient, BucketFileObject class TestAWSConnection(unittest.TestCase): diff --git a/tests/integration/test_fetch_orderbook.py b/tests/integration/test_fetch_orderbook.py new file mode 100644 index 00000000..e4be7fd6 --- /dev/null +++ b/tests/integration/test_fetch_orderbook.py @@ -0,0 +1,130 @@ +import unittest + +import pandas as pd + +from src.fetch.orderbook import OrderbookFetcher +from src.models.block_range import BlockRange + + +class TestFetchOrderbook(unittest.TestCase): + def test_latest_block_reasonable(self): + self.assertGreater(OrderbookFetcher.get_latest_block(), 16020300) + + def test_latest_block_increasing(self): + latest_block = OrderbookFetcher.get_latest_block() + self.assertGreaterEqual(OrderbookFetcher.get_latest_block(), latest_block) + + def test_get_order_rewards(self): + block_number = 16000000 + block_range = BlockRange(block_number, block_number + 50) + rewards_df = OrderbookFetcher.get_order_rewards(block_range) + expected = pd.DataFrame( + { + "block_number": [16000018, 16000050], + "order_uid": [ + "0xb52fecfe3df73f0e93f1f9b27c92e3def50322960f9c62d0b97bc2ceee36c07a0639dda84198dc06f5bc91bddbb62cd2e38c2f9a6378140f", + "0xf61cba0b42ed3e956f9db049c0523e123967723c5bcf76ccac0b179a66305b2a7fee439ed7a6bb1b8e7ca1ffdb0a5ca8d993c030637815ad", + ], + "solver": [ + "0x3cee8c7d9b5c8f225a8c36e7d3514e1860309651", + "0xc9ec550bea1c64d779124b23a26292cc223327b6", + ], + "tx_hash": [ + "0xb6f7df8a1114129f7b61f2863b3f81b3620e95f73e5b769a62bb7a87ab6983f4", + "0x2ce77009e78c291cdf39eb6f8ddf7e2c3401b4f962ef1240bdac47e632f8eb7f", + ], + "surplus_fee": ["0", "0"], + "amount": [40.70410, 39.00522], + } + ) + + self.assertIsNone(pd.testing.assert_frame_equal(expected, rewards_df)) + + def test_get_batch_rewards(self): + block_number = 16846500 + block_range = BlockRange(block_number, block_number + 25) + rewards_df = OrderbookFetcher.get_batch_rewards(block_range) + expected = pd.DataFrame( + { + "block_number": pd.Series([16846495, 16846502, pd.NA], dtype="Int64"), + "block_deadline": [16846509, 16846516, 16846524], + "tx_hash": [ + "0x2189c2994dcffcd40cc92245e216b0fda42e0f30573ce4b131341e8ac776ed75", + "0x8328fa642f47adb61f751363cf718d707dafcdc258898fa953945afd42aa020f", + None, + ], + "solver": [ + "0xb20b86c4e6deeb432a22d773a221898bbbd03036", + "0x55a37a2e5e5973510ac9d9c723aec213fa161919", + "0x55a37a2e5e5973510ac9d9c723aec213fa161919", + ], + "execution_cost": [ + "5417013431615490", + "14681404168612460", + "0", + ], + "surplus": [ + "5867838023808109", + "104011002982952097", + "0", + ], + "fee": [ + "7751978767036064", + "10350680045815651", + "0", + ], + "uncapped_payment_eth": [ + "7232682540629268", + "82825156151734420", + "-3527106002507021", + ], + "capped_payment": [ + "7232682540629268", + "24681404168612460", + "-3527106002507021", + ], + "winning_score": [ + "6537976145828389", + "95640781782532198", + "3527282436747751", + ], + "reference_score": [ + "6387134250214905", + "31536526877033328", + "3527106002507021", + ], + "participating_solvers": [ + [ + "0x398890be7c4fac5d766e1aeffde44b2ee99f38ef", + "0xb20b86c4e6deeb432a22d773a221898bbbd03036", + ], + [ + "0x55a37a2e5e5973510ac9d9c723aec213fa161919", + "0x97ec0a17432d71a3234ef7173c6b48a2c0940896", + "0xa21740833858985e4d801533a808786d3647fb83", + "0xb20b86c4e6deeb432a22d773a221898bbbd03036", + "0xbff9a1b539516f9e20c7b621163e676949959a66", + "0xc9ec550bea1c64d779124b23a26292cc223327b6", + "0xda869be4adea17ad39e1dfece1bc92c02491504f", + ], + [ + "0x149d0f9282333681ee41d30589824b2798e9fb47", + "0x3cee8c7d9b5c8f225a8c36e7d3514e1860309651", + "0x55a37a2e5e5973510ac9d9c723aec213fa161919", + "0x7a0a8890d71a4834285efdc1d18bb3828e765c6a", + "0x97ec0a17432d71a3234ef7173c6b48a2c0940896", + "0xa21740833858985e4d801533a808786d3647fb83", + "0xb20b86c4e6deeb432a22d773a221898bbbd03036", + "0xbff9a1b539516f9e20c7b621163e676949959a66", + "0xc9ec550bea1c64d779124b23a26292cc223327b6", + "0xda869be4adea17ad39e1dfece1bc92c02491504f", + "0xe9ae2d792f981c53ea7f6493a17abf5b2a45a86b", + ], + ], + }, + ) + self.assertIsNone(pd.testing.assert_frame_equal(expected, rewards_df)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/integration/test_warehouse_fetcher.py b/tests/integration/test_warehouse_fetcher.py new file mode 100644 index 00000000..a832e2d5 --- /dev/null +++ b/tests/integration/test_warehouse_fetcher.py @@ -0,0 +1,42 @@ +import os +import unittest + +import pandas as pd +from dotenv import load_dotenv + +from src.fetch.postgres import PostgresFetcher +from src.models.block_range import BlockRange + + +class TestPostgresWarehouseFetching(unittest.TestCase): + def setUp(self) -> None: + load_dotenv() + # TODO - deploy test DB and populate with some records... + self.fetcher = PostgresFetcher(db_url=os.environ["WAREHOUSE_URL"]) + + def test_latest_block_reasonable(self): + self.assertGreater(self.fetcher.get_latest_block(), 17273090) + + def test_get_imbalances(self): + imbalance_df = self.fetcher.get_internal_imbalances( + BlockRange(17236982, 17236983) + ) + expected = pd.DataFrame( + { + "block_number": pd.Series([17236983, 17236983], dtype="int64"), + "tx_hash": [ + "0x9dc611149c7d6a936554b4be0e4fde455c015a9d5c81650af1433df5e904c791", + "0x9dc611149c7d6a936554b4be0e4fde455c015a9d5c81650af1433df5e904c791", + ], + "token": [ + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + ], + "amount": ["-1438513324", "789652004205719637"], + }, + ) + self.assertIsNone(pd.testing.assert_frame_equal(expected, imbalance_df)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/test_batch_rewards_schema.py b/tests/unit/test_batch_rewards_schema.py new file mode 100644 index 00000000..cfdaaca1 --- /dev/null +++ b/tests/unit/test_batch_rewards_schema.py @@ -0,0 +1,101 @@ +import unittest + +import pandas +import pandas as pd + +from src.models.batch_rewards_schema import BatchRewards + +ONE_ETH = 1000000000000000000 + + +class TestModelBatchRewards(unittest.TestCase): + def test_order_rewards_transformation(self): + max_uint = 115792089237316195423570985008687907853269984665640564039457584007913129639936 + sample_df = pd.DataFrame( + { + "block_number": pd.Series([123, pandas.NA], dtype="Int64"), + "block_deadline": [789, 1011], + "tx_hash": [ + "0x71", + None, + ], + "solver": [ + "0x51", + "0x52", + ], + "execution_cost": [9999 * ONE_ETH, 1], + "surplus": [2 * ONE_ETH, 3 * ONE_ETH], + "fee": [ + 1000000000000000, + max_uint, + ], + "uncapped_payment_eth": [0, -10 * ONE_ETH], + "capped_payment": [-1000000000000000, -1000000000000000], + "winning_score": [123456 * ONE_ETH, 6789 * ONE_ETH], + "reference_score": [ONE_ETH, 2 * ONE_ETH], + "participating_solvers": [ + [ + "0x51", + "0x52", + "0x53", + ], + [ + "0x51", + "0x52", + "0x53", + "0x54", + "0x55", + "0x56", + ], + ], + } + ) + + self.assertEqual( + [ + { + "block_deadline": 789, + "block_number": 123, + "data": { + "capped_payment": -1000000000000000, + "execution_cost": 9999000000000000000000, + "fee": 1000000000000000, + "participating_solvers": ["0x51", "0x52", "0x53"], + "reference_score": 1000000000000000000, + "surplus": 2000000000000000000, + "uncapped_payment_eth": 0, + "winning_score": 123456000000000000000000, + }, + "solver": "0x51", + "tx_hash": "0x71", + }, + { + "block_deadline": 1011, + "block_number": None, + "data": { + "capped_payment": -1000000000000000, + "execution_cost": 1, + "fee": max_uint, + "participating_solvers": [ + "0x51", + "0x52", + "0x53", + "0x54", + "0x55", + "0x56", + ], + "reference_score": 2000000000000000000, + "surplus": 3000000000000000000, + "uncapped_payment_eth": -10000000000000000000, + "winning_score": 6789000000000000000000, + }, + "solver": "0x52", + "tx_hash": None, + }, + ], + BatchRewards.from_pdf_to_dune_records(sample_df), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/test_ipfs.py b/tests/unit/test_ipfs.py new file mode 100644 index 00000000..e26e2279 --- /dev/null +++ b/tests/unit/test_ipfs.py @@ -0,0 +1,215 @@ +import os +import unittest +from unittest import IsolatedAsyncioTestCase + +from dotenv import load_dotenv + +from src.fetch.ipfs import Cid + +load_dotenv() +ACCESS_KEY = os.environ["IPFS_ACCESS_KEY"] + + +class TestIPFS(unittest.TestCase): + def test_cid_parsing(self): + self.assertEqual( + "bafybeib5q5w6r7gxbfutjhes24y65mcif7ugm7hmub2vsk4hqueb2yylti", + str( + Cid.old_schema( + "0x3d876de8fcd70969349c92d731eeb0482fe8667ceca075592b8785081d630b9a" + ) + ), + ) + self.assertEqual( + "bafybeia747cvkwz7tqkp67da3ehrl4nfwena3jnr5cvainmcugzocbmnbq", + str( + Cid.old_schema( + "0x1FE7C5555B3F9C14FF7C60D90F15F1A5B11A0DA5B1E8AA043582A1B2E1058D0C" + ) + ), + ) + + def test_new_hash_to_cid(self): + self.assertEqual( + "bafkrwiek6tumtfzvo6yivqq5c7jtdkw6q3ar5pgfcjdujvrbzkbwl3eueq", + str( + Cid( + "0x8af4e8c9973577b08ac21d17d331aade86c11ebcc5124744d621ca8365ec9424" + ) + ), + ) + + def test_cid_constructor(self): + # works with or without 0x prefix: + hex_str = "0x3d876de8fcd70969349c92d731eeb0482fe8667ceca075592b8785081d630b9a" + self.assertEqual(Cid(hex_str), Cid(hex_str[2:])) + self.assertEqual(hex_str, Cid(hex_str).hex) + + def test_no_content(self): + null_cid = Cid( + "0000000000000000000000000000000000000000000000000000000000000000" + ) + + self.assertEqual(None, null_cid.get_content(ACCESS_KEY, max_retries=10)) + + def test_get_content(self): + self.assertEqual( + { + "version": "0.1.0", + "appCode": "CowSwap", + "metadata": { + "referrer": { + "version": "0.1.0", + "address": "0x424a46612794dbb8000194937834250Dc723fFa5", + } + }, + }, + Cid.old_schema( + "3d876de8fcd70969349c92d731eeb0482fe8667ceca075592b8785081d630b9a" + ).get_content(ACCESS_KEY, max_retries=10), + ) + + self.assertEqual( + { + "version": "1.0.0", + "appCode": "CowSwap", + "metadata": { + "referrer": { + "kind": "referrer", + "referrer": "0x8c35B7eE520277D14af5F6098835A584C337311b", + "version": "1.0.0", + } + }, + }, + Cid.old_schema( + "1FE7C5555B3F9C14FF7C60D90F15F1A5B11A0DA5B1E8AA043582A1B2E1058D0C" + ).get_content(ACCESS_KEY), + ) + + +class TestAsyncIPFS(IsolatedAsyncioTestCase): + async def test_fetch_many(self): + x = [ + { + "app_hash": "0xa0029a1376a317ea4af7d64c1a15cb02c5b1f33c72645cc8612a8d302a6e2ac8", + "first_seen_block": 13897827, + }, + { + "app_hash": "0xd8e0e541ebb486bfb4fbfeeaf097e56487b5b3ea7190bd1b286693ac02954ecd", + "first_seen_block": 15428678, + }, + { + "app_hash": "0xd93cc4feb701b8b7c8013c33be82f05d28f04c127116eb3764d36881e5cd7d07", + "first_seen_block": 13602894, + }, + { + "app_hash": "0x2c699c8c8b379a0ab6e6b1bc252dd2539b59b50056da1c62b2bcaf4f706d1e81", + "first_seen_block": 13643476, + }, + { + "app_hash": "0xe4096788536b002e3d0af9e3a4ac44cbd5456cbd3a88d96f02c6b23be319fc6b", + "first_seen_block": 15430776, + }, + { + "app_hash": "0xd7d476c0682b033fc4025f69dfb5967afe5ea96be872b1f6a740bbdc7dd97b25", + "first_seen_block": 13671622, + }, + { + "app_hash": "0x37a52cce8b0b5f4b2047b8d34992d27755c9af4341290d38e4cf9278e3b8fcc9", + "first_seen_block": 15104812, + }, + { + "app_hash": "0x6faca2b31493acf30239268b08ef6fa9a54ff093018c5c53a0847eb13ad8181f", + "first_seen_block": 15557705, + }, + { + "app_hash": "0x476e33b8975dd54ada4a99d52c9ea4b3d41bd50763377f6e078c60631d8bc02a", + "first_seen_block": 13783477, + }, + { + "app_hash": "0xd4f33cd977ef095b38aabb1ee7a2f6f69fabb4022601fdc29c9fc78c451f4e12", + "first_seen_block": 13824523, + }, + { + "app_hash": "0x8b98eaf7082a14b3201011c23a39b23706d880c1269e76c154675230daf6af8d", + "first_seen_block": 13709795, + }, + { + "app_hash": "0x5f3b188668cc36ab896f348913668550749c7b7f38304f45b9449cb5bea034b6", + "first_seen_block": 13608189, + }, + { + "app_hash": "0xec3143ebecec4ac0f5471ce611f8220bd0abe5509c29216c6837be29fe573830", + "first_seen_block": 13906769, + }, + { + "app_hash": "0x4c4d75807c15451613cead7de87e465fe9368708644fbad1e04bc442ee015466", + "first_seen_block": 13916779, + }, + { + "app_hash": "0x8ee92d0defae33a7471eb0e4abac92c7be3d6811f256ecd5d439609086a9e353", + "first_seen_block": 15684132, + }, + { + "app_hash": "0x5cf63a36847e6f6c98c8eb74d03cfbad8e234d7388637ae5ca53be9dd90eccca", + "first_seen_block": 15296113, + }, + { + "app_hash": "0x517af09fe972bd26beb08cd951783b64a494ed22b87e088754156f9fbc9b993f", + "first_seen_block": 13745623, + }, + { + "app_hash": "0x385802ecdb00e564d36aed7410454a64ecb3cfcb84792bad1ead79d1447ab090", + "first_seen_block": 13967575, + }, + { + "app_hash": "0x809877e2e366166a0bcbce17b759b90d2d74af0d830a0441a44ea246876ccec0", + "first_seen_block": 13938253, + }, + { + "app_hash": "0xe81711d4f01afa98128ec73441fbda767db1cf84b8c4c1bfb4243a5dddca1155", + "first_seen_block": 13944768, + }, + { + "app_hash": "0x1ec8fdb53a2697ab15bc4228c9e4d3125c955470b618fcb1659175d0f6e6c991", + "first_seen_block": 14624083, + }, + { + "app_hash": "0xa15b953113490d318baa1e35f1c9c33db3a6f400ce58539db287456b17bf3e58", + "first_seen_block": 13886533, + }, + { + "app_hash": "0x039ffb0546992a50e4eeb70b2b6bb04c0a9a82dcad586b1c4f971d6c66109894", + "first_seen_block": 13607684, + }, + { + "app_hash": "0xc56523f1422d97dcd77ab7471f3de724ac223d5a51c3af832c98a25e8ad30ab3", + "first_seen_block": 13664176, + }, + { + "app_hash": "0x690483cfb64d0a175e4cb85c313e8da3e1826966acefc6697026176adacc9b19", + "first_seen_block": 14699411, + }, + { + "app_hash": "0xe86fbb850981b11ab388e077c9a5a6da6cfe386c55f78709bc6549e5b806fc08", + "first_seen_block": 13905438, + }, + { + "app_hash": "0xba00f0857b72309aba6a395694d999e573d6b183cf5a605341b0f3ddfba333de", + "first_seen_block": 13779206, + }, + { + "app_hash": "0xdf93796ffbd982e40c53ea10517cc90eb1355eb3f843f0dafa441165a337557f", + "first_seen_block": 13616326, + }, + { + "app_hash": "0x12f79d7c232ee2ef20b7aaefaf677a69d00d179d003cf5257d557d9154427f0f", + "first_seen_block": 13971658, + }, + ] + results = await Cid.fetch_many(x, ACCESS_KEY) + print(results) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/test_order_rewards_schema.py b/tests/unit/test_order_rewards_schema.py new file mode 100644 index 00000000..e8e479b7 --- /dev/null +++ b/tests/unit/test_order_rewards_schema.py @@ -0,0 +1,59 @@ +import unittest + +import pandas as pd + +from src.models.order_rewards_schema import OrderRewards + + +class TestModelOrderRewards(unittest.TestCase): + def test_order_rewards_transformation(self): + rewards_df = pd.DataFrame( + { + "block_number": [1, 2, 3], + "order_uid": ["0x01", "0x02", "0x03"], + "solver": ["0x51", "0x52", "0x53"], + "tx_hash": ["0x71", "0x72", "0x73"], + "surplus_fee": [12345678910111213, 0, 0], + "amount": [40.70410, 39.00522, 0], + } + ) + + self.assertEqual( + [ + { + "block_number": 1, + "order_uid": "0x01", + "solver": "0x51", + "tx_hash": "0x71", + "data": { + "surplus_fee": "12345678910111213", + "amount": 40.70410, + }, + }, + { + "block_number": 2, + "order_uid": "0x02", + "solver": "0x52", + "tx_hash": "0x72", + "data": { + "surplus_fee": "0", + "amount": 39.00522, + }, + }, + { + "block_number": 3, + "order_uid": "0x03", + "solver": "0x53", + "tx_hash": "0x73", + "data": { + "surplus_fee": "0", + "amount": 0.0, + }, + }, + ], + OrderRewards.from_pdf_to_dune_records(rewards_df), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/test_token_imbalance_schema.py b/tests/unit/test_token_imbalance_schema.py new file mode 100644 index 00000000..136e3c37 --- /dev/null +++ b/tests/unit/test_token_imbalance_schema.py @@ -0,0 +1,46 @@ +import unittest + +import pandas as pd + +from src.models.token_imbalance_schema import TokenImbalance + + +class TestModelTokenImbalance(unittest.TestCase): + def test_token_imbalance_schema(self): + max_uint = 115792089237316195423570985008687907853269984665640564039457584007913129639936 + sample_df = pd.DataFrame( + { + "block_number": pd.Series([123, 456], dtype="int64"), + "tx_hash": [ + "0x71", + "0x72", + ], + "token": [ + "0xa0", + "0xa1", + ], + "amount": [-9999, max_uint], + } + ) + + self.assertEqual( + [ + { + "amount": "-9999", + "block_number": 123, + "token": "0xa0", + "tx_hash": "0x71", + }, + { + "amount": "115792089237316195423570985008687907853269984665640564039457584007913129639936", + "block_number": 456, + "token": "0xa1", + "tx_hash": "0x72", + }, + ], + TokenImbalance.from_pdf_to_dune_records(sample_df), + ) + + +if __name__ == "__main__": + unittest.main()