Compare commits
4 Commits
v1.10.18
...
buildbot-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
621fd8fe7f | ||
|
|
019483bdaf | ||
|
|
7e0e69c622 | ||
|
|
0a05134c71 |
274
.mailmap
274
.mailmap
@@ -1,237 +1,123 @@
|
||||
Aaron Buchwald <aaron.buchwald56@gmail.com>
|
||||
Jeffrey Wilcke <jeffrey@ethereum.org>
|
||||
Jeffrey Wilcke <jeffrey@ethereum.org> <geffobscura@gmail.com>
|
||||
Jeffrey Wilcke <jeffrey@ethereum.org> <obscuren@obscura.com>
|
||||
Jeffrey Wilcke <jeffrey@ethereum.org> <obscuren@users.noreply.github.com>
|
||||
|
||||
Aaron Kumavis <kumavis@users.noreply.github.com>
|
||||
Viktor Trón <viktor.tron@gmail.com>
|
||||
|
||||
Abel Nieto <abel.nieto90@gmail.com>
|
||||
Abel Nieto <abel.nieto90@gmail.com> <anietoro@uwaterloo.ca>
|
||||
Joseph Goulden <joegoulden@gmail.com>
|
||||
|
||||
Afri Schoedon <58883403+q9f@users.noreply.github.com>
|
||||
Afri Schoedon <5chdn@users.noreply.github.com> <58883403+q9f@users.noreply.github.com>
|
||||
Nick Savers <nicksavers@gmail.com>
|
||||
|
||||
Alec Perseghin <aperseghin@gmail.com>
|
||||
Maran Hidskes <maran.hidskes@gmail.com>
|
||||
|
||||
Aleksey Smyrnov <i@soar.name>
|
||||
|
||||
Alex Leverington <alex@ethdev.com>
|
||||
Alex Leverington <alex@ethdev.com> <subtly@users.noreply.github.com>
|
||||
|
||||
Alex Pozhilenkov <alex_pozhilenkov@adoriasoft.com>
|
||||
Alex Pozhilenkov <alex_pozhilenkov@adoriasoft.com> <leshiy12345678@gmail.com>
|
||||
|
||||
Alexey Akhunov <akhounov@gmail.com>
|
||||
|
||||
Alon Muroch <alonmuroch@gmail.com>
|
||||
|
||||
Andrey Petrov <shazow@gmail.com>
|
||||
Andrey Petrov <shazow@gmail.com> <andrey.petrov@shazow.net>
|
||||
|
||||
Arkadiy Paronyan <arkadiy@ethdev.com>
|
||||
|
||||
Armin Braun <me@obrown.io>
|
||||
|
||||
Aron Fischer <github@aron.guru> <homotopycolimit@users.noreply.github.com>
|
||||
|
||||
Austin Roberts <code@ausiv.com>
|
||||
Austin Roberts <code@ausiv.com> <git@ausiv.com>
|
||||
Taylor Gerring <taylor.gerring@gmail.com>
|
||||
Taylor Gerring <taylor.gerring@gmail.com> <taylor.gerring@ethereum.org>
|
||||
|
||||
Bas van Kervel <bas@ethdev.com>
|
||||
Bas van Kervel <bas@ethdev.com> <basvankervel@ziggo.nl>
|
||||
Bas van Kervel <bas@ethdev.com> <basvankervel@gmail.com>
|
||||
Bas van Kervel <bas@ethdev.com> <bas-vk@users.noreply.github.com>
|
||||
|
||||
Boqin Qin <bobbqqin@bupt.edu.cn>
|
||||
Boqin Qin <bobbqqin@bupt.edu.cn> <Bobbqqin@gmail.com>
|
||||
Sven Ehlert <sven@ethdev.com>
|
||||
|
||||
Casey Detrio <cdetrio@gmail.com>
|
||||
Vitalik Buterin <v@buterin.com>
|
||||
|
||||
Cheng Li <lob4tt@gmail.com>
|
||||
|
||||
Chris Ziogas <ziogaschr@gmail.com>
|
||||
Chris Ziogas <ziogaschr@gmail.com> <ziogas_chr@hotmail.com>
|
||||
Marian Oancea <contact@siteshop.ro>
|
||||
|
||||
Christoph Jentzsch <jentzsch.software@gmail.com>
|
||||
|
||||
Diederik Loerakker <proto@protolambda.com>
|
||||
Heiko Hees <heiko@heiko.org>
|
||||
|
||||
Alex Leverington <alex@ethdev.com>
|
||||
Alex Leverington <alex@ethdev.com> <subtly@users.noreply.github.com>
|
||||
|
||||
Zsolt Felföldi <zsfelfoldi@gmail.com>
|
||||
|
||||
Gavin Wood <i@gavwood.com>
|
||||
|
||||
Martin Becze <mjbecze@gmail.com>
|
||||
Martin Becze <mjbecze@gmail.com> <wanderer@users.noreply.github.com>
|
||||
|
||||
Dimitry Khokhlov <winsvega@mail.ru>
|
||||
|
||||
Domino Valdano <dominoplural@gmail.com>
|
||||
Domino Valdano <dominoplural@gmail.com> <jeff@okcupid.com>
|
||||
Roman Mandeleil <roman.mandeleil@gmail.com>
|
||||
|
||||
Edgar Aroutiounian <edgar.factorial@gmail.com>
|
||||
Alec Perseghin <aperseghin@gmail.com>
|
||||
|
||||
Elliot Shepherd <elliot@identitii.com>
|
||||
Alon Muroch <alonmuroch@gmail.com>
|
||||
|
||||
Arkadiy Paronyan <arkadiy@ethdev.com>
|
||||
|
||||
Jae Kwon <jkwon.work@gmail.com>
|
||||
|
||||
Aaron Kumavis <kumavis@users.noreply.github.com>
|
||||
|
||||
Nick Dodson <silentcicero@outlook.com>
|
||||
|
||||
Jason Carver <jacarver@linkedin.com>
|
||||
Jason Carver <jacarver@linkedin.com> <ut96caarrs@snkmail.com>
|
||||
|
||||
Joseph Chow <ethereum@outlook.com>
|
||||
Joseph Chow <ethereum@outlook.com> ethers <TODO>
|
||||
|
||||
Enrique Fynn <enriquefynn@gmail.com>
|
||||
|
||||
Enrique Fynn <me@enriquefynn.com>
|
||||
Enrique Fynn <me@enriquefynn.com> <enriquefynn@gmail.com>
|
||||
Vincent G <caktux@gmail.com>
|
||||
|
||||
Ernesto del Toro <ernesto.deltoro@gmail.com>
|
||||
Ernesto del Toro <ernesto.deltoro@gmail.com> <ernestodeltoro@users.noreply.github.com>
|
||||
RJ Catalano <catalanor0220@gmail.com>
|
||||
RJ Catalano <catalanor0220@gmail.com> <rj@erisindustries.com>
|
||||
|
||||
Everton Fraga <ev@ethereum.org>
|
||||
Nchinda Nchinda <nchinda2@gmail.com>
|
||||
|
||||
Aron Fischer <github@aron.guru> <homotopycolimit@users.noreply.github.com>
|
||||
|
||||
Vlad Gluhovsky <gluk256@users.noreply.github.com>
|
||||
|
||||
Ville Sundell <github@solarius.fi>
|
||||
|
||||
Elliot Shepherd <elliot@identitii.com>
|
||||
|
||||
Yohann Léon <sybiload@gmail.com>
|
||||
|
||||
Gregg Dourgarian <greggd@tempworks.com>
|
||||
|
||||
Casey Detrio <cdetrio@gmail.com>
|
||||
|
||||
Jens Agerberg <github@agerberg.me>
|
||||
|
||||
Nick Johnson <arachnid@notdot.net>
|
||||
|
||||
Henning Diedrich <hd@eonblast.com>
|
||||
Henning Diedrich <hd@eonblast.com> Drake Burroughs <wildfyre@hotmail.com>
|
||||
|
||||
Felix Lange <fjl@twurst.com>
|
||||
Felix Lange <fjl@twurst.com> <fjl@users.noreply.github.com>
|
||||
|
||||
Максим Чусовлянов <mchusovlianov@gmail.com>
|
||||
|
||||
Louis Holbrook <dev@holbrook.no>
|
||||
Louis Holbrook <dev@holbrook.no> <nolash@users.noreply.github.com>
|
||||
|
||||
Thomas Bocek <tom@tomp2p.net>
|
||||
|
||||
Victor Tran <vu.tran54@gmail.com>
|
||||
|
||||
Justin Drake <drakefjustin@gmail.com>
|
||||
|
||||
Frank Wang <eternnoir@gmail.com>
|
||||
|
||||
Gary Rong <garyrong0905@gmail.com>
|
||||
|
||||
Gavin Wood <i@gavwood.com>
|
||||
|
||||
Gregg Dourgarian <greggd@tempworks.com>
|
||||
|
||||
Guillaume Ballet <gballet@gmail.com>
|
||||
Guillaume Ballet <gballet@gmail.com> <3272758+gballet@users.noreply.github.com>
|
||||
|
||||
Guillaume Nicolas <guin56@gmail.com>
|
||||
|
||||
Hanjiang Yu <delacroix.yu@gmail.com>
|
||||
Hanjiang Yu <delacroix.yu@gmail.com> <42531996+de1acr0ix@users.noreply.github.com>
|
||||
|
||||
Heiko Hees <heiko@heiko.org>
|
||||
|
||||
Henning Diedrich <hd@eonblast.com>
|
||||
Henning Diedrich <hd@eonblast.com> Drake Burroughs <wildfyre@hotmail.com>
|
||||
|
||||
Hwanjo Heo <34005989+hwanjo@users.noreply.github.com>
|
||||
|
||||
Iskander (Alex) Sharipov <quasilyte@gmail.com>
|
||||
Iskander (Alex) Sharipov <quasilyte@gmail.com> <i.sharipov@corp.vk.com>
|
||||
|
||||
Jae Kwon <jkwon.work@gmail.com>
|
||||
|
||||
Janoš Guljaš <janos@resenje.org> <janos@users.noreply.github.com>
|
||||
Janoš Guljaš <janos@resenje.org> Janos Guljas <janos@resenje.org>
|
||||
|
||||
Jared Wasinger <j-wasinger@hotmail.com>
|
||||
|
||||
Jason Carver <jacarver@linkedin.com>
|
||||
Jason Carver <jacarver@linkedin.com> <ut96caarrs@snkmail.com>
|
||||
|
||||
Javier Peletier <jm@epiclabs.io>
|
||||
Javier Peletier <jm@epiclabs.io> <jpeletier@users.noreply.github.com>
|
||||
|
||||
Jeffrey Wilcke <jeffrey@ethereum.org>
|
||||
Jeffrey Wilcke <jeffrey@ethereum.org> <geffobscura@gmail.com>
|
||||
Jeffrey Wilcke <jeffrey@ethereum.org> <obscuren@obscura.com>
|
||||
Jeffrey Wilcke <jeffrey@ethereum.org> <obscuren@users.noreply.github.com>
|
||||
|
||||
Jens Agerberg <github@agerberg.me>
|
||||
|
||||
Joseph Chow <ethereum@outlook.com>
|
||||
Joseph Chow <ethereum@outlook.com> ethers <TODO>
|
||||
|
||||
|
||||
Joseph Goulden <joegoulden@gmail.com>
|
||||
|
||||
Justin Drake <drakefjustin@gmail.com>
|
||||
|
||||
Kenso Trabing <ktrabing@acm.org>
|
||||
Kenso Trabing <ktrabing@acm.org> <kenso.trabing@bloomwebsite.com>
|
||||
|
||||
Liang Ma <liangma@liangbit.com>
|
||||
Liang Ma <liangma@liangbit.com> <liangma.ul@gmail.com>
|
||||
|
||||
Louis Holbrook <dev@holbrook.no>
|
||||
Louis Holbrook <dev@holbrook.no> <nolash@users.noreply.github.com>
|
||||
|
||||
Maran Hidskes <maran.hidskes@gmail.com>
|
||||
|
||||
Marian Oancea <contact@siteshop.ro>
|
||||
|
||||
Martin Becze <mjbecze@gmail.com>
|
||||
Martin Becze <mjbecze@gmail.com> <wanderer@users.noreply.github.com>
|
||||
|
||||
Martin Lundfall <martin.lundfall@protonmail.com>
|
||||
|
||||
Matt Garnett <14004106+lightclient@users.noreply.github.com>
|
||||
|
||||
Matthew Halpern <matthalp@gmail.com>
|
||||
Matthew Halpern <matthalp@gmail.com> <matthalp@google.com>
|
||||
|
||||
Michael Riabzev <michael@starkware.co>
|
||||
|
||||
Nchinda Nchinda <nchinda2@gmail.com>
|
||||
|
||||
Nick Dodson <silentcicero@outlook.com>
|
||||
|
||||
Nick Johnson <arachnid@notdot.net>
|
||||
|
||||
Nick Savers <nicksavers@gmail.com>
|
||||
|
||||
Nishant Das <nishdas93@gmail.com>
|
||||
Nishant Das <nishdas93@gmail.com> <nish1993@hotmail.com>
|
||||
|
||||
Olivier Hervieu <olivier.hervieu@gmail.com>
|
||||
|
||||
Pascal Dierich <pascal@merkleplant.xyz>
|
||||
Pascal Dierich <pascal@merkleplant.xyz> <pascal@pascaldierich.com>
|
||||
|
||||
RJ Catalano <catalanor0220@gmail.com>
|
||||
RJ Catalano <catalanor0220@gmail.com> <rj@erisindustries.com>
|
||||
|
||||
Ralph Caraveo <deckarep@gmail.com>
|
||||
|
||||
Rene Lubov <41963722+renaynay@users.noreply.github.com>
|
||||
|
||||
Robert Zaremba <robert@zaremba.ch>
|
||||
Robert Zaremba <robert@zaremba.ch> <robert.zaremba@scale-it.pl>
|
||||
|
||||
Roman Mandeleil <roman.mandeleil@gmail.com>
|
||||
|
||||
Sorin Neacsu <sorin.neacsu@gmail.com>
|
||||
Sorin Neacsu <sorin.neacsu@gmail.com> <sorin@users.noreply.github.com>
|
||||
|
||||
Sven Ehlert <sven@ethdev.com>
|
||||
|
||||
Taylor Gerring <taylor.gerring@gmail.com>
|
||||
Taylor Gerring <taylor.gerring@gmail.com> <taylor.gerring@ethereum.org>
|
||||
|
||||
Thomas Bocek <tom@tomp2p.net>
|
||||
|
||||
Tim Cooijmans <timcooijmans@gmail.com>
|
||||
|
||||
Valentin Wüstholz <wuestholz@gmail.com>
|
||||
Valentin Wüstholz <wuestholz@gmail.com> <wuestholz@users.noreply.github.com>
|
||||
|
||||
Victor Tran <vu.tran54@gmail.com>
|
||||
Armin Braun <me@obrown.io>
|
||||
|
||||
Viktor Trón <viktor.tron@gmail.com>
|
||||
|
||||
Ville Sundell <github@solarius.fi>
|
||||
|
||||
Vincent G <caktux@gmail.com>
|
||||
|
||||
Vitalik Buterin <v@buterin.com>
|
||||
|
||||
Vlad Gluhovsky <gluk256@gmail.com>
|
||||
Vlad Gluhovsky <gluk256@gmail.com> <gluk256@users.noreply.github.com>
|
||||
|
||||
Wenshao Zhong <wzhong20@uic.edu>
|
||||
Wenshao Zhong <wzhong20@uic.edu> <11510383@mail.sustc.edu.cn>
|
||||
Wenshao Zhong <wzhong20@uic.edu> <374662347@qq.com>
|
||||
|
||||
Will Villanueva <hello@willvillanueva.com>
|
||||
|
||||
Xiaobing Jiang <s7v7nislands@gmail.com>
|
||||
|
||||
Xudong Liu <33193253+r1cs@users.noreply.github.com>
|
||||
|
||||
Yohann Léon <sybiload@gmail.com>
|
||||
|
||||
Zachinquarantine <Zachinquarantine@protonmail.com>
|
||||
Zachinquarantine <Zachinquarantine@protonmail.com> <zachinquarantine@yahoo.com>
|
||||
|
||||
Ziyuan Zhong <zzy.albert@163.com>
|
||||
|
||||
Zsolt Felföldi <zsfelfoldi@gmail.com>
|
||||
|
||||
meowsbits <b5c6@protonmail.com>
|
||||
meowsbits <b5c6@protonmail.com> <45600330+meowsbits@users.noreply.github.com>
|
||||
|
||||
nedifi <103940716+nedifi@users.noreply.github.com>
|
||||
|
||||
Максим Чусовлянов <mchusovlianov@gmail.com>
|
||||
Ernesto del Toro <ernesto.deltoro@gmail.com>
|
||||
Ernesto del Toro <ernesto.deltoro@gmail.com> <ernestodeltoro@users.noreply.github.com>
|
||||
|
||||
260
AUTHORS
260
AUTHORS
@@ -1,46 +1,27 @@
|
||||
# This is the official list of go-ethereum authors for copyright purposes.
|
||||
|
||||
6543 <6543@obermui.de>
|
||||
a e r t h <aerth@users.noreply.github.com>
|
||||
Aaron Buchwald <aaron.buchwald56@gmail.com>
|
||||
Abel Nieto <abel.nieto90@gmail.com>
|
||||
Abel Nieto <anietoro@uwaterloo.ca>
|
||||
Adam Babik <a.babik@designfortress.com>
|
||||
Adam Schmideg <adamschmideg@users.noreply.github.com>
|
||||
Aditya <adityasripal@gmail.com>
|
||||
Aditya Arora <arora.aditya520@gmail.com>
|
||||
Adrià Cidre <adria.cidre@gmail.com>
|
||||
Afanasii Kurakin <afanasy@users.noreply.github.com>
|
||||
Afri Schoedon <5chdn@users.noreply.github.com>
|
||||
Agustin Armellini Fischer <armellini13@gmail.com>
|
||||
Ahyun <urbanart2251@gmail.com>
|
||||
Airead <fgh1987168@gmail.com>
|
||||
Alan Chen <alanchchen@users.noreply.github.com>
|
||||
Alejandro Isaza <alejandro.isaza@gmail.com>
|
||||
Aleksey Smyrnov <i@soar.name>
|
||||
Ales Katona <ales@coinbase.com>
|
||||
Alex Beregszaszi <alex@rtfs.hu>
|
||||
Alex Leverington <alex@ethdev.com>
|
||||
Alex Mazalov <mazalov@gmail.com>
|
||||
Alex Pozhilenkov <alex_pozhilenkov@adoriasoft.com>
|
||||
Alex Prut <1648497+alexprut@users.noreply.github.com>
|
||||
Alex Wu <wuyiding@gmail.com>
|
||||
Alexander van der Meij <alexandervdm@users.noreply.github.com>
|
||||
Alexander Yastrebov <yastrebov.alex@gmail.com>
|
||||
Alexandre Van de Sande <alex.vandesande@ethdev.com>
|
||||
Alexey Akhunov <akhounov@gmail.com>
|
||||
Alexey Shekhirin <a.shekhirin@gmail.com>
|
||||
alexwang <39109351+dipingxian2@users.noreply.github.com>
|
||||
Ali Atiia <42751398+aliatiia@users.noreply.github.com>
|
||||
Ali Hajimirza <Ali92hm@users.noreply.github.com>
|
||||
am2rican5 <am2rican5@gmail.com>
|
||||
AmitBRD <60668103+AmitBRD@users.noreply.github.com>
|
||||
Anatole <62328077+a2br@users.noreply.github.com>
|
||||
Andrea Franz <andrea@gravityblast.com>
|
||||
Andrei Maiboroda <andrei@ethereum.org>
|
||||
Andrey Petrov <andrey.petrov@shazow.net>
|
||||
Andrey Petrov <shazow@gmail.com>
|
||||
ANOTHEL <anothel1@naver.com>
|
||||
Antoine Rondelet <rondelet.antoine@gmail.com>
|
||||
Antoine Toulme <atoulme@users.noreply.github.com>
|
||||
Anton Evangelatov <anton.evangelatov@gmail.com>
|
||||
Antonio Salazar Cardozo <savedfastcool@gmail.com>
|
||||
Arba Sasmoyo <arba.sasmoyo@gmail.com>
|
||||
@@ -48,26 +29,19 @@ Armani Ferrante <armaniferrante@berkeley.edu>
|
||||
Armin Braun <me@obrown.io>
|
||||
Aron Fischer <github@aron.guru>
|
||||
atsushi-ishibashi <atsushi.ishibashi@finatext.com>
|
||||
Austin Roberts <code@ausiv.com>
|
||||
ayeowch <ayeowch@gmail.com>
|
||||
b00ris <b00ris@mail.ru>
|
||||
b1ackd0t <blackd0t@protonmail.com>
|
||||
bailantaotao <Edwin@maicoin.com>
|
||||
baizhenxuan <nkbai@163.com>
|
||||
Balaji Shetty Pachai <32358081+balajipachai@users.noreply.github.com>
|
||||
Balint Gabor <balint.g@gmail.com>
|
||||
baptiste-b-pegasys <85155432+baptiste-b-pegasys@users.noreply.github.com>
|
||||
Bas van Kervel <bas@ethdev.com>
|
||||
Benjamin Brent <benjamin@benjaminbrent.com>
|
||||
benma <mbencun@gmail.com>
|
||||
Benoit Verkindt <benoit.verkindt@gmail.com>
|
||||
Binacs <bin646891055@gmail.com>
|
||||
bloonfield <bloonfield@163.com>
|
||||
Bo <bohende@gmail.com>
|
||||
Bo Ye <boy.e.computer.1982@outlook.com>
|
||||
Bob Glickstein <bobg@users.noreply.github.com>
|
||||
Boqin Qin <bobbqqin@bupt.edu.cn>
|
||||
Brandon Harden <b.harden92@gmail.com>
|
||||
Brent <bmperrea@gmail.com>
|
||||
Brian Schroeder <bts@gmail.com>
|
||||
Bruno Škvorc <bruno@skvorc.me>
|
||||
@@ -75,58 +49,36 @@ C. Brown <hackdom@majoolr.io>
|
||||
Caesar Chad <BLUE.WEB.GEEK@gmail.com>
|
||||
Casey Detrio <cdetrio@gmail.com>
|
||||
CDsigma <cdsigma271@gmail.com>
|
||||
Ceelog <chenwei@ceelog.org>
|
||||
Ceyhun Onur <ceyhun.onur@avalabs.org>
|
||||
chabashilah <doumodoumo@gmail.com>
|
||||
changhong <changhong.yu@shanbay.com>
|
||||
Chase Wright <mysticryuujin@gmail.com>
|
||||
Chen Quan <terasum@163.com>
|
||||
Cheng Li <lob4tt@gmail.com>
|
||||
chenglin <910372762@qq.com>
|
||||
chenyufeng <yufengcode@gmail.com>
|
||||
Chris Pacia <ctpacia@gmail.com>
|
||||
Chris Ziogas <ziogaschr@gmail.com>
|
||||
Christian Muehlhaeuser <muesli@gmail.com>
|
||||
Christoph Jentzsch <jentzsch.software@gmail.com>
|
||||
chuwt <weitaochu@gmail.com>
|
||||
cong <ackratos@users.noreply.github.com>
|
||||
Connor Stein <connor.stein@mail.mcgill.ca>
|
||||
Corey Lin <514971757@qq.com>
|
||||
courtier <derinilter@gmail.com>
|
||||
cpusoft <cpusoft@live.com>
|
||||
Crispin Flowerday <crispin@bitso.com>
|
||||
croath <croathliu@gmail.com>
|
||||
cui <523516579@qq.com>
|
||||
Dan DeGreef <dan.degreef@gmail.com>
|
||||
Dan Kinsley <dan@joincivil.com>
|
||||
Dan Sosedoff <dan.sosedoff@gmail.com>
|
||||
Daniel A. Nagy <nagy.da@gmail.com>
|
||||
Daniel Perez <daniel@perez.sh>
|
||||
Daniel Sloof <goapsychadelic@gmail.com>
|
||||
Darioush Jalali <darioush.jalali@avalabs.org>
|
||||
Darrel Herbst <dherbst@gmail.com>
|
||||
Dave Appleton <calistralabs@gmail.com>
|
||||
Dave McGregor <dave.s.mcgregor@gmail.com>
|
||||
David Cai <davidcai1993@yahoo.com>
|
||||
David Huie <dahuie@gmail.com>
|
||||
Denver <aeharvlee@gmail.com>
|
||||
Derek Chiang <me@derekchiang.com>
|
||||
Derek Gottfrid <derek@codecubed.com>
|
||||
Di Peng <pendyaaa@gmail.com>
|
||||
Diederik Loerakker <proto@protolambda.com>
|
||||
Diego Siqueira <DiSiqueira@users.noreply.github.com>
|
||||
Diep Pham <mrfavadi@gmail.com>
|
||||
dipingxian2 <39109351+dipingxian2@users.noreply.github.com>
|
||||
divergencetech <94644849+divergencetech@users.noreply.github.com>
|
||||
dm4 <sunrisedm4@gmail.com>
|
||||
Dmitrij Koniajev <dimchansky@gmail.com>
|
||||
Dmitry Shulyak <yashulyak@gmail.com>
|
||||
Dmitry Zenovich <dzenovich@gmail.com>
|
||||
Domino Valdano <dominoplural@gmail.com>
|
||||
Domino Valdano <jeff@okcupid.com>
|
||||
Dragan Milic <dragan@netice9.com>
|
||||
dragonvslinux <35779158+dragononcrypto@users.noreply.github.com>
|
||||
Edgar Aroutiounian <edgar.factorial@gmail.com>
|
||||
Eduard S <eduardsanou@posteo.net>
|
||||
Egon Elbre <egonelbre@gmail.com>
|
||||
Elad <theman@elad.im>
|
||||
Eli <elihanover@yahoo.com>
|
||||
@@ -134,189 +86,131 @@ Elias Naur <elias.naur@gmail.com>
|
||||
Elliot Shepherd <elliot@identitii.com>
|
||||
Emil <mursalimovemeel@gmail.com>
|
||||
emile <emile@users.noreply.github.com>
|
||||
Emmanuel T Odeke <odeke@ualberta.ca>
|
||||
Eng Zer Jun <engzerjun@gmail.com>
|
||||
Enrique Fynn <enriquefynn@gmail.com>
|
||||
Enrique Fynn <me@enriquefynn.com>
|
||||
Enrique Ortiz <hi@enriqueortiz.dev>
|
||||
EOS Classic <info@eos-classic.io>
|
||||
Erichin <erichinbato@gmail.com>
|
||||
Ernesto del Toro <ernesto.deltoro@gmail.com>
|
||||
Ethan Buchman <ethan@coinculture.info>
|
||||
ethersphere <thesw@rm.eth>
|
||||
Eugene Lepeico <eugenelepeico@gmail.com>
|
||||
Eugene Valeyev <evgen.povt@gmail.com>
|
||||
Evangelos Pappas <epappas@evalonlabs.com>
|
||||
Everton Fraga <ev@ethereum.org>
|
||||
Evgeny <awesome.observer@yandex.com>
|
||||
Evgeny Danilenko <6655321@bk.ru>
|
||||
evgk <evgeniy.kamyshev@gmail.com>
|
||||
Evolution404 <35091674+Evolution404@users.noreply.github.com>
|
||||
EXEC <execvy@gmail.com>
|
||||
Fabian Vogelsteller <fabian@frozeman.de>
|
||||
Fabio Barone <fabio.barone.co@gmail.com>
|
||||
Fabio Berger <fabioberger1991@gmail.com>
|
||||
FaceHo <facehoshi@gmail.com>
|
||||
Felipe Strozberg <48066928+FelStroz@users.noreply.github.com>
|
||||
Felix Lange <fjl@twurst.com>
|
||||
Ferenc Szabo <frncmx@gmail.com>
|
||||
ferhat elmas <elmas.ferhat@gmail.com>
|
||||
Ferran Borreguero <ferranbt@protonmail.com>
|
||||
Fiisio <liangcszzu@163.com>
|
||||
Fire Man <55934298+basdevelop@users.noreply.github.com>
|
||||
flowerofdream <775654398@qq.com>
|
||||
fomotrader <82184770+fomotrader@users.noreply.github.com>
|
||||
ForLina <471133417@qq.com>
|
||||
Frank Szendzielarz <33515470+FrankSzendzielarz@users.noreply.github.com>
|
||||
Frank Wang <eternnoir@gmail.com>
|
||||
Franklin <mr_franklin@126.com>
|
||||
Furkan KAMACI <furkankamaci@gmail.com>
|
||||
Fuyang Deng <dengfuyang@outlook.com>
|
||||
GagziW <leon.stanko@rwth-aachen.de>
|
||||
Gary Rong <garyrong0905@gmail.com>
|
||||
Gautam Botrel <gautam.botrel@gmail.com>
|
||||
George Ornbo <george@shapeshed.com>
|
||||
Giuseppe Bertone <bertone.giuseppe@gmail.com>
|
||||
Greg Colvin <greg@colvin.org>
|
||||
Gregg Dourgarian <greggd@tempworks.com>
|
||||
Gregory Markou <16929357+GregTheGreek@users.noreply.github.com>
|
||||
Guifel <toowik@gmail.com>
|
||||
Guilherme Salgado <gsalgado@gmail.com>
|
||||
Guillaume Ballet <gballet@gmail.com>
|
||||
Guillaume Nicolas <guin56@gmail.com>
|
||||
GuiltyMorishita <morilliantblue@gmail.com>
|
||||
Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com>
|
||||
Gus <yo@soygus.com>
|
||||
Gustav Simonsson <gustav.simonsson@gmail.com>
|
||||
Gísli Kristjánsson <gislik@hamstur.is>
|
||||
Ha ĐANG <dvietha@gmail.com>
|
||||
HackyMiner <hackyminer@gmail.com>
|
||||
hadv <dvietha@gmail.com>
|
||||
Hanjiang Yu <delacroix.yu@gmail.com>
|
||||
Hao Bryan Cheng <haobcheng@gmail.com>
|
||||
Hao Duan <duanhao0814@gmail.com>
|
||||
HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
|
||||
Harry Dutton <me@bytejedi.com>
|
||||
haryu703 <34744512+haryu703@users.noreply.github.com>
|
||||
Hendrik Hofstadt <hendrik@nexantic.com>
|
||||
Henning Diedrich <hd@eonblast.com>
|
||||
henopied <13500516+henopied@users.noreply.github.com>
|
||||
hero5512 <lvshuaino@gmail.com>
|
||||
holisticode <holistic.computing@gmail.com>
|
||||
Hongbin Mao <hello2mao@gmail.com>
|
||||
Hsien-Tang Kao <htkao@pm.me>
|
||||
hsyodyssey <47173566+hsyodyssey@users.noreply.github.com>
|
||||
Husam Ibrahim <39692071+HusamIbrahim@users.noreply.github.com>
|
||||
Hwanjo Heo <34005989+hwanjo@users.noreply.github.com>
|
||||
hydai <z54981220@gmail.com>
|
||||
Hyung-Kyu Hqueue Choi <hyungkyu.choi@gmail.com>
|
||||
Håvard Anda Estensen <haavard.ae@gmail.com>
|
||||
Ian Macalinao <me@ian.pw>
|
||||
Ian Norden <iannordenn@gmail.com>
|
||||
icodezjb <icodezjb@163.com>
|
||||
Ikko Ashimine <eltociear@gmail.com>
|
||||
Ilan Gitter <8359193+gitteri@users.noreply.github.com>
|
||||
ImanSharaf <78227895+ImanSharaf@users.noreply.github.com>
|
||||
Isidoro Ghezzi <isidoro.ghezzi@icloud.com>
|
||||
Iskander (Alex) Sharipov <quasilyte@gmail.com>
|
||||
Ivan Bogatyy <bogatyi@gmail.com>
|
||||
Ivan Daniluk <ivan.daniluk@gmail.com>
|
||||
Ivo Georgiev <ivo@strem.io>
|
||||
jacksoom <lifengliu1994@gmail.com>
|
||||
Jae Kwon <jkwon.work@gmail.com>
|
||||
James Prestwich <10149425+prestwich@users.noreply.github.com>
|
||||
Jamie Pitts <james.pitts@gmail.com>
|
||||
Janoš Guljaš <janos@resenje.org>
|
||||
Jared Wasinger <j-wasinger@hotmail.com>
|
||||
Janos Guljas <janos@resenje.org>
|
||||
Janoš Guljaš <janos@users.noreply.github.com>
|
||||
Jason Carver <jacarver@linkedin.com>
|
||||
Javier Peletier <jm@epiclabs.io>
|
||||
Javier Peletier <jpeletier@users.noreply.github.com>
|
||||
Javier Sagredo <jasataco@gmail.com>
|
||||
Jay <codeholic.arena@gmail.com>
|
||||
Jay Guo <guojiannan1101@gmail.com>
|
||||
Jaynti Kanani <jdkanani@gmail.com>
|
||||
Jeff Prestes <jeffprestes@gmail.com>
|
||||
Jeff R. Allen <jra@nella.org>
|
||||
Jeff Wentworth <jeff@curvegrid.com>
|
||||
Jeffery Robert Walsh <rlxrlps@gmail.com>
|
||||
Jeffrey Wilcke <jeffrey@ethereum.org>
|
||||
Jens Agerberg <github@agerberg.me>
|
||||
Jeremy McNevin <jeremy.mcnevin@optum.com>
|
||||
Jeremy Schlatter <jeremy.schlatter@gmail.com>
|
||||
Jerzy Lasyk <jerzylasyk@gmail.com>
|
||||
Jesse Tane <jesse.tane@gmail.com>
|
||||
Jia Chenhui <jiachenhui1989@gmail.com>
|
||||
Jim McDonald <Jim@mcdee.net>
|
||||
jk-jeongkyun <45347815+jeongkyun-oh@users.noreply.github.com>
|
||||
jkcomment <jkcomment@gmail.com>
|
||||
JoeGruffins <34998433+JoeGruffins@users.noreply.github.com>
|
||||
Joel Burget <joelburget@gmail.com>
|
||||
John C. Vernaleo <john@netpurgatory.com>
|
||||
John Difool <johndifoolpi@gmail.com>
|
||||
Johns Beharry <johns@peakshift.com>
|
||||
Jonas <felberj@users.noreply.github.com>
|
||||
Jonathan Brown <jbrown@bluedroplet.com>
|
||||
Jonathan Chappelow <chappjc@users.noreply.github.com>
|
||||
Jonathan Gimeno <jgimeno@gmail.com>
|
||||
JoranHonig <JoranHonig@users.noreply.github.com>
|
||||
Jordan Krage <jmank88@gmail.com>
|
||||
Jorropo <jorropo.pgm@gmail.com>
|
||||
Joseph Chow <ethereum@outlook.com>
|
||||
Joshua Colvin <jcolvin@offchainlabs.com>
|
||||
Joshua Gutow <jbgutow@gmail.com>
|
||||
jovijovi <mageyul@hotmail.com>
|
||||
jtakalai <juuso.takalainen@streamr.com>
|
||||
JU HYEONG PARK <dkdkajej@gmail.com>
|
||||
Julian Y <jyap808@users.noreply.github.com>
|
||||
Justin Clark-Casey <justincc@justincc.org>
|
||||
Justin Drake <drakefjustin@gmail.com>
|
||||
Justus <jus@gtsbr.org>
|
||||
Kawashima <91420903+sscodereth@users.noreply.github.com>
|
||||
jwasinger <j-wasinger@hotmail.com>
|
||||
ken10100147 <sunhongping@kanjian.com>
|
||||
Kenji Siu <kenji@isuntv.com>
|
||||
Kenso Trabing <kenso.trabing@bloomwebsite.com>
|
||||
Kenso Trabing <ktrabing@acm.org>
|
||||
Kevin <denk.kevin@web.de>
|
||||
kevin.xu <cming.xu@gmail.com>
|
||||
KibGzr <kibgzr@gmail.com>
|
||||
kiel barry <kiel.j.barry@gmail.com>
|
||||
kilic <onurkilic1004@gmail.com>
|
||||
kimmylin <30611210+kimmylin@users.noreply.github.com>
|
||||
Kitten King <53072918+kittenking@users.noreply.github.com>
|
||||
knarfeh <hejun1874@gmail.com>
|
||||
Kobi Gurkan <kobigurk@gmail.com>
|
||||
komika <komika@komika.org>
|
||||
Konrad Feldmeier <konrad@brainbot.com>
|
||||
Kris Shinn <raggamuffin.music@gmail.com>
|
||||
Kristofer Peterson <svenski123@users.noreply.github.com>
|
||||
Kumar Anirudha <mail@anirudha.dev>
|
||||
Kurkó Mihály <kurkomisi@users.noreply.github.com>
|
||||
Kushagra Sharma <ksharm01@gmail.com>
|
||||
Kwuaint <34888408+kwuaint@users.noreply.github.com>
|
||||
Kyuntae Ethan Kim <ethan.kyuntae.kim@gmail.com>
|
||||
Lee Bousfield <ljbousfield@gmail.com>
|
||||
ledgerwatch <akhounov@gmail.com>
|
||||
Lefteris Karapetsas <lefteris@refu.co>
|
||||
Leif Jurvetson <leijurv@gmail.com>
|
||||
Leo Shklovskii <leo@thermopylae.net>
|
||||
LeoLiao <leofantast@gmail.com>
|
||||
Lewis Marshall <lewis@lmars.net>
|
||||
lhendre <lhendre2@gmail.com>
|
||||
Li Dongwei <lidw1988@126.com>
|
||||
Liang Ma <liangma.ul@gmail.com>
|
||||
Liang Ma <liangma@liangbit.com>
|
||||
Liang ZOU <liang.d.zou@gmail.com>
|
||||
libby kent <viskovitzzz@gmail.com>
|
||||
libotony <liboliqi@gmail.com>
|
||||
LieutenantRoger <dijsky_2015@hotmail.com>
|
||||
ligi <ligi@ligi.de>
|
||||
Lio李欧 <lionello@users.noreply.github.com>
|
||||
lmittmann <lmittmann@users.noreply.github.com>
|
||||
Lorenzo Manacorda <lorenzo@kinvolk.io>
|
||||
Louis Holbrook <dev@holbrook.no>
|
||||
Luca Zeug <luclu@users.noreply.github.com>
|
||||
Lucas Hendren <lhendre2@gmail.com>
|
||||
lzhfromustc <43191155+lzhfromustc@users.noreply.github.com>
|
||||
Magicking <s@6120.eu>
|
||||
manlio <manlio.poltronieri@gmail.com>
|
||||
Maran Hidskes <maran.hidskes@gmail.com>
|
||||
Marek Kotewicz <marek.kotewicz@gmail.com>
|
||||
Mariano Cortesi <mcortesi@gmail.com>
|
||||
Marius van der Wijden <m.vanderwijden@live.de>
|
||||
Mark <markya0616@gmail.com>
|
||||
Mark Rushakoff <mark.rushakoff@gmail.com>
|
||||
@@ -324,193 +218,108 @@ mark.lin <mark@maicoin.com>
|
||||
Martin Alex Philip Dawson <u1356770@gmail.com>
|
||||
Martin Holst Swende <martin@swende.se>
|
||||
Martin Klepsch <martinklepsch@googlemail.com>
|
||||
Martin Lundfall <martin.lundfall@protonmail.com>
|
||||
Martin Michlmayr <tbm@cyrius.com>
|
||||
Martin Redmond <21436+reds@users.noreply.github.com>
|
||||
Mason Fischer <mason@kissr.co>
|
||||
Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
|
||||
Mats Julian Olsen <mats@plysjbyen.net>
|
||||
Matt Garnett <14004106+lightclient@users.noreply.github.com>
|
||||
Matt K <1036969+mkrump@users.noreply.github.com>
|
||||
Matthew Di Ferrante <mattdf@users.noreply.github.com>
|
||||
Matthew Halpern <matthalp@gmail.com>
|
||||
Matthew Halpern <matthalp@google.com>
|
||||
Matthew Wampler-Doty <matthew.wampler.doty@gmail.com>
|
||||
Max Sistemich <mafrasi2@googlemail.com>
|
||||
Maxim Zhiburt <zhiburt@gmail.com>
|
||||
Maximilian Meister <mmeister@suse.de>
|
||||
me020523 <me020523@gmail.com>
|
||||
Melvin Junhee Woo <melvin.woo@groundx.xyz>
|
||||
meowsbits <b5c6@protonmail.com>
|
||||
Micah Zoltu <micah@zoltu.net>
|
||||
Michael Forney <mforney@mforney.org>
|
||||
Michael Riabzev <michael@starkware.co>
|
||||
Michael Ruminer <michael.ruminer+github@gmail.com>
|
||||
michael1011 <me@michael1011.at>
|
||||
Miguel Mota <miguelmota2@gmail.com>
|
||||
Mike Burr <mburr@nightmare.com>
|
||||
Mikhail Mikheev <mmvsha73@gmail.com>
|
||||
milesvant <milesvant@gmail.com>
|
||||
Miro <mirokuratczyk@users.noreply.github.com>
|
||||
Miya Chen <miyatlchen@gmail.com>
|
||||
Mohanson <mohanson@outlook.com>
|
||||
mr_franklin <mr_franklin@126.com>
|
||||
Mudit Gupta <guptamudit@ymail.com>
|
||||
Mymskmkt <1847234666@qq.com>
|
||||
Nalin Bhardwaj <nalinbhardwaj@nibnalin.me>
|
||||
Natsu Kagami <natsukagami@gmail.com>
|
||||
Nchinda Nchinda <nchinda2@gmail.com>
|
||||
nebojsa94 <nebojsa94@users.noreply.github.com>
|
||||
necaremus <necaremus@gmail.com>
|
||||
nedifi <103940716+nedifi@users.noreply.github.com>
|
||||
needkane <604476380@qq.com>
|
||||
Nguyen Kien Trung <trung.n.k@gmail.com>
|
||||
Nguyen Sy Thanh Son <thanhson1085@gmail.com>
|
||||
Nic Jansma <nic@nicj.net>
|
||||
Nick Dodson <silentcicero@outlook.com>
|
||||
Nick Johnson <arachnid@notdot.net>
|
||||
Nicolas Feignon <nfeignon@gmail.com>
|
||||
Nicolas Guillaume <gunicolas@sqli.com>
|
||||
Nikita Kozhemyakin <enginegl.ec@gmail.com>
|
||||
Nikola Madjarevic <nikola.madjarevic@gmail.com>
|
||||
Nilesh Trivedi <nilesh@hypertrack.io>
|
||||
Nimrod Gutman <nimrod.gutman@gmail.com>
|
||||
Nishant Das <nishdas93@gmail.com>
|
||||
njupt-moon <1015041018@njupt.edu.cn>
|
||||
nkbai <nkbai@163.com>
|
||||
noam-alchemy <76969113+noam-alchemy@users.noreply.github.com>
|
||||
nobody <ddean2009@163.com>
|
||||
Noman <noman@noman.land>
|
||||
nujabes403 <nujabes403@gmail.com>
|
||||
Nye Liu <nyet@nyet.org>
|
||||
Oleg Kovalov <iamolegkovalov@gmail.com>
|
||||
Oli Bye <olibye@users.noreply.github.com>
|
||||
Oliver Tale-Yazdi <oliver@perun.network>
|
||||
Olivier Hervieu <olivier.hervieu@gmail.com>
|
||||
Or Neeman <oneeman@gmail.com>
|
||||
Osoro Bironga <fanosoro@gmail.com>
|
||||
Osuke <arget-fee.free.dgm@hotmail.co.jp>
|
||||
Pantelis Peslis <pespantelis@gmail.com>
|
||||
Pascal Dierich <pascal@merkleplant.xyz>
|
||||
Patrick O'Grady <prohb125@gmail.com>
|
||||
Pau <pau@dabax.net>
|
||||
Paul Berg <hello@paulrberg.com>
|
||||
Paul Litvak <litvakpol@012.net.il>
|
||||
Paul-Armand Verhaegen <paularmand.verhaegen@gmail.com>
|
||||
Paulo L F Casaretto <pcasaretto@gmail.com>
|
||||
Paweł Bylica <chfast@gmail.com>
|
||||
Pedro Gomes <otherview@gmail.com>
|
||||
Pedro Pombeiro <PombeirP@users.noreply.github.com>
|
||||
Peter Broadhurst <peter@themumbles.net>
|
||||
peter cresswell <pcresswell@gmail.com>
|
||||
Peter Pratscher <pratscher@gmail.com>
|
||||
Peter Simard <petesimard56@gmail.com>
|
||||
Petr Mikusek <petr@mikusek.info>
|
||||
Philip Schlump <pschlump@gmail.com>
|
||||
Pierre Neter <pierreneter@gmail.com>
|
||||
Pierre R <p.rousset@gmail.com>
|
||||
piersy <pierspowlesland@gmail.com>
|
||||
PilkyuJung <anothel1@naver.com>
|
||||
Piotr Dyraga <piotr.dyraga@keep.network>
|
||||
ploui <64719999+ploui@users.noreply.github.com>
|
||||
Preston Van Loon <preston@prysmaticlabs.com>
|
||||
Prince Sinha <sinhaprince013@gmail.com>
|
||||
protolambda <proto@protolambda.com>
|
||||
Péter Szilágyi <peterke@gmail.com>
|
||||
qd-ethan <31876119+qdgogogo@users.noreply.github.com>
|
||||
Qian Bin <cola.tin.com@gmail.com>
|
||||
Quest Henkart <qhenkart@gmail.com>
|
||||
Rachel Franks <nfranks@protonmail.com>
|
||||
Rafael Matias <rafael@skyle.net>
|
||||
Raghav Sood <raghavsood@gmail.com>
|
||||
Ralph Caraveo <deckarep@gmail.com>
|
||||
Ralph Caraveo III <deckarep@gmail.com>
|
||||
Ramesh Nair <ram@hiddentao.com>
|
||||
rangzen <public@l-homme.com>
|
||||
reinerRubin <tolstov.georgij@gmail.com>
|
||||
Rene Lubov <41963722+renaynay@users.noreply.github.com>
|
||||
rhaps107 <dod-source@yandex.ru>
|
||||
Ricardo Catalinas Jiménez <r@untroubled.be>
|
||||
Ricardo Domingos <ricardohsd@gmail.com>
|
||||
Richard Hart <richardhart92@gmail.com>
|
||||
Rick <rick.no@groundx.xyz>
|
||||
RJ Catalano <catalanor0220@gmail.com>
|
||||
Rob <robert@rojotek.com>
|
||||
Rob Mulholand <rmulholand@8thlight.com>
|
||||
Robert Zaremba <robert@zaremba.ch>
|
||||
Robert Zaremba <robert.zaremba@scale-it.pl>
|
||||
Roc Yu <rociiu0112@gmail.com>
|
||||
Roman Mazalov <83914728+gopherxyz@users.noreply.github.com>
|
||||
Ross <9055337+Chadsr@users.noreply.github.com>
|
||||
Runchao Han <elvisage941102@gmail.com>
|
||||
Russ Cox <rsc@golang.org>
|
||||
Ryan Schneider <ryanleeschneider@gmail.com>
|
||||
ryanc414 <ryan@tokencard.io>
|
||||
Rémy Roy <remyroy@remyroy.com>
|
||||
S. Matthew English <s-matthew-english@users.noreply.github.com>
|
||||
salanfe <salanfe@users.noreply.github.com>
|
||||
Sam <39165351+Xia-Sam@users.noreply.github.com>
|
||||
Sammy Libre <7374093+sammy007@users.noreply.github.com>
|
||||
Samuel Marks <samuelmarks@gmail.com>
|
||||
sanskarkhare <sanskarkhare47@gmail.com>
|
||||
Sarlor <kinsleer@outlook.com>
|
||||
Sasuke1964 <neilperry1964@gmail.com>
|
||||
Satpal <28562234+SatpalSandhu61@users.noreply.github.com>
|
||||
Saulius Grigaitis <saulius@necolt.com>
|
||||
Sean <darcys22@gmail.com>
|
||||
Serhat Şevki Dinçer <jfcgauss@gmail.com>
|
||||
Shane Bammel <sjb933@gmail.com>
|
||||
shawn <36943337+lxex@users.noreply.github.com>
|
||||
shigeyuki azuchi <azuchi@chaintope.com>
|
||||
Shihao Xia <charlesxsh@hotmail.com>
|
||||
Shiming <codingmylife@gmail.com>
|
||||
Sheldon <11510383@mail.sustc.edu.cn>
|
||||
Sheldon <374662347@qq.com>
|
||||
Shintaro Kaneko <kaneshin0120@gmail.com>
|
||||
shiqinfeng1 <150627601@qq.com>
|
||||
Shuai Qi <qishuai231@gmail.com>
|
||||
Shude Li <islishude@gmail.com>
|
||||
Shunsuke Watanabe <ww.shunsuke@gmail.com>
|
||||
silence <wangsai.silence@qq.com>
|
||||
Simon Jentzsch <simon@slock.it>
|
||||
Sina Mahmoodi <1591639+s1na@users.noreply.github.com>
|
||||
sixdays <lj491685571@126.com>
|
||||
SjonHortensius <SjonHortensius@users.noreply.github.com>
|
||||
Slava Karpenko <slavikus@gmail.com>
|
||||
slumber1122 <slumber1122@gmail.com>
|
||||
Smilenator <yurivanenko@yandex.ru>
|
||||
soc1c <soc1c@users.noreply.github.com>
|
||||
Sorin Neacsu <sorin.neacsu@gmail.com>
|
||||
Sparty <vignesh.crysis@gmail.com>
|
||||
Stein Dekker <dekker.stein@gmail.com>
|
||||
Steve Gattuso <steve@stevegattuso.me>
|
||||
Steve Ruckdashel <steve.ruckdashel@gmail.com>
|
||||
Steve Waldman <swaldman@mchange.com>
|
||||
Steven E. Harris <seh@panix.com>
|
||||
Steven Roose <stevenroose@gmail.com>
|
||||
stompesi <stompesi@gmail.com>
|
||||
stormpang <jialinpeng@vip.qq.com>
|
||||
sunxiaojun2014 <sunxiaojun-xy@360.cn>
|
||||
Suriyaa Sundararuban <isc.suriyaa@gmail.com>
|
||||
Sylvain Laurent <s@6120.eu>
|
||||
Taeik Lim <sibera21@gmail.com>
|
||||
tamirms <tamir@trello.com>
|
||||
Tangui Clairet <tangui.clairet@gmail.com>
|
||||
Tatsuya Shimoda <tacoo@users.noreply.github.com>
|
||||
Taylor Gerring <taylor.gerring@gmail.com>
|
||||
TColl <38299499+TColl@users.noreply.github.com>
|
||||
terasum <terasum@163.com>
|
||||
tgyKomgo <52910426+tgyKomgo@users.noreply.github.com>
|
||||
Thad Guidry <thadguidry@gmail.com>
|
||||
Thomas Bocek <tom@tomp2p.net>
|
||||
thomasmodeneis <thomas.modeneis@gmail.com>
|
||||
thumb8432 <thumb8432@gmail.com>
|
||||
Ti Zhou <tizhou1986@gmail.com>
|
||||
tia-99 <67107070+tia-99@users.noreply.github.com>
|
||||
Tim Cooijmans <timcooijmans@gmail.com>
|
||||
Tobias Hildebrandt <79341166+tobias-hildebrandt@users.noreply.github.com>
|
||||
Tosh Camille <tochecamille@gmail.com>
|
||||
tsarpaul <Litvakpol@012.net.il>
|
||||
Tyler Chambers <2775339+tylerchambers@users.noreply.github.com>
|
||||
tzapu <alex@tzapu.com>
|
||||
ucwong <ucwong@126.com>
|
||||
uji <49834542+uji@users.noreply.github.com>
|
||||
ult-bobonovski <alex@ultiledger.io>
|
||||
Valentin Trinqué <ValentinTrinque@users.noreply.github.com>
|
||||
Valentin Wüstholz <wuestholz@gmail.com>
|
||||
Vedhavyas Singareddi <vedhavyas.singareddi@gmail.com>
|
||||
Victor Farazdagi <simple.square@gmail.com>
|
||||
@@ -521,71 +330,40 @@ Ville Sundell <github@solarius.fi>
|
||||
vim88 <vim88vim88@gmail.com>
|
||||
Vincent G <caktux@gmail.com>
|
||||
Vincent Serpoul <vincent@serpoul.com>
|
||||
Vinod Damle <vdamle@users.noreply.github.com>
|
||||
Vitalik Buterin <v@buterin.com>
|
||||
Vitaly Bogdanov <vsbogd@gmail.com>
|
||||
Vitaly V <vvelikodny@gmail.com>
|
||||
Vivek Anand <vivekanand1101@users.noreply.github.com>
|
||||
Vlad <gluk256@gmail.com>
|
||||
Vlad Bokov <razum2um@mail.ru>
|
||||
Vlad Gluhovsky <gluk256@gmail.com>
|
||||
Ward Bradt <wardbradt5@gmail.com>
|
||||
Water <44689567+codeoneline@users.noreply.github.com>
|
||||
wbt <wbt@users.noreply.github.com>
|
||||
Vlad Gluhovsky <gluk256@users.noreply.github.com>
|
||||
weimumu <934657014@qq.com>
|
||||
Wenbiao Zheng <delweng@gmail.com>
|
||||
Wenshao Zhong <wzhong20@uic.edu>
|
||||
Will Villanueva <hello@willvillanueva.com>
|
||||
William Morriss <wjmelements@gmail.com>
|
||||
William Setzer <bootstrapsetzer@gmail.com>
|
||||
williambannas <wrschwartz@wpi.edu>
|
||||
wuff1996 <33193253+wuff1996@users.noreply.github.com>
|
||||
Wuxiang <wuxiangzhou2010@gmail.com>
|
||||
Xiaobing Jiang <s7v7nislands@gmail.com>
|
||||
xiekeyang <xiekeyang@users.noreply.github.com>
|
||||
xincaosu <xincaosu@126.com>
|
||||
xinluyin <31590468+xinluyin@users.noreply.github.com>
|
||||
Xudong Liu <33193253+r1cs@users.noreply.github.com>
|
||||
xwjack <XWJACK@users.noreply.github.com>
|
||||
yahtoo <yahtoo.ma@gmail.com>
|
||||
Yang Hau <vulxj0j8j8@gmail.com>
|
||||
YaoZengzeng <yaozengzeng@zju.edu.cn>
|
||||
YH-Zhou <yanhong.zhou05@gmail.com>
|
||||
Yihau Chen <a122092487@gmail.com>
|
||||
Yohann Léon <sybiload@gmail.com>
|
||||
Yoichi Hirai <i@yoichihirai.com>
|
||||
Yole <007yuyue@gmail.com>
|
||||
Yondon Fu <yondon.fu@gmail.com>
|
||||
YOSHIDA Masanori <masanori.yoshida@gmail.com>
|
||||
yoza <yoza.is12s@gmail.com>
|
||||
yumiel yoomee1313 <yumiel.ko@groundx.xyz>
|
||||
Yusup <awklsgrep@gmail.com>
|
||||
yutianwu <wzxingbupt@gmail.com>
|
||||
ywzqwwt <39263032+ywzqwwt@users.noreply.github.com>
|
||||
zaccoding <zaccoding725@gmail.com>
|
||||
Zach <zach.ramsay@gmail.com>
|
||||
Zachinquarantine <Zachinquarantine@protonmail.com>
|
||||
zah <zahary@gmail.com>
|
||||
Zahoor Mohamed <zahoor@zahoor.in>
|
||||
Zak Cole <zak@beattiecole.com>
|
||||
zcheng9 <zcheng9@hawk.iit.edu>
|
||||
zer0to0ne <36526113+zer0to0ne@users.noreply.github.com>
|
||||
zgfzgf <48779939+zgfzgf@users.noreply.github.com>
|
||||
Zhang Zhuo <mycinbrin@gmail.com>
|
||||
zhangsoledad <787953403@qq.com>
|
||||
zhaochonghe <41711151+zhaochonghe@users.noreply.github.com>
|
||||
Zhenguo Niu <Niu.ZGlinux@gmail.com>
|
||||
zhiqiangxu <652732310@qq.com>
|
||||
Zhou Zhiyao <ZHOU0250@e.ntu.edu.sg>
|
||||
Ziyuan Zhong <zzy.albert@163.com>
|
||||
Zoe Nolan <github@zoenolan.org>
|
||||
Zou Guangxian <zouguangxian@gmail.com>
|
||||
Zsolt Felföldi <zsfelfoldi@gmail.com>
|
||||
Łukasz Kurowski <crackcomm@users.noreply.github.com>
|
||||
Łukasz Zimnoch <lukaszzimnoch1994@gmail.com>
|
||||
ΞTHΞЯSPHΞЯΞ <{viktor.tron,nagydani,zsfelfoldi}@gmail.com>
|
||||
Максим Чусовлянов <mchusovlianov@gmail.com>
|
||||
大彬 <hz_stb@163.com>
|
||||
沉风 <myself659@users.noreply.github.com>
|
||||
贺鹏飞 <hpf@hackerful.cn>
|
||||
陈佳 <chenjiablog@gmail.com>
|
||||
유용환 <33824408+eric-yoo@users.noreply.github.com>
|
||||
|
||||
@@ -488,11 +488,3 @@ func TestCall(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCrashers contains some strings which previously caused the abi codec to crash.
|
||||
func TestCrashers(t *testing.T) {
|
||||
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"_1"}]}]}]`))
|
||||
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"&"}]}]}]`))
|
||||
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"----"}]}]}]`))
|
||||
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"foo.Bar"}]}]}]`))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -23,8 +23,6 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
@@ -175,9 +173,6 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
|
||||
if err != nil {
|
||||
return Type{}, err
|
||||
}
|
||||
if !isValidFieldName(fieldName) {
|
||||
return Type{}, fmt.Errorf("field %d has invalid name", idx)
|
||||
}
|
||||
overloadedNames[fieldName] = fieldName
|
||||
fields = append(fields, reflect.StructField{
|
||||
Name: fieldName, // reflect.StructOf will panic for any exported field.
|
||||
@@ -404,30 +399,3 @@ func getTypeSize(t Type) int {
|
||||
}
|
||||
return 32
|
||||
}
|
||||
|
||||
// isLetter reports whether a given 'rune' is classified as a Letter.
|
||||
// This method is copied from reflect/type.go
|
||||
func isLetter(ch rune) bool {
|
||||
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch)
|
||||
}
|
||||
|
||||
// isValidFieldName checks if a string is a valid (struct) field name or not.
|
||||
//
|
||||
// According to the language spec, a field name should be an identifier.
|
||||
//
|
||||
// identifier = letter { letter | unicode_digit } .
|
||||
// letter = unicode_letter | "_" .
|
||||
// This method is copied from reflect/type.go
|
||||
func isValidFieldName(fieldName string) bool {
|
||||
for i, c := range fieldName {
|
||||
if i == 0 && !isLetter(c) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !(isLetter(c) || unicode.IsDigit(c)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return len(fieldName) > 0
|
||||
}
|
||||
|
||||
14
build/ci.go
14
build/ci.go
@@ -132,12 +132,12 @@ var (
|
||||
// Note: the following Ubuntu releases have been officially deprecated on Launchpad:
|
||||
// wily, yakkety, zesty, artful, cosmic, disco, eoan, groovy, hirsuite
|
||||
debDistroGoBoots = map[string]string{
|
||||
"trusty": "golang-1.11", // EOL: 04/2024
|
||||
"xenial": "golang-go", // EOL: 04/2026
|
||||
"bionic": "golang-go", // EOL: 04/2028
|
||||
"focal": "golang-go", // EOL: 04/2030
|
||||
"impish": "golang-go", // EOL: 07/2022
|
||||
"jammy": "golang-go", // EOL: 04/2032
|
||||
"trusty": "golang-1.11", // EOL: 04/2024
|
||||
"xenial": "golang-go", // EOL: 04/2026
|
||||
"bionic": "golang-go", // EOL: 04/2028
|
||||
"focal": "golang-go", // EOL: 04/2030
|
||||
"impish": "golang-go", // EOL: 07/2022
|
||||
"jammy": "golang-go", // EOL: 04/2032
|
||||
//"kinetic": "golang-go", // EOL: 07/2023
|
||||
}
|
||||
|
||||
@@ -461,7 +461,7 @@ func maybeSkipArchive(env build.Environment) {
|
||||
log.Printf("skipping archive creation because this is a cron job")
|
||||
os.Exit(0)
|
||||
}
|
||||
if env.Branch != "master" && !strings.HasPrefix(env.Tag, "v1.") {
|
||||
if env.Branch != "master" && env.Branch != "buildbot-testing" && !strings.HasPrefix(env.Tag, "v1.") {
|
||||
log.Printf("skipping archive creation because branch %q, tag %q is not on the inclusion list", env.Branch, env.Tag)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build none
|
||||
// +build none
|
||||
|
||||
/*
|
||||
@@ -68,9 +67,7 @@ var (
|
||||
"common/bitutil/bitutil",
|
||||
"common/prque/",
|
||||
"consensus/ethash/xor.go",
|
||||
"crypto/blake2b/",
|
||||
"crypto/bn256/",
|
||||
"crypto/bls12381/",
|
||||
"crypto/ecies/",
|
||||
"graphql/graphiql.go",
|
||||
"internal/jsre/deps",
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -21,9 +21,11 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common/compiler"
|
||||
@@ -57,6 +59,24 @@ var (
|
||||
Name: "combined-json",
|
||||
Usage: "Path to the combined-json file generated by compiler",
|
||||
}
|
||||
solFlag = cli.StringFlag{
|
||||
Name: "sol",
|
||||
Usage: "Path to the Ethereum contract Solidity source to build and bind",
|
||||
}
|
||||
solcFlag = cli.StringFlag{
|
||||
Name: "solc",
|
||||
Usage: "Solidity compiler to use if source builds are requested",
|
||||
Value: "solc",
|
||||
}
|
||||
vyFlag = cli.StringFlag{
|
||||
Name: "vy",
|
||||
Usage: "Path to the Ethereum contract Vyper source to build and bind",
|
||||
}
|
||||
vyperFlag = cli.StringFlag{
|
||||
Name: "vyper",
|
||||
Usage: "Vyper compiler to use if source builds are requested",
|
||||
Value: "vyper",
|
||||
}
|
||||
excFlag = cli.StringFlag{
|
||||
Name: "exc",
|
||||
Usage: "Comma separated types to exclude from binding",
|
||||
@@ -87,6 +107,10 @@ func init() {
|
||||
binFlag,
|
||||
typeFlag,
|
||||
jsonFlag,
|
||||
solFlag,
|
||||
solcFlag,
|
||||
vyFlag,
|
||||
vyperFlag,
|
||||
excFlag,
|
||||
pkgFlag,
|
||||
outFlag,
|
||||
@@ -98,7 +122,7 @@ func init() {
|
||||
}
|
||||
|
||||
func abigen(c *cli.Context) error {
|
||||
utils.CheckExclusive(c, abiFlag, jsonFlag) // Only one source can be selected.
|
||||
utils.CheckExclusive(c, abiFlag, jsonFlag, solFlag, vyFlag) // Only one source can be selected.
|
||||
if c.GlobalString(pkgFlag.Name) == "" {
|
||||
utils.Fatalf("No destination package specified (--pkg)")
|
||||
}
|
||||
@@ -162,9 +186,33 @@ func abigen(c *cli.Context) error {
|
||||
for _, kind := range strings.Split(c.GlobalString(excFlag.Name), ",") {
|
||||
exclude[strings.ToLower(kind)] = true
|
||||
}
|
||||
var err error
|
||||
var contracts map[string]*compiler.Contract
|
||||
|
||||
if c.GlobalIsSet(jsonFlag.Name) {
|
||||
switch {
|
||||
case c.GlobalIsSet(solFlag.Name):
|
||||
contracts, err = compiler.CompileSolidity(c.GlobalString(solcFlag.Name), c.GlobalString(solFlag.Name))
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to build Solidity contract: %v", err)
|
||||
}
|
||||
case c.GlobalIsSet(vyFlag.Name):
|
||||
output, err := compiler.CompileVyper(c.GlobalString(vyperFlag.Name), c.GlobalString(vyFlag.Name))
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to build Vyper contract: %v", err)
|
||||
}
|
||||
contracts = make(map[string]*compiler.Contract)
|
||||
for n, contract := range output {
|
||||
name := n
|
||||
// Sanitize the combined json names to match the
|
||||
// format expected by solidity.
|
||||
if !strings.Contains(n, ":") {
|
||||
// Remove extra path components
|
||||
name = abi.ToCamelCase(strings.TrimSuffix(filepath.Base(name), ".vy"))
|
||||
}
|
||||
contracts[name] = contract
|
||||
}
|
||||
|
||||
case c.GlobalIsSet(jsonFlag.Name):
|
||||
jsonOutput, err := os.ReadFile(c.GlobalString(jsonFlag.Name))
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read combined-json from compiler: %v", err)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest
|
||||
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest
|
||||
|
||||
import "github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package t8ntool
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package t8ntool
|
||||
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
@@ -221,7 +223,15 @@ func verifyState(ctx *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
log.Info("Verified the state", "root", root)
|
||||
return snapshot.CheckDanglingStorage(chaindb)
|
||||
if err := checkDanglingDiskStorage(chaindb); err != nil {
|
||||
log.Error("Dangling snap disk-storage check failed", "root", root, "err", err)
|
||||
return err
|
||||
}
|
||||
if err := checkDanglingMemStorage(chaindb); err != nil {
|
||||
log.Error("Dangling snap mem-storage check failed", "root", root, "err", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkDanglingStorage iterates the snap storage data, and verifies that all
|
||||
@@ -230,7 +240,56 @@ func checkDanglingStorage(ctx *cli.Context) error {
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
return snapshot.CheckDanglingStorage(utils.MakeChainDatabase(ctx, stack, true))
|
||||
chaindb := utils.MakeChainDatabase(ctx, stack, true)
|
||||
if err := checkDanglingDiskStorage(chaindb); err != nil {
|
||||
return err
|
||||
}
|
||||
return checkDanglingMemStorage(chaindb)
|
||||
|
||||
}
|
||||
|
||||
// checkDanglingDiskStorage checks if there is any 'dangling' storage data in the
|
||||
// disk-backed snapshot layer.
|
||||
func checkDanglingDiskStorage(chaindb ethdb.Database) error {
|
||||
log.Info("Checking dangling snapshot disk storage")
|
||||
var (
|
||||
lastReport = time.Now()
|
||||
start = time.Now()
|
||||
lastKey []byte
|
||||
it = rawdb.NewKeyLengthIterator(chaindb.NewIterator(rawdb.SnapshotStoragePrefix, nil), 1+2*common.HashLength)
|
||||
)
|
||||
defer it.Release()
|
||||
for it.Next() {
|
||||
k := it.Key()
|
||||
accKey := k[1:33]
|
||||
if bytes.Equal(accKey, lastKey) {
|
||||
// No need to look up for every slot
|
||||
continue
|
||||
}
|
||||
lastKey = common.CopyBytes(accKey)
|
||||
if time.Since(lastReport) > time.Second*8 {
|
||||
log.Info("Iterating snap storage", "at", fmt.Sprintf("%#x", accKey), "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
lastReport = time.Now()
|
||||
}
|
||||
if data := rawdb.ReadAccountSnapshot(chaindb, common.BytesToHash(accKey)); len(data) == 0 {
|
||||
log.Error("Dangling storage - missing account", "account", fmt.Sprintf("%#x", accKey), "storagekey", fmt.Sprintf("%#x", k))
|
||||
return fmt.Errorf("dangling snapshot storage account %#x", accKey)
|
||||
}
|
||||
}
|
||||
log.Info("Verified the snapshot disk storage", "time", common.PrettyDuration(time.Since(start)), "err", it.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkDanglingMemStorage checks if there is any 'dangling' storage in the journalled
|
||||
// snapshot difflayers.
|
||||
func checkDanglingMemStorage(chaindb ethdb.Database) error {
|
||||
start := time.Now()
|
||||
log.Info("Checking dangling snapshot difflayer journalled storage")
|
||||
if err := snapshot.CheckJournalStorage(chaindb); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Verified the snapshot journalled storage", "time", common.PrettyDuration(time.Since(start)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// traverseState is a helper function used for pruning verification.
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build !windows && !openbsd
|
||||
// +build !windows,!openbsd
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build openbsd
|
||||
// +build openbsd
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package utils
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package utils
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package utils contains internal helper functions for go-ethereum commands.
|
||||
package utils
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package utils contains internal helper functions for go-ethereum commands.
|
||||
package utils
|
||||
|
||||
@@ -17,6 +17,14 @@
|
||||
// Package compiler wraps the Solidity and Vyper compiler executables (solc; vyper).
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var versionRegexp = regexp.MustCompile(`([0-9]+)\.([0-9]+)\.([0-9]+)`)
|
||||
|
||||
// Contract contains information about a compiled contract, alongside its code and runtime code.
|
||||
type Contract struct {
|
||||
Code string `json:"code"`
|
||||
@@ -43,3 +51,15 @@ type ContractInfo struct {
|
||||
DeveloperDoc interface{} `json:"developerDoc"`
|
||||
Metadata string `json:"metadata"`
|
||||
}
|
||||
|
||||
func slurpFiles(files []string) (string, error) {
|
||||
var concat bytes.Buffer
|
||||
for _, file := range files {
|
||||
content, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
concat.Write(content)
|
||||
}
|
||||
return concat.String(), nil
|
||||
}
|
||||
|
||||
@@ -14,14 +14,26 @@
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package compiler wraps the ABI compilation outputs.
|
||||
// Package compiler wraps the Solidity and Vyper compiler executables (solc; vyper).
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Solidity contains information about the solidity compiler.
|
||||
type Solidity struct {
|
||||
Path, Version, FullVersion string
|
||||
Major, Minor, Patch int
|
||||
ExtraAllowedPath []string
|
||||
}
|
||||
|
||||
// --combined-output format
|
||||
type solcOutput struct {
|
||||
Contracts map[string]struct {
|
||||
@@ -47,6 +59,109 @@ type solcOutputV8 struct {
|
||||
Version string
|
||||
}
|
||||
|
||||
func (s *Solidity) allowedPaths() string {
|
||||
paths := []string{".", "./", "../"} // default to support relative paths
|
||||
if len(s.ExtraAllowedPath) > 0 {
|
||||
paths = append(paths, s.ExtraAllowedPath...)
|
||||
}
|
||||
return strings.Join(paths, ", ")
|
||||
}
|
||||
|
||||
func (s *Solidity) makeArgs() []string {
|
||||
p := []string{
|
||||
"--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc",
|
||||
"--optimize", // code optimizer switched on
|
||||
"--allow-paths", s.allowedPaths(),
|
||||
}
|
||||
if s.Major > 0 || s.Minor > 4 || s.Patch > 6 {
|
||||
p[1] += ",metadata,hashes"
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// SolidityVersion runs solc and parses its version output.
|
||||
func SolidityVersion(solc string) (*Solidity, error) {
|
||||
if solc == "" {
|
||||
solc = "solc"
|
||||
}
|
||||
var out bytes.Buffer
|
||||
cmd := exec.Command(solc, "--version")
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matches := versionRegexp.FindStringSubmatch(out.String())
|
||||
if len(matches) != 4 {
|
||||
return nil, fmt.Errorf("can't parse solc version %q", out.String())
|
||||
}
|
||||
s := &Solidity{Path: cmd.Path, FullVersion: out.String(), Version: matches[0]}
|
||||
if s.Major, err = strconv.Atoi(matches[1]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.Minor, err = strconv.Atoi(matches[2]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.Patch, err = strconv.Atoi(matches[3]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// CompileSolidityString builds and returns all the contracts contained within a source string.
|
||||
func CompileSolidityString(solc, source string) (map[string]*Contract, error) {
|
||||
if len(source) == 0 {
|
||||
return nil, errors.New("solc: empty source string")
|
||||
}
|
||||
s, err := SolidityVersion(solc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.CompileSource(source)
|
||||
}
|
||||
|
||||
// CompileSolidity compiles all given Solidity source files.
|
||||
func CompileSolidity(solc string, sourcefiles ...string) (map[string]*Contract, error) {
|
||||
if len(sourcefiles) == 0 {
|
||||
return nil, errors.New("solc: no source files")
|
||||
}
|
||||
s, err := SolidityVersion(solc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.CompileFiles(sourcefiles...)
|
||||
}
|
||||
|
||||
// CompileSource builds and returns all the contracts contained within a source string.
|
||||
func (s *Solidity) CompileSource(source string) (map[string]*Contract, error) {
|
||||
args := append(s.makeArgs(), "--")
|
||||
cmd := exec.Command(s.Path, append(args, "-")...)
|
||||
cmd.Stdin = strings.NewReader(source)
|
||||
return s.run(cmd, source)
|
||||
}
|
||||
|
||||
// CompileFiles compiles all given Solidity source files.
|
||||
func (s *Solidity) CompileFiles(sourcefiles ...string) (map[string]*Contract, error) {
|
||||
source, err := slurpFiles(sourcefiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args := append(s.makeArgs(), "--")
|
||||
cmd := exec.Command(s.Path, append(args, sourcefiles...)...)
|
||||
return s.run(cmd, source)
|
||||
}
|
||||
|
||||
func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*Contract, error) {
|
||||
var stderr, stdout bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
cmd.Stdout = &stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("solc: %v\n%s", err, stderr.Bytes())
|
||||
}
|
||||
return ParseCombinedJSON(stdout.Bytes(), source, s.Version, s.Version, strings.Join(s.makeArgs(), " "))
|
||||
}
|
||||
|
||||
// ParseCombinedJSON takes the direct output of a solc --combined-output run and
|
||||
// parses it into a map of string contract name to Contract structs. The
|
||||
// provided source, language and compiler version, and compiler options are all
|
||||
|
||||
78
common/compiler/solidity_test.go
Normal file
78
common/compiler/solidity_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
testSource = `
|
||||
pragma solidity >0.0.0;
|
||||
contract test {
|
||||
/// @notice Will multiply ` + "`a`" + ` by 7.
|
||||
function multiply(uint a) public returns(uint d) {
|
||||
return a * 7;
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
func skipWithoutSolc(t *testing.T) {
|
||||
if _, err := exec.LookPath("solc"); err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSolidityCompiler(t *testing.T) {
|
||||
skipWithoutSolc(t)
|
||||
|
||||
contracts, err := CompileSolidityString("", testSource)
|
||||
if err != nil {
|
||||
t.Fatalf("error compiling source. result %v: %v", contracts, err)
|
||||
}
|
||||
if len(contracts) != 1 {
|
||||
t.Errorf("one contract expected, got %d", len(contracts))
|
||||
}
|
||||
c, ok := contracts["test"]
|
||||
if !ok {
|
||||
c, ok = contracts["<stdin>:test"]
|
||||
if !ok {
|
||||
t.Fatal("info for contract 'test' not present in result")
|
||||
}
|
||||
}
|
||||
if c.Code == "" {
|
||||
t.Error("empty code")
|
||||
}
|
||||
if c.Info.Source != testSource {
|
||||
t.Error("wrong source")
|
||||
}
|
||||
if c.Info.CompilerVersion == "" {
|
||||
t.Error("empty version")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSolidityCompileError(t *testing.T) {
|
||||
skipWithoutSolc(t)
|
||||
|
||||
contracts, err := CompileSolidityString("", testSource[4:])
|
||||
if err == nil {
|
||||
t.Errorf("error expected compiling source. got none. result %v", contracts)
|
||||
}
|
||||
t.Logf("error: %v", err)
|
||||
}
|
||||
3
common/compiler/test.v.py
Normal file
3
common/compiler/test.v.py
Normal file
@@ -0,0 +1,3 @@
|
||||
@public
|
||||
def test():
|
||||
hello: int128
|
||||
3
common/compiler/test_bad.v.py
Normal file
3
common/compiler/test_bad.v.py
Normal file
@@ -0,0 +1,3 @@
|
||||
lic
|
||||
def test():
|
||||
hello: int128
|
||||
144
common/compiler/vyper.go
Normal file
144
common/compiler/vyper.go
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package compiler wraps the Solidity and Vyper compiler executables (solc; vyper).
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Vyper contains information about the vyper compiler.
|
||||
type Vyper struct {
|
||||
Path, Version, FullVersion string
|
||||
Major, Minor, Patch int
|
||||
}
|
||||
|
||||
func (s *Vyper) makeArgs() []string {
|
||||
p := []string{
|
||||
"-f", "combined_json",
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// VyperVersion runs vyper and parses its version output.
|
||||
func VyperVersion(vyper string) (*Vyper, error) {
|
||||
if vyper == "" {
|
||||
vyper = "vyper"
|
||||
}
|
||||
var out bytes.Buffer
|
||||
cmd := exec.Command(vyper, "--version")
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matches := versionRegexp.FindStringSubmatch(out.String())
|
||||
if len(matches) != 4 {
|
||||
return nil, fmt.Errorf("can't parse vyper version %q", out.String())
|
||||
}
|
||||
s := &Vyper{Path: cmd.Path, FullVersion: out.String(), Version: matches[0]}
|
||||
if s.Major, err = strconv.Atoi(matches[1]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.Minor, err = strconv.Atoi(matches[2]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.Patch, err = strconv.Atoi(matches[3]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// CompileVyper compiles all given Vyper source files.
|
||||
func CompileVyper(vyper string, sourcefiles ...string) (map[string]*Contract, error) {
|
||||
if len(sourcefiles) == 0 {
|
||||
return nil, errors.New("vyper: no source files")
|
||||
}
|
||||
source, err := slurpFiles(sourcefiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s, err := VyperVersion(vyper)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args := s.makeArgs()
|
||||
cmd := exec.Command(s.Path, append(args, sourcefiles...)...)
|
||||
return s.run(cmd, source)
|
||||
}
|
||||
|
||||
func (s *Vyper) run(cmd *exec.Cmd, source string) (map[string]*Contract, error) {
|
||||
var stderr, stdout bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
cmd.Stdout = &stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("vyper: %v\n%s", err, stderr.Bytes())
|
||||
}
|
||||
|
||||
return ParseVyperJSON(stdout.Bytes(), source, s.Version, s.Version, strings.Join(s.makeArgs(), " "))
|
||||
}
|
||||
|
||||
// ParseVyperJSON takes the direct output of a vyper --f combined_json run and
|
||||
// parses it into a map of string contract name to Contract structs. The
|
||||
// provided source, language and compiler version, and compiler options are all
|
||||
// passed through into the Contract structs.
|
||||
//
|
||||
// The vyper output is expected to contain ABI and source mapping.
|
||||
//
|
||||
// Returns an error if the JSON is malformed or missing data, or if the JSON
|
||||
// embedded within the JSON is malformed.
|
||||
func ParseVyperJSON(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) {
|
||||
var output map[string]interface{}
|
||||
if err := json.Unmarshal(combinedJSON, &output); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Compilation succeeded, assemble and return the contracts.
|
||||
contracts := make(map[string]*Contract)
|
||||
for name, info := range output {
|
||||
// Parse the individual compilation results.
|
||||
if name == "version" {
|
||||
continue
|
||||
}
|
||||
c := info.(map[string]interface{})
|
||||
|
||||
contracts[name] = &Contract{
|
||||
Code: c["bytecode"].(string),
|
||||
RuntimeCode: c["bytecode_runtime"].(string),
|
||||
Info: ContractInfo{
|
||||
Source: source,
|
||||
Language: "Vyper",
|
||||
LanguageVersion: languageVersion,
|
||||
CompilerVersion: compilerVersion,
|
||||
CompilerOptions: compilerOptions,
|
||||
SrcMap: c["source_map"],
|
||||
SrcMapRuntime: "",
|
||||
AbiDefinition: c["abi"],
|
||||
UserDoc: "",
|
||||
DeveloperDoc: "",
|
||||
Metadata: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
return contracts, nil
|
||||
}
|
||||
71
common/compiler/vyper_test.go
Normal file
71
common/compiler/vyper_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func skipWithoutVyper(t *testing.T) {
|
||||
if _, err := exec.LookPath("vyper"); err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVyperCompiler(t *testing.T) {
|
||||
skipWithoutVyper(t)
|
||||
|
||||
testSource := []string{"test.v.py"}
|
||||
source, err := slurpFiles(testSource)
|
||||
if err != nil {
|
||||
t.Error("couldn't read test files")
|
||||
}
|
||||
contracts, err := CompileVyper("", testSource...)
|
||||
if err != nil {
|
||||
t.Fatalf("error compiling test.v.py. result %v: %v", contracts, err)
|
||||
}
|
||||
if len(contracts) != 1 {
|
||||
t.Errorf("one contract expected, got %d", len(contracts))
|
||||
}
|
||||
c, ok := contracts["test.v.py"]
|
||||
if !ok {
|
||||
c, ok = contracts["<stdin>:test"]
|
||||
if !ok {
|
||||
t.Fatal("info for contract 'test.v.py' not present in result")
|
||||
}
|
||||
}
|
||||
if c.Code == "" {
|
||||
t.Error("empty code")
|
||||
}
|
||||
if c.Info.Source != source {
|
||||
t.Error("wrong source")
|
||||
}
|
||||
if c.Info.CompilerVersion == "" {
|
||||
t.Error("empty version")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVyperCompileError(t *testing.T) {
|
||||
skipWithoutVyper(t)
|
||||
|
||||
contracts, err := CompileVyper("", "test_bad.v.py")
|
||||
if err == nil {
|
||||
t.Errorf("error expected compiling test_bad.v.py. got none. result %v", contracts)
|
||||
}
|
||||
t.Logf("error: %v", err)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package beacon
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package rawdb
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package rawdb
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
const (
|
||||
snapAccount = "account" // Identifier of account snapshot generation
|
||||
snapStorage = "storage" // Identifier of storage snapshot generation
|
||||
)
|
||||
|
||||
// generatorStats is a collection of statistics gathered by the snapshot generator
|
||||
// for logging purposes.
|
||||
type generatorStats struct {
|
||||
origin uint64 // Origin prefix where generation started
|
||||
start time.Time // Timestamp when generation started
|
||||
accounts uint64 // Number of accounts indexed(generated or recovered)
|
||||
slots uint64 // Number of storage slots indexed(generated or recovered)
|
||||
dangling uint64 // Number of dangling storage slots
|
||||
storage common.StorageSize // Total account and storage slot size(generation or recovery)
|
||||
}
|
||||
|
||||
// Log creates an contextual log with the given message and the context pulled
|
||||
// from the internally maintained statistics.
|
||||
func (gs *generatorStats) Log(msg string, root common.Hash, marker []byte) {
|
||||
var ctx []interface{}
|
||||
if root != (common.Hash{}) {
|
||||
ctx = append(ctx, []interface{}{"root", root}...)
|
||||
}
|
||||
// Figure out whether we're after or within an account
|
||||
switch len(marker) {
|
||||
case common.HashLength:
|
||||
ctx = append(ctx, []interface{}{"at", common.BytesToHash(marker)}...)
|
||||
case 2 * common.HashLength:
|
||||
ctx = append(ctx, []interface{}{
|
||||
"in", common.BytesToHash(marker[:common.HashLength]),
|
||||
"at", common.BytesToHash(marker[common.HashLength:]),
|
||||
}...)
|
||||
}
|
||||
// Add the usual measurements
|
||||
ctx = append(ctx, []interface{}{
|
||||
"accounts", gs.accounts,
|
||||
"slots", gs.slots,
|
||||
"storage", gs.storage,
|
||||
"dangling", gs.dangling,
|
||||
"elapsed", common.PrettyDuration(time.Since(gs.start)),
|
||||
}...)
|
||||
// Calculate the estimated indexing time based on current stats
|
||||
if len(marker) > 0 {
|
||||
if done := binary.BigEndian.Uint64(marker[:8]) - gs.origin; done > 0 {
|
||||
left := math.MaxUint64 - binary.BigEndian.Uint64(marker[:8])
|
||||
|
||||
speed := done/uint64(time.Since(gs.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero
|
||||
ctx = append(ctx, []interface{}{
|
||||
"eta", common.PrettyDuration(time.Duration(left/speed) * time.Millisecond),
|
||||
}...)
|
||||
}
|
||||
}
|
||||
log.Info(msg, ctx...)
|
||||
}
|
||||
|
||||
// generatorContext carries a few global values to be shared by all generation functions.
|
||||
type generatorContext struct {
|
||||
stats *generatorStats // Generation statistic collection
|
||||
db ethdb.KeyValueStore // Key-value store containing the snapshot data
|
||||
account *holdableIterator // Iterator of account snapshot data
|
||||
storage *holdableIterator // Iterator of storage snapshot data
|
||||
batch ethdb.Batch // Database batch for writing batch data atomically
|
||||
logged time.Time // The timestamp when last generation progress was displayed
|
||||
}
|
||||
|
||||
// newGeneratorContext initializes the context for generation.
|
||||
func newGeneratorContext(stats *generatorStats, db ethdb.KeyValueStore, accMarker []byte, storageMarker []byte) *generatorContext {
|
||||
ctx := &generatorContext{
|
||||
stats: stats,
|
||||
db: db,
|
||||
batch: db.NewBatch(),
|
||||
logged: time.Now(),
|
||||
}
|
||||
ctx.openIterator(snapAccount, accMarker)
|
||||
ctx.openIterator(snapStorage, storageMarker)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// openIterator constructs global account and storage snapshot iterators
|
||||
// at the interrupted position. These iterators should be reopened from time
|
||||
// to time to avoid blocking leveldb compaction for a long time.
|
||||
func (ctx *generatorContext) openIterator(kind string, start []byte) {
|
||||
if kind == snapAccount {
|
||||
iter := ctx.db.NewIterator(rawdb.SnapshotAccountPrefix, start)
|
||||
ctx.account = newHoldableIterator(rawdb.NewKeyLengthIterator(iter, 1+common.HashLength))
|
||||
return
|
||||
}
|
||||
iter := ctx.db.NewIterator(rawdb.SnapshotStoragePrefix, start)
|
||||
ctx.storage = newHoldableIterator(rawdb.NewKeyLengthIterator(iter, 1+2*common.HashLength))
|
||||
}
|
||||
|
||||
// reopenIterator releases the specified snapshot iterator and re-open it
|
||||
// in the next position. It's aimed for not blocking leveldb compaction.
|
||||
func (ctx *generatorContext) reopenIterator(kind string) {
|
||||
// Shift iterator one more step, so that we can reopen
|
||||
// the iterator at the right position.
|
||||
var iter = ctx.account
|
||||
if kind == snapStorage {
|
||||
iter = ctx.storage
|
||||
}
|
||||
hasNext := iter.Next()
|
||||
if !hasNext {
|
||||
// Iterator exhausted, release forever and create an already exhausted virtual iterator
|
||||
iter.Release()
|
||||
if kind == snapAccount {
|
||||
ctx.account = newHoldableIterator(memorydb.New().NewIterator(nil, nil))
|
||||
return
|
||||
}
|
||||
ctx.storage = newHoldableIterator(memorydb.New().NewIterator(nil, nil))
|
||||
return
|
||||
}
|
||||
next := iter.Key()
|
||||
iter.Release()
|
||||
ctx.openIterator(kind, next[1:])
|
||||
}
|
||||
|
||||
// close releases all the held resources.
|
||||
func (ctx *generatorContext) close() {
|
||||
ctx.account.Release()
|
||||
ctx.storage.Release()
|
||||
}
|
||||
|
||||
// iterator returns the corresponding iterator specified by the kind.
|
||||
func (ctx *generatorContext) iterator(kind string) *holdableIterator {
|
||||
if kind == snapAccount {
|
||||
return ctx.account
|
||||
}
|
||||
return ctx.storage
|
||||
}
|
||||
|
||||
// removeStorageBefore deletes all storage entries which are located before
|
||||
// the specified account. When the iterator touches the storage entry which
|
||||
// is located in or outside the given account, it stops and holds the current
|
||||
// iterated element locally.
|
||||
func (ctx *generatorContext) removeStorageBefore(account common.Hash) {
|
||||
var (
|
||||
count uint64
|
||||
start = time.Now()
|
||||
iter = ctx.storage
|
||||
)
|
||||
for iter.Next() {
|
||||
key := iter.Key()
|
||||
if bytes.Compare(key[1:1+common.HashLength], account.Bytes()) >= 0 {
|
||||
iter.Hold()
|
||||
break
|
||||
}
|
||||
count++
|
||||
ctx.batch.Delete(key)
|
||||
if ctx.batch.ValueSize() > ethdb.IdealBatchSize {
|
||||
ctx.batch.Write()
|
||||
ctx.batch.Reset()
|
||||
}
|
||||
}
|
||||
ctx.stats.dangling += count
|
||||
snapStorageCleanCounter.Inc(time.Since(start).Nanoseconds())
|
||||
}
|
||||
|
||||
// removeStorageAt deletes all storage entries which are located in the specified
|
||||
// account. When the iterator touches the storage entry which is outside the given
|
||||
// account, it stops and holds the current iterated element locally. An error will
|
||||
// be returned if the initial position of iterator is not in the given account.
|
||||
func (ctx *generatorContext) removeStorageAt(account common.Hash) error {
|
||||
var (
|
||||
count int64
|
||||
start = time.Now()
|
||||
iter = ctx.storage
|
||||
)
|
||||
for iter.Next() {
|
||||
key := iter.Key()
|
||||
cmp := bytes.Compare(key[1:1+common.HashLength], account.Bytes())
|
||||
if cmp < 0 {
|
||||
return errors.New("invalid iterator position")
|
||||
}
|
||||
if cmp > 0 {
|
||||
iter.Hold()
|
||||
break
|
||||
}
|
||||
count++
|
||||
ctx.batch.Delete(key)
|
||||
if ctx.batch.ValueSize() > ethdb.IdealBatchSize {
|
||||
ctx.batch.Write()
|
||||
ctx.batch.Reset()
|
||||
}
|
||||
}
|
||||
snapWipedStorageMeter.Mark(count)
|
||||
snapStorageCleanCounter.Inc(time.Since(start).Nanoseconds())
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeStorageLeft deletes all storage entries which are located after
|
||||
// the current iterator position.
|
||||
func (ctx *generatorContext) removeStorageLeft() {
|
||||
var (
|
||||
count uint64
|
||||
start = time.Now()
|
||||
iter = ctx.storage
|
||||
)
|
||||
for iter.Next() {
|
||||
count++
|
||||
ctx.batch.Delete(iter.Key())
|
||||
if ctx.batch.ValueSize() > ethdb.IdealBatchSize {
|
||||
ctx.batch.Write()
|
||||
ctx.batch.Reset()
|
||||
}
|
||||
}
|
||||
ctx.stats.dangling += count
|
||||
snapDanglingStorageMeter.Mark(int64(count))
|
||||
snapStorageCleanCounter.Inc(time.Since(start).Nanoseconds())
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// CheckDanglingStorage iterates the snap storage data, and verifies that all
|
||||
// storage also has corresponding account data.
|
||||
func CheckDanglingStorage(chaindb ethdb.KeyValueStore) error {
|
||||
if err := checkDanglingDiskStorage(chaindb); err != nil {
|
||||
return err
|
||||
}
|
||||
return checkDanglingMemStorage(chaindb)
|
||||
}
|
||||
|
||||
// checkDanglingDiskStorage checks if there is any 'dangling' storage data in the
|
||||
// disk-backed snapshot layer.
|
||||
func checkDanglingDiskStorage(chaindb ethdb.KeyValueStore) error {
|
||||
var (
|
||||
lastReport = time.Now()
|
||||
start = time.Now()
|
||||
lastKey []byte
|
||||
it = rawdb.NewKeyLengthIterator(chaindb.NewIterator(rawdb.SnapshotStoragePrefix, nil), 1+2*common.HashLength)
|
||||
)
|
||||
log.Info("Checking dangling snapshot disk storage")
|
||||
|
||||
defer it.Release()
|
||||
for it.Next() {
|
||||
k := it.Key()
|
||||
accKey := k[1:33]
|
||||
if bytes.Equal(accKey, lastKey) {
|
||||
// No need to look up for every slot
|
||||
continue
|
||||
}
|
||||
lastKey = common.CopyBytes(accKey)
|
||||
if time.Since(lastReport) > time.Second*8 {
|
||||
log.Info("Iterating snap storage", "at", fmt.Sprintf("%#x", accKey), "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
lastReport = time.Now()
|
||||
}
|
||||
if data := rawdb.ReadAccountSnapshot(chaindb, common.BytesToHash(accKey)); len(data) == 0 {
|
||||
log.Warn("Dangling storage - missing account", "account", fmt.Sprintf("%#x", accKey), "storagekey", fmt.Sprintf("%#x", k))
|
||||
return fmt.Errorf("dangling snapshot storage account %#x", accKey)
|
||||
}
|
||||
}
|
||||
log.Info("Verified the snapshot disk storage", "time", common.PrettyDuration(time.Since(start)), "err", it.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkDanglingMemStorage checks if there is any 'dangling' storage in the journalled
|
||||
// snapshot difflayers.
|
||||
func checkDanglingMemStorage(db ethdb.KeyValueStore) error {
|
||||
var (
|
||||
start = time.Now()
|
||||
journal = rawdb.ReadSnapshotJournal(db)
|
||||
)
|
||||
if len(journal) == 0 {
|
||||
log.Warn("Loaded snapshot journal", "diffs", "missing")
|
||||
return nil
|
||||
}
|
||||
r := rlp.NewStream(bytes.NewReader(journal), 0)
|
||||
// Firstly, resolve the first element as the journal version
|
||||
version, err := r.Uint()
|
||||
if err != nil {
|
||||
log.Warn("Failed to resolve the journal version", "error", err)
|
||||
return nil
|
||||
}
|
||||
if version != journalVersion {
|
||||
log.Warn("Discarded the snapshot journal with wrong version", "required", journalVersion, "got", version)
|
||||
return nil
|
||||
}
|
||||
// Secondly, resolve the disk layer root, ensure it's continuous
|
||||
// with disk layer. Note now we can ensure it's the snapshot journal
|
||||
// correct version, so we expect everything can be resolved properly.
|
||||
var root common.Hash
|
||||
if err := r.Decode(&root); err != nil {
|
||||
return errors.New("missing disk layer root")
|
||||
}
|
||||
// The diff journal is not matched with disk, discard them.
|
||||
// It can happen that Geth crashes without persisting the latest
|
||||
// diff journal.
|
||||
// Load all the snapshot diffs from the journal
|
||||
if err := checkDanglingJournalStorage(r); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Verified the snapshot journalled storage", "time", common.PrettyDuration(time.Since(start)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadDiffLayer reads the next sections of a snapshot journal, reconstructing a new
|
||||
// diff and verifying that it can be linked to the requested parent.
|
||||
func checkDanglingJournalStorage(r *rlp.Stream) error {
|
||||
for {
|
||||
// Read the next diff journal entry
|
||||
var root common.Hash
|
||||
if err := r.Decode(&root); err != nil {
|
||||
// The first read may fail with EOF, marking the end of the journal
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("load diff root: %v", err)
|
||||
}
|
||||
var destructs []journalDestruct
|
||||
if err := r.Decode(&destructs); err != nil {
|
||||
return fmt.Errorf("load diff destructs: %v", err)
|
||||
}
|
||||
var accounts []journalAccount
|
||||
if err := r.Decode(&accounts); err != nil {
|
||||
return fmt.Errorf("load diff accounts: %v", err)
|
||||
}
|
||||
accountData := make(map[common.Hash][]byte)
|
||||
for _, entry := range accounts {
|
||||
if len(entry.Blob) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that
|
||||
accountData[entry.Hash] = entry.Blob
|
||||
} else {
|
||||
accountData[entry.Hash] = nil
|
||||
}
|
||||
}
|
||||
var storage []journalStorage
|
||||
if err := r.Decode(&storage); err != nil {
|
||||
return fmt.Errorf("load diff storage: %v", err)
|
||||
}
|
||||
for _, entry := range storage {
|
||||
if _, ok := accountData[entry.Hash]; !ok {
|
||||
log.Error("Dangling storage - missing account", "account", fmt.Sprintf("%#x", entry.Hash), "root", root)
|
||||
return fmt.Errorf("dangling journal snapshot storage account %#x", entry.Hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package snapshot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
@@ -26,11 +27,13 @@ import (
|
||||
"github.com/VictoriaMetrics/fastcache"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
@@ -44,14 +47,14 @@ var (
|
||||
|
||||
// accountCheckRange is the upper limit of the number of accounts involved in
|
||||
// each range check. This is a value estimated based on experience. If this
|
||||
// range is too large, the failure rate of range proof will increase. Otherwise,
|
||||
// if the range is too small, the efficiency of the state recovery will decrease.
|
||||
// value is too large, the failure rate of range prove will increase. Otherwise
|
||||
// the value is too small, the efficiency of the state recovery will decrease.
|
||||
accountCheckRange = 128
|
||||
|
||||
// storageCheckRange is the upper limit of the number of storage slots involved
|
||||
// in each range check. This is a value estimated based on experience. If this
|
||||
// range is too large, the failure rate of range proof will increase. Otherwise,
|
||||
// if the range is too small, the efficiency of the state recovery will decrease.
|
||||
// value is too large, the failure rate of range prove will increase. Otherwise
|
||||
// the value is too small, the efficiency of the state recovery will decrease.
|
||||
storageCheckRange = 1024
|
||||
|
||||
// errMissingTrie is returned if the target trie is missing while the generation
|
||||
@@ -59,6 +62,85 @@ var (
|
||||
errMissingTrie = errors.New("missing trie")
|
||||
)
|
||||
|
||||
// Metrics in generation
|
||||
var (
|
||||
snapGeneratedAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/generated", nil)
|
||||
snapRecoveredAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/recovered", nil)
|
||||
snapWipedAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/wiped", nil)
|
||||
snapMissallAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/missall", nil)
|
||||
snapGeneratedStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/generated", nil)
|
||||
snapRecoveredStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/recovered", nil)
|
||||
snapWipedStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/wiped", nil)
|
||||
snapMissallStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/missall", nil)
|
||||
snapSuccessfulRangeProofMeter = metrics.NewRegisteredMeter("state/snapshot/generation/proof/success", nil)
|
||||
snapFailedRangeProofMeter = metrics.NewRegisteredMeter("state/snapshot/generation/proof/failure", nil)
|
||||
|
||||
// snapAccountProveCounter measures time spent on the account proving
|
||||
snapAccountProveCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/prove", nil)
|
||||
// snapAccountTrieReadCounter measures time spent on the account trie iteration
|
||||
snapAccountTrieReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/trieread", nil)
|
||||
// snapAccountSnapReadCounter measues time spent on the snapshot account iteration
|
||||
snapAccountSnapReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/snapread", nil)
|
||||
// snapAccountWriteCounter measures time spent on writing/updating/deleting accounts
|
||||
snapAccountWriteCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/write", nil)
|
||||
// snapStorageProveCounter measures time spent on storage proving
|
||||
snapStorageProveCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/prove", nil)
|
||||
// snapStorageTrieReadCounter measures time spent on the storage trie iteration
|
||||
snapStorageTrieReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/trieread", nil)
|
||||
// snapStorageSnapReadCounter measures time spent on the snapshot storage iteration
|
||||
snapStorageSnapReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/snapread", nil)
|
||||
// snapStorageWriteCounter measures time spent on writing/updating/deleting storages
|
||||
snapStorageWriteCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/write", nil)
|
||||
)
|
||||
|
||||
// generatorStats is a collection of statistics gathered by the snapshot generator
|
||||
// for logging purposes.
|
||||
type generatorStats struct {
|
||||
origin uint64 // Origin prefix where generation started
|
||||
start time.Time // Timestamp when generation started
|
||||
accounts uint64 // Number of accounts indexed(generated or recovered)
|
||||
slots uint64 // Number of storage slots indexed(generated or recovered)
|
||||
storage common.StorageSize // Total account and storage slot size(generation or recovery)
|
||||
}
|
||||
|
||||
// Log creates an contextual log with the given message and the context pulled
|
||||
// from the internally maintained statistics.
|
||||
func (gs *generatorStats) Log(msg string, root common.Hash, marker []byte) {
|
||||
var ctx []interface{}
|
||||
if root != (common.Hash{}) {
|
||||
ctx = append(ctx, []interface{}{"root", root}...)
|
||||
}
|
||||
// Figure out whether we're after or within an account
|
||||
switch len(marker) {
|
||||
case common.HashLength:
|
||||
ctx = append(ctx, []interface{}{"at", common.BytesToHash(marker)}...)
|
||||
case 2 * common.HashLength:
|
||||
ctx = append(ctx, []interface{}{
|
||||
"in", common.BytesToHash(marker[:common.HashLength]),
|
||||
"at", common.BytesToHash(marker[common.HashLength:]),
|
||||
}...)
|
||||
}
|
||||
// Add the usual measurements
|
||||
ctx = append(ctx, []interface{}{
|
||||
"accounts", gs.accounts,
|
||||
"slots", gs.slots,
|
||||
"storage", gs.storage,
|
||||
"elapsed", common.PrettyDuration(time.Since(gs.start)),
|
||||
}...)
|
||||
// Calculate the estimated indexing time based on current stats
|
||||
if len(marker) > 0 {
|
||||
if done := binary.BigEndian.Uint64(marker[:8]) - gs.origin; done > 0 {
|
||||
left := math.MaxUint64 - binary.BigEndian.Uint64(marker[:8])
|
||||
|
||||
speed := done/uint64(time.Since(gs.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero
|
||||
ctx = append(ctx, []interface{}{
|
||||
"eta", common.PrettyDuration(time.Duration(left/speed) * time.Millisecond),
|
||||
}...)
|
||||
}
|
||||
}
|
||||
log.Info(msg, ctx...)
|
||||
}
|
||||
|
||||
// generateSnapshot regenerates a brand new snapshot based on an existing state
|
||||
// database and head block asynchronously. The snapshot is returned immediately
|
||||
// and generation is continued in the background until done.
|
||||
@@ -166,35 +248,25 @@ func (result *proofResult) forEach(callback func(key []byte, val []byte) error)
|
||||
//
|
||||
// The proof result will be returned if the range proving is finished, otherwise
|
||||
// the error will be returned to abort the entire procedure.
|
||||
func (dl *diskLayer) proveRange(ctx *generatorContext, root common.Hash, prefix []byte, kind string, origin []byte, max int, valueConvertFn func([]byte) ([]byte, error)) (*proofResult, error) {
|
||||
func (dl *diskLayer) proveRange(stats *generatorStats, root common.Hash, prefix []byte, kind string, origin []byte, max int, valueConvertFn func([]byte) ([]byte, error)) (*proofResult, error) {
|
||||
var (
|
||||
keys [][]byte
|
||||
vals [][]byte
|
||||
proof = rawdb.NewMemoryDatabase()
|
||||
diskMore = false
|
||||
iter = ctx.iterator(kind)
|
||||
start = time.Now()
|
||||
min = append(prefix, origin...)
|
||||
)
|
||||
iter := dl.diskdb.NewIterator(prefix, origin)
|
||||
defer iter.Release()
|
||||
|
||||
var start = time.Now()
|
||||
for iter.Next() {
|
||||
// Ensure the iterated item is always equal or larger than the given origin.
|
||||
key := iter.Key()
|
||||
if bytes.Compare(key, min) < 0 {
|
||||
return nil, errors.New("invalid iteration position")
|
||||
if len(key) != len(prefix)+common.HashLength {
|
||||
continue
|
||||
}
|
||||
// Ensure the iterated item still fall in the specified prefix. If
|
||||
// not which means the items in the specified area are all visited.
|
||||
// Move the iterator a step back since we iterate one extra element
|
||||
// out.
|
||||
if !bytes.Equal(key[:len(prefix)], prefix) {
|
||||
iter.Hold()
|
||||
break
|
||||
}
|
||||
// Break if we've reached the max size, and signal that we're not
|
||||
// done yet. Move the iterator a step back since we iterate one
|
||||
// extra element out.
|
||||
if len(keys) == max {
|
||||
iter.Hold()
|
||||
// Break if we've reached the max size, and signal that we're not
|
||||
// done yet.
|
||||
diskMore = true
|
||||
break
|
||||
}
|
||||
@@ -210,7 +282,7 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, root common.Hash, prefix
|
||||
// generation to heal the invalid data.
|
||||
//
|
||||
// Here append the original value to ensure that the number of key and
|
||||
// value are aligned.
|
||||
// value are the same.
|
||||
vals = append(vals, common.CopyBytes(iter.Value()))
|
||||
log.Error("Failed to convert account state data", "err", err)
|
||||
} else {
|
||||
@@ -219,13 +291,13 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, root common.Hash, prefix
|
||||
}
|
||||
}
|
||||
// Update metrics for database iteration and merkle proving
|
||||
if kind == snapStorage {
|
||||
if kind == "storage" {
|
||||
snapStorageSnapReadCounter.Inc(time.Since(start).Nanoseconds())
|
||||
} else {
|
||||
snapAccountSnapReadCounter.Inc(time.Since(start).Nanoseconds())
|
||||
}
|
||||
defer func(start time.Time) {
|
||||
if kind == snapStorage {
|
||||
if kind == "storage" {
|
||||
snapStorageProveCounter.Inc(time.Since(start).Nanoseconds())
|
||||
} else {
|
||||
snapAccountProveCounter.Inc(time.Since(start).Nanoseconds())
|
||||
@@ -250,7 +322,7 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, root common.Hash, prefix
|
||||
// Snap state is chunked, generate edge proofs for verification.
|
||||
tr, err := trie.New(root, dl.triedb)
|
||||
if err != nil {
|
||||
ctx.stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker)
|
||||
stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker)
|
||||
return nil, errMissingTrie
|
||||
}
|
||||
// Firstly find out the key of last iterated element.
|
||||
@@ -299,23 +371,19 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, root common.Hash, prefix
|
||||
|
||||
// onStateCallback is a function that is called by generateRange, when processing a range of
|
||||
// accounts or storage slots. For each element, the callback is invoked.
|
||||
//
|
||||
// - If 'delete' is true, then this element (and potential slots) needs to be deleted from the snapshot.
|
||||
// - If 'write' is true, then this element needs to be updated with the 'val'.
|
||||
// - If 'write' is false, then this element is already correct, and needs no update.
|
||||
// If 'delete' is true, then this element (and potential slots) needs to be deleted from the snapshot.
|
||||
// If 'write' is true, then this element needs to be updated with the 'val'.
|
||||
// If 'write' is false, then this element is already correct, and needs no update. However,
|
||||
// for accounts, the storage trie of the account needs to be checked.
|
||||
// The 'val' is the canonical encoding of the value (not the slim format for accounts)
|
||||
//
|
||||
// However, for accounts, the storage trie of the account needs to be checked. Also,
|
||||
// dangling storages(storage exists but the corresponding account is missing) need to
|
||||
// be cleaned up.
|
||||
type onStateCallback func(key []byte, val []byte, write bool, delete bool) error
|
||||
|
||||
// generateRange generates the state segment with particular prefix. Generation can
|
||||
// either verify the correctness of existing state through range-proof and skip
|
||||
// generation, or iterate trie to regenerate state on demand.
|
||||
func (dl *diskLayer) generateRange(ctx *generatorContext, root common.Hash, prefix []byte, kind string, origin []byte, max int, onState onStateCallback, valueConvertFn func([]byte) ([]byte, error)) (bool, []byte, error) {
|
||||
func (dl *diskLayer) generateRange(root common.Hash, prefix []byte, kind string, origin []byte, max int, stats *generatorStats, onState onStateCallback, valueConvertFn func([]byte) ([]byte, error)) (bool, []byte, error) {
|
||||
// Use range prover to check the validity of the flat state in the range
|
||||
result, err := dl.proveRange(ctx, root, prefix, kind, origin, max, valueConvertFn)
|
||||
result, err := dl.proveRange(stats, root, prefix, kind, origin, max, valueConvertFn)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
@@ -346,17 +414,18 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, root common.Hash, pref
|
||||
snapFailedRangeProofMeter.Mark(1)
|
||||
|
||||
// Special case, the entire trie is missing. In the original trie scheme,
|
||||
// all the duplicated subtries will be filtered out (only one copy of data
|
||||
// all the duplicated subtries will be filter out(only one copy of data
|
||||
// will be stored). While in the snapshot model, all the storage tries
|
||||
// belong to different contracts will be kept even they are duplicated.
|
||||
// Track it to a certain extent remove the noise data used for statistics.
|
||||
if origin == nil && last == nil {
|
||||
meter := snapMissallAccountMeter
|
||||
if kind == snapStorage {
|
||||
if kind == "storage" {
|
||||
meter = snapMissallStorageMeter
|
||||
}
|
||||
meter.Mark(1)
|
||||
}
|
||||
|
||||
// We use the snap data to build up a cache which can be used by the
|
||||
// main account trie as a primary lookup when resolving hashes
|
||||
var snapNodeCache ethdb.KeyValueStore
|
||||
@@ -370,16 +439,15 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, root common.Hash, pref
|
||||
root, _, _ := snapTrie.Commit(nil)
|
||||
snapTrieDb.Commit(root, false, nil)
|
||||
}
|
||||
// Construct the trie for state iteration, reuse the trie
|
||||
// if it's already opened with some nodes resolved.
|
||||
tr := result.tr
|
||||
if tr == nil {
|
||||
tr, err = trie.New(root, dl.triedb)
|
||||
if err != nil {
|
||||
ctx.stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker)
|
||||
stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker)
|
||||
return false, nil, errMissingTrie
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
trieMore bool
|
||||
nodeIt = tr.NodeIterator(origin)
|
||||
@@ -398,7 +466,6 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, root common.Hash, pref
|
||||
internal time.Duration
|
||||
)
|
||||
nodeIt.AddResolver(snapNodeCache)
|
||||
|
||||
for iter.Next() {
|
||||
if last != nil && bytes.Compare(iter.Key, last) > 0 {
|
||||
trieMore = true
|
||||
@@ -452,7 +519,7 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, root common.Hash, pref
|
||||
internal += time.Since(istart)
|
||||
|
||||
// Update metrics for counting trie iteration
|
||||
if kind == snapStorage {
|
||||
if kind == "storage" {
|
||||
snapStorageTrieReadCounter.Inc((time.Since(start) - internal).Nanoseconds())
|
||||
} else {
|
||||
snapAccountTrieReadCounter.Inc((time.Since(start) - internal).Nanoseconds())
|
||||
@@ -467,69 +534,66 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, root common.Hash, pref
|
||||
|
||||
// checkAndFlush checks if an interruption signal is received or the
|
||||
// batch size has exceeded the allowance.
|
||||
func (dl *diskLayer) checkAndFlush(ctx *generatorContext, current []byte) error {
|
||||
func (dl *diskLayer) checkAndFlush(current []byte, batch ethdb.Batch, stats *generatorStats, logged *time.Time) error {
|
||||
var abort chan *generatorStats
|
||||
select {
|
||||
case abort = <-dl.genAbort:
|
||||
default:
|
||||
}
|
||||
if ctx.batch.ValueSize() > ethdb.IdealBatchSize || abort != nil {
|
||||
if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil {
|
||||
if bytes.Compare(current, dl.genMarker) < 0 {
|
||||
log.Error("Snapshot generator went backwards", "current", fmt.Sprintf("%x", current), "genMarker", fmt.Sprintf("%x", dl.genMarker))
|
||||
}
|
||||
// Flush out the batch anyway no matter it's empty or not.
|
||||
// It's possible that all the states are recovered and the
|
||||
// generation indeed makes progress.
|
||||
journalProgress(ctx.batch, current, ctx.stats)
|
||||
journalProgress(batch, current, stats)
|
||||
|
||||
if err := ctx.batch.Write(); err != nil {
|
||||
if err := batch.Write(); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.batch.Reset()
|
||||
batch.Reset()
|
||||
|
||||
dl.lock.Lock()
|
||||
dl.genMarker = current
|
||||
dl.lock.Unlock()
|
||||
|
||||
if abort != nil {
|
||||
ctx.stats.Log("Aborting state snapshot generation", dl.root, current)
|
||||
stats.Log("Aborting state snapshot generation", dl.root, current)
|
||||
return newAbortErr(abort) // bubble up an error for interruption
|
||||
}
|
||||
// Don't hold the iterators too long, release them to let compactor works
|
||||
ctx.reopenIterator(snapAccount)
|
||||
ctx.reopenIterator(snapStorage)
|
||||
}
|
||||
if time.Since(ctx.logged) > 8*time.Second {
|
||||
ctx.stats.Log("Generating state snapshot", dl.root, current)
|
||||
ctx.logged = time.Now()
|
||||
if time.Since(*logged) > 8*time.Second {
|
||||
stats.Log("Generating state snapshot", dl.root, current)
|
||||
*logged = time.Now()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateStorages generates the missing storage slots of the specific contract.
|
||||
// It's supposed to restart the generation from the given origin position.
|
||||
func generateStorages(ctx *generatorContext, dl *diskLayer, account common.Hash, storageRoot common.Hash, storeMarker []byte) error {
|
||||
func generateStorages(dl *diskLayer, account common.Hash, storageRoot common.Hash, storeMarker []byte, batch ethdb.Batch, stats *generatorStats, logged *time.Time) error {
|
||||
onStorage := func(key []byte, val []byte, write bool, delete bool) error {
|
||||
defer func(start time.Time) {
|
||||
snapStorageWriteCounter.Inc(time.Since(start).Nanoseconds())
|
||||
}(time.Now())
|
||||
|
||||
if delete {
|
||||
rawdb.DeleteStorageSnapshot(ctx.batch, account, common.BytesToHash(key))
|
||||
rawdb.DeleteStorageSnapshot(batch, account, common.BytesToHash(key))
|
||||
snapWipedStorageMeter.Mark(1)
|
||||
return nil
|
||||
}
|
||||
if write {
|
||||
rawdb.WriteStorageSnapshot(ctx.batch, account, common.BytesToHash(key), val)
|
||||
rawdb.WriteStorageSnapshot(batch, account, common.BytesToHash(key), val)
|
||||
snapGeneratedStorageMeter.Mark(1)
|
||||
} else {
|
||||
snapRecoveredStorageMeter.Mark(1)
|
||||
}
|
||||
ctx.stats.storage += common.StorageSize(1 + 2*common.HashLength + len(val))
|
||||
ctx.stats.slots++
|
||||
stats.storage += common.StorageSize(1 + 2*common.HashLength + len(val))
|
||||
stats.slots++
|
||||
|
||||
// If we've exceeded our batch allowance or termination was requested, flush to disk
|
||||
if err := dl.checkAndFlush(ctx, append(account[:], key...)); err != nil {
|
||||
if err := dl.checkAndFlush(append(account[:], key...), batch, stats, logged); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -537,7 +601,7 @@ func generateStorages(ctx *generatorContext, dl *diskLayer, account common.Hash,
|
||||
// Loop for re-generating the missing storage slots.
|
||||
var origin = common.CopyBytes(storeMarker)
|
||||
for {
|
||||
exhausted, last, err := dl.generateRange(ctx, storageRoot, append(rawdb.SnapshotStoragePrefix, account.Bytes()...), snapStorage, origin, storageCheckRange, onStorage, nil)
|
||||
exhausted, last, err := dl.generateRange(storageRoot, append(rawdb.SnapshotStoragePrefix, account.Bytes()...), "storage", origin, storageCheckRange, stats, onStorage, nil)
|
||||
if err != nil {
|
||||
return err // The procedure it aborted, either by external signal or internal error.
|
||||
}
|
||||
@@ -555,19 +619,23 @@ func generateStorages(ctx *generatorContext, dl *diskLayer, account common.Hash,
|
||||
// generateAccounts generates the missing snapshot accounts as well as their
|
||||
// storage slots in the main trie. It's supposed to restart the generation
|
||||
// from the given origin position.
|
||||
func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) error {
|
||||
func generateAccounts(dl *diskLayer, accMarker []byte, batch ethdb.Batch, stats *generatorStats, logged *time.Time) error {
|
||||
onAccount := func(key []byte, val []byte, write bool, delete bool) error {
|
||||
// Make sure to clear all dangling storages before this account
|
||||
account := common.BytesToHash(key)
|
||||
ctx.removeStorageBefore(account)
|
||||
|
||||
start := time.Now()
|
||||
var (
|
||||
start = time.Now()
|
||||
accountHash = common.BytesToHash(key)
|
||||
)
|
||||
if delete {
|
||||
rawdb.DeleteAccountSnapshot(ctx.batch, account)
|
||||
rawdb.DeleteAccountSnapshot(batch, accountHash)
|
||||
snapWipedAccountMeter.Mark(1)
|
||||
snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds())
|
||||
|
||||
ctx.removeStorageAt(account)
|
||||
// Ensure that any previous snapshot storage values are cleared
|
||||
prefix := append(rawdb.SnapshotStoragePrefix, accountHash.Bytes()...)
|
||||
keyLen := len(rawdb.SnapshotStoragePrefix) + 2*common.HashLength
|
||||
if err := wipeKeyRange(dl.diskdb, "storage", prefix, nil, nil, keyLen, snapWipedStorageMeter, false); err != nil {
|
||||
return err
|
||||
}
|
||||
snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds())
|
||||
return nil
|
||||
}
|
||||
// Retrieve the current account and flatten it into the internal format
|
||||
@@ -581,7 +649,7 @@ func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) er
|
||||
log.Crit("Invalid account encountered during snapshot creation", "err", err)
|
||||
}
|
||||
// If the account is not yet in-progress, write it out
|
||||
if accMarker == nil || !bytes.Equal(account[:], accMarker) {
|
||||
if accMarker == nil || !bytes.Equal(accountHash[:], accMarker) {
|
||||
dataLen := len(val) // Approximate size, saves us a round of RLP-encoding
|
||||
if !write {
|
||||
if bytes.Equal(acc.CodeHash, emptyCode[:]) {
|
||||
@@ -594,34 +662,44 @@ func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) er
|
||||
} else {
|
||||
data := SlimAccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash)
|
||||
dataLen = len(data)
|
||||
rawdb.WriteAccountSnapshot(ctx.batch, account, data)
|
||||
rawdb.WriteAccountSnapshot(batch, accountHash, data)
|
||||
snapGeneratedAccountMeter.Mark(1)
|
||||
}
|
||||
ctx.stats.storage += common.StorageSize(1 + common.HashLength + dataLen)
|
||||
ctx.stats.accounts++
|
||||
stats.storage += common.StorageSize(1 + common.HashLength + dataLen)
|
||||
stats.accounts++
|
||||
}
|
||||
marker := accountHash[:]
|
||||
// If the snap generation goes here after interrupted, genMarker may go backward
|
||||
// when last genMarker is consisted of accountHash and storageHash
|
||||
marker := account[:]
|
||||
if accMarker != nil && bytes.Equal(marker, accMarker) && len(dl.genMarker) > common.HashLength {
|
||||
marker = dl.genMarker[:]
|
||||
}
|
||||
// If we've exceeded our batch allowance or termination was requested, flush to disk
|
||||
if err := dl.checkAndFlush(ctx, marker); err != nil {
|
||||
if err := dl.checkAndFlush(marker, batch, stats, logged); err != nil {
|
||||
return err
|
||||
}
|
||||
snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds()) // let's count flush time as well
|
||||
|
||||
// If the iterated account is the contract, create a further loop to
|
||||
// verify or regenerate the contract storage.
|
||||
if acc.Root == emptyRoot {
|
||||
ctx.removeStorageAt(account)
|
||||
// If the root is empty, we still need to ensure that any previous snapshot
|
||||
// storage values are cleared
|
||||
// TODO: investigate if this can be avoided, this will be very costly since it
|
||||
// affects every single EOA account
|
||||
// - Perhaps we can avoid if where codeHash is emptyCode
|
||||
prefix := append(rawdb.SnapshotStoragePrefix, accountHash.Bytes()...)
|
||||
keyLen := len(rawdb.SnapshotStoragePrefix) + 2*common.HashLength
|
||||
if err := wipeKeyRange(dl.diskdb, "storage", prefix, nil, nil, keyLen, snapWipedStorageMeter, false); err != nil {
|
||||
return err
|
||||
}
|
||||
snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds())
|
||||
} else {
|
||||
snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds())
|
||||
|
||||
var storeMarker []byte
|
||||
if accMarker != nil && bytes.Equal(account[:], accMarker) && len(dl.genMarker) > common.HashLength {
|
||||
if accMarker != nil && bytes.Equal(accountHash[:], accMarker) && len(dl.genMarker) > common.HashLength {
|
||||
storeMarker = dl.genMarker[common.HashLength:]
|
||||
}
|
||||
if err := generateStorages(ctx, dl, account, acc.Root, storeMarker); err != nil {
|
||||
if err := generateStorages(dl, accountHash, acc.Root, storeMarker, batch, stats, logged); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -629,26 +707,25 @@ func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) er
|
||||
accMarker = nil
|
||||
return nil
|
||||
}
|
||||
// Always reset the initial account range as 1 whenever recover from the
|
||||
// interruption. TODO(rjl493456442) can we remove it?
|
||||
// Always reset the initial account range as 1 whenever recover from the interruption.
|
||||
var accountRange = accountCheckRange
|
||||
if len(accMarker) > 0 {
|
||||
accountRange = 1
|
||||
}
|
||||
// Global loop for re-generating the account snapshots + all layered storage snapshots.
|
||||
origin := common.CopyBytes(accMarker)
|
||||
for {
|
||||
exhausted, last, err := dl.generateRange(ctx, dl.root, rawdb.SnapshotAccountPrefix, snapAccount, origin, accountRange, onAccount, FullAccountRLP)
|
||||
exhausted, last, err := dl.generateRange(dl.root, rawdb.SnapshotAccountPrefix, "account", origin, accountRange, stats, onAccount, FullAccountRLP)
|
||||
if err != nil {
|
||||
return err // The procedure it aborted, either by external signal or internal error.
|
||||
}
|
||||
origin = increaseKey(last)
|
||||
|
||||
// Last step, cleanup the storages after the last account.
|
||||
// All the left storages should be treated as dangling.
|
||||
if origin == nil || exhausted {
|
||||
ctx.removeStorageLeft()
|
||||
// Abort the procedure if the entire snapshot is generated
|
||||
if exhausted {
|
||||
break
|
||||
}
|
||||
if origin = increaseKey(last); origin == nil {
|
||||
break // special case, the last is 0xffffffff...fff
|
||||
}
|
||||
accountRange = accountCheckRange
|
||||
}
|
||||
return nil
|
||||
@@ -659,27 +736,19 @@ func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) er
|
||||
// gathering and logging, since the method surfs the blocks as they arrive, often
|
||||
// being restarted.
|
||||
func (dl *diskLayer) generate(stats *generatorStats) {
|
||||
var (
|
||||
accMarker []byte
|
||||
abort chan *generatorStats
|
||||
)
|
||||
var accMarker []byte
|
||||
if len(dl.genMarker) > 0 { // []byte{} is the start, use nil for that
|
||||
accMarker = dl.genMarker[:common.HashLength]
|
||||
}
|
||||
var (
|
||||
batch = dl.diskdb.NewBatch()
|
||||
logged = time.Now()
|
||||
abort chan *generatorStats
|
||||
)
|
||||
stats.Log("Resuming state snapshot generation", dl.root, dl.genMarker)
|
||||
|
||||
// Initialize the global generator context. The snapshot iterators are
|
||||
// opened at the interrupted position because the assumption is held
|
||||
// that all the snapshot data are generated correctly before the marker.
|
||||
// Even if the snapshot data is updated during the interruption (before
|
||||
// or at the marker), the assumption is still held.
|
||||
// For the account or storage slot at the interruption, they will be
|
||||
// processed twice by the generator(they are already processed in the
|
||||
// last run) but it's fine.
|
||||
ctx := newGeneratorContext(stats, dl.diskdb, accMarker, dl.genMarker)
|
||||
defer ctx.close()
|
||||
|
||||
if err := generateAccounts(ctx, dl, accMarker); err != nil {
|
||||
// Generate the snapshot accounts from the point where they left off.
|
||||
if err := generateAccounts(dl, accMarker, batch, stats, &logged); err != nil {
|
||||
// Extract the received interruption signal if exists
|
||||
if aerr, ok := err.(*abortErr); ok {
|
||||
abort = aerr.abort
|
||||
@@ -694,18 +763,18 @@ func (dl *diskLayer) generate(stats *generatorStats) {
|
||||
// Snapshot fully generated, set the marker to nil.
|
||||
// Note even there is nothing to commit, persist the
|
||||
// generator anyway to mark the snapshot is complete.
|
||||
journalProgress(ctx.batch, nil, stats)
|
||||
if err := ctx.batch.Write(); err != nil {
|
||||
journalProgress(batch, nil, stats)
|
||||
if err := batch.Write(); err != nil {
|
||||
log.Error("Failed to flush batch", "err", err)
|
||||
|
||||
abort = <-dl.genAbort
|
||||
abort <- stats
|
||||
return
|
||||
}
|
||||
ctx.batch.Reset()
|
||||
batch.Reset()
|
||||
|
||||
log.Info("Generated state snapshot", "accounts", stats.accounts, "slots", stats.slots,
|
||||
"storage", stats.storage, "dangling", stats.dangling, "elapsed", common.PrettyDuration(time.Since(stats.start)))
|
||||
"storage", stats.storage, "elapsed", common.PrettyDuration(time.Since(stats.start)))
|
||||
|
||||
dl.lock.Lock()
|
||||
dl.genMarker = nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
@@ -148,10 +148,8 @@ func TestGenerateExistentState(t *testing.T) {
|
||||
|
||||
func checkSnapRoot(t *testing.T, snap *diskLayer, trieRoot common.Hash) {
|
||||
t.Helper()
|
||||
|
||||
accIt := snap.AccountIterator(common.Hash{})
|
||||
defer accIt.Release()
|
||||
|
||||
snapRoot, err := generateTrieRoot(nil, accIt, common.Hash{}, stackTrieGenerate,
|
||||
func(db ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) {
|
||||
storageIt, _ := snap.StorageIterator(accountHash, common.Hash{})
|
||||
@@ -170,9 +168,6 @@ func checkSnapRoot(t *testing.T, snap *diskLayer, trieRoot common.Hash) {
|
||||
if snapRoot != trieRoot {
|
||||
t.Fatalf("snaproot: %#x != trieroot #%x", snapRoot, trieRoot)
|
||||
}
|
||||
if err := CheckDanglingStorage(snap.diskdb); err != nil {
|
||||
t.Fatalf("Detected dangling storages %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type testHelper struct {
|
||||
@@ -836,122 +831,3 @@ func TestGenerateWithIncompleteStorage(t *testing.T) {
|
||||
snap.genAbort <- stop
|
||||
<-stop
|
||||
}
|
||||
|
||||
func incKey(key []byte) []byte {
|
||||
for i := len(key) - 1; i >= 0; i-- {
|
||||
key[i]++
|
||||
if key[i] != 0x0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func decKey(key []byte) []byte {
|
||||
for i := len(key) - 1; i >= 0; i-- {
|
||||
key[i]--
|
||||
if key[i] != 0xff {
|
||||
break
|
||||
}
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func populateDangling(disk ethdb.KeyValueStore) {
|
||||
populate := func(accountHash common.Hash, keys []string, vals []string) {
|
||||
for i, key := range keys {
|
||||
rawdb.WriteStorageSnapshot(disk, accountHash, hashData([]byte(key)), []byte(vals[i]))
|
||||
}
|
||||
}
|
||||
// Dangling storages of the "first" account
|
||||
populate(common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
|
||||
// Dangling storages of the "last" account
|
||||
populate(common.HexToHash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
|
||||
// Dangling storages around the account 1
|
||||
hash := decKey(hashData([]byte("acc-1")).Bytes())
|
||||
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
hash = incKey(hashData([]byte("acc-1")).Bytes())
|
||||
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
|
||||
// Dangling storages around the account 2
|
||||
hash = decKey(hashData([]byte("acc-2")).Bytes())
|
||||
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
hash = incKey(hashData([]byte("acc-2")).Bytes())
|
||||
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
|
||||
// Dangling storages around the account 3
|
||||
hash = decKey(hashData([]byte("acc-3")).Bytes())
|
||||
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
hash = incKey(hashData([]byte("acc-3")).Bytes())
|
||||
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
|
||||
// Dangling storages of the random account
|
||||
populate(randomHash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
populate(randomHash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
populate(randomHash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
}
|
||||
|
||||
// Tests that snapshot generation with dangling storages. Dangling storage means
|
||||
// the storage data is existent while the corresponding account data is missing.
|
||||
//
|
||||
// This test will populate some dangling storages to see if they can be cleaned up.
|
||||
func TestGenerateCompleteSnapshotWithDanglingStorage(t *testing.T) {
|
||||
var helper = newHelper()
|
||||
stRoot := helper.makeStorageTrie([]string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
|
||||
helper.addAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
|
||||
helper.addAccount("acc-2", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
|
||||
helper.addAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
|
||||
|
||||
helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
helper.addSnapStorage("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
|
||||
populateDangling(helper.diskdb)
|
||||
|
||||
root, snap := helper.Generate()
|
||||
select {
|
||||
case <-snap.genPending:
|
||||
// Snapshot generation succeeded
|
||||
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Errorf("Snapshot generation failed")
|
||||
}
|
||||
checkSnapRoot(t, snap, root)
|
||||
|
||||
// Signal abortion to the generator and wait for it to tear down
|
||||
stop := make(chan *generatorStats)
|
||||
snap.genAbort <- stop
|
||||
<-stop
|
||||
}
|
||||
|
||||
// Tests that snapshot generation with dangling storages. Dangling storage means
|
||||
// the storage data is existent while the corresponding account data is missing.
|
||||
//
|
||||
// This test will populate some dangling storages to see if they can be cleaned up.
|
||||
func TestGenerateBrokenSnapshotWithDanglingStorage(t *testing.T) {
|
||||
var helper = newHelper()
|
||||
stRoot := helper.makeStorageTrie([]string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
|
||||
helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
|
||||
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
|
||||
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()})
|
||||
|
||||
populateDangling(helper.diskdb)
|
||||
|
||||
root, snap := helper.Generate()
|
||||
select {
|
||||
case <-snap.genPending:
|
||||
// Snapshot generation succeeded
|
||||
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Errorf("Snapshot generation failed")
|
||||
}
|
||||
checkSnapRoot(t, snap, root)
|
||||
|
||||
// Signal abortion to the generator and wait for it to tear down
|
||||
stop := make(chan *generatorStats)
|
||||
snap.genAbort <- stop
|
||||
<-stop
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
)
|
||||
|
||||
// holdableIterator is a wrapper of underlying database iterator. It extends
|
||||
// the basic iterator interface by adding Hold which can hold the element
|
||||
// locally where the iterator is currently located and serve it up next time.
|
||||
type holdableIterator struct {
|
||||
it ethdb.Iterator
|
||||
key []byte
|
||||
val []byte
|
||||
atHeld bool
|
||||
}
|
||||
|
||||
// newHoldableIterator initializes the holdableIterator with the given iterator.
|
||||
func newHoldableIterator(it ethdb.Iterator) *holdableIterator {
|
||||
return &holdableIterator{it: it}
|
||||
}
|
||||
|
||||
// Hold holds the element locally where the iterator is currently located which
|
||||
// can be served up next time.
|
||||
func (it *holdableIterator) Hold() {
|
||||
if it.it.Key() == nil {
|
||||
return // nothing to hold
|
||||
}
|
||||
it.key = common.CopyBytes(it.it.Key())
|
||||
it.val = common.CopyBytes(it.it.Value())
|
||||
it.atHeld = false
|
||||
}
|
||||
|
||||
// Next moves the iterator to the next key/value pair. It returns whether the
|
||||
// iterator is exhausted.
|
||||
func (it *holdableIterator) Next() bool {
|
||||
if !it.atHeld && it.key != nil {
|
||||
it.atHeld = true
|
||||
} else if it.atHeld {
|
||||
it.atHeld = false
|
||||
it.key = nil
|
||||
it.val = nil
|
||||
}
|
||||
if it.key != nil {
|
||||
return true // shifted to locally held value
|
||||
}
|
||||
return it.it.Next()
|
||||
}
|
||||
|
||||
// Error returns any accumulated error. Exhausting all the key/value pairs
|
||||
// is not considered to be an error.
|
||||
func (it *holdableIterator) Error() error { return it.it.Error() }
|
||||
|
||||
// Release releases associated resources. Release should always succeed and can
|
||||
// be called multiple times without causing error.
|
||||
func (it *holdableIterator) Release() {
|
||||
it.atHeld = false
|
||||
it.key = nil
|
||||
it.val = nil
|
||||
it.it.Release()
|
||||
}
|
||||
|
||||
// Key returns the key of the current key/value pair, or nil if done. The caller
|
||||
// should not modify the contents of the returned slice, and its contents may
|
||||
// change on the next call to Next.
|
||||
func (it *holdableIterator) Key() []byte {
|
||||
if it.key != nil {
|
||||
return it.key
|
||||
}
|
||||
return it.it.Key()
|
||||
}
|
||||
|
||||
// Value returns the value of the current key/value pair, or nil if done. The
|
||||
// caller should not modify the contents of the returned slice, and its contents
|
||||
// may change on the next call to Next.
|
||||
func (it *holdableIterator) Value() []byte {
|
||||
if it.val != nil {
|
||||
return it.val
|
||||
}
|
||||
return it.it.Value()
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
)
|
||||
|
||||
func TestIteratorHold(t *testing.T) {
|
||||
// Create the key-value data store
|
||||
var (
|
||||
content = map[string]string{"k1": "v1", "k2": "v2", "k3": "v3"}
|
||||
order = []string{"k1", "k2", "k3"}
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
)
|
||||
for key, val := range content {
|
||||
if err := db.Put([]byte(key), []byte(val)); err != nil {
|
||||
t.Fatalf("failed to insert item %s:%s into database: %v", key, val, err)
|
||||
}
|
||||
}
|
||||
// Iterate over the database with the given configs and verify the results
|
||||
it, idx := newHoldableIterator(db.NewIterator(nil, nil)), 0
|
||||
|
||||
// Nothing should be affected for calling Discard on non-initialized iterator
|
||||
it.Hold()
|
||||
|
||||
for it.Next() {
|
||||
if len(content) <= idx {
|
||||
t.Errorf("more items than expected: checking idx=%d (key %q), expecting len=%d", idx, it.Key(), len(order))
|
||||
break
|
||||
}
|
||||
if !bytes.Equal(it.Key(), []byte(order[idx])) {
|
||||
t.Errorf("item %d: key mismatch: have %s, want %s", idx, string(it.Key()), order[idx])
|
||||
}
|
||||
if !bytes.Equal(it.Value(), []byte(content[order[idx]])) {
|
||||
t.Errorf("item %d: value mismatch: have %s, want %s", idx, string(it.Value()), content[order[idx]])
|
||||
}
|
||||
// Should be safe to call discard multiple times
|
||||
it.Hold()
|
||||
it.Hold()
|
||||
|
||||
// Shift iterator to the discarded element
|
||||
it.Next()
|
||||
if !bytes.Equal(it.Key(), []byte(order[idx])) {
|
||||
t.Errorf("item %d: key mismatch: have %s, want %s", idx, string(it.Key()), order[idx])
|
||||
}
|
||||
if !bytes.Equal(it.Value(), []byte(content[order[idx]])) {
|
||||
t.Errorf("item %d: value mismatch: have %s, want %s", idx, string(it.Value()), content[order[idx]])
|
||||
}
|
||||
|
||||
// Discard/Next combo should work always
|
||||
it.Hold()
|
||||
it.Next()
|
||||
if !bytes.Equal(it.Key(), []byte(order[idx])) {
|
||||
t.Errorf("item %d: key mismatch: have %s, want %s", idx, string(it.Key()), order[idx])
|
||||
}
|
||||
if !bytes.Equal(it.Value(), []byte(content[order[idx]])) {
|
||||
t.Errorf("item %d: value mismatch: have %s, want %s", idx, string(it.Value()), content[order[idx]])
|
||||
}
|
||||
idx++
|
||||
}
|
||||
if err := it.Error(); err != nil {
|
||||
t.Errorf("iteration failed: %v", err)
|
||||
}
|
||||
if idx != len(order) {
|
||||
t.Errorf("iteration terminated prematurely: have %d, want %d", idx, len(order))
|
||||
}
|
||||
db.Close()
|
||||
}
|
||||
|
||||
func TestReopenIterator(t *testing.T) {
|
||||
var (
|
||||
content = map[common.Hash]string{
|
||||
common.HexToHash("a1"): "v1",
|
||||
common.HexToHash("a2"): "v2",
|
||||
common.HexToHash("a3"): "v3",
|
||||
common.HexToHash("a4"): "v4",
|
||||
common.HexToHash("a5"): "v5",
|
||||
common.HexToHash("a6"): "v6",
|
||||
}
|
||||
order = []common.Hash{
|
||||
common.HexToHash("a1"),
|
||||
common.HexToHash("a2"),
|
||||
common.HexToHash("a3"),
|
||||
common.HexToHash("a4"),
|
||||
common.HexToHash("a5"),
|
||||
common.HexToHash("a6"),
|
||||
}
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
)
|
||||
for key, val := range content {
|
||||
rawdb.WriteAccountSnapshot(db, key, []byte(val))
|
||||
}
|
||||
checkVal := func(it *holdableIterator, index int) {
|
||||
if !bytes.Equal(it.Key(), append(rawdb.SnapshotAccountPrefix, order[index].Bytes()...)) {
|
||||
t.Fatalf("Unexpected data entry key, want %v got %v", order[index], it.Key())
|
||||
}
|
||||
if !bytes.Equal(it.Value(), []byte(content[order[index]])) {
|
||||
t.Fatalf("Unexpected data entry key, want %v got %v", []byte(content[order[index]]), it.Value())
|
||||
}
|
||||
}
|
||||
// Iterate over the database with the given configs and verify the results
|
||||
ctx, idx := newGeneratorContext(&generatorStats{}, db, nil, nil), -1
|
||||
|
||||
idx++
|
||||
ctx.account.Next()
|
||||
checkVal(ctx.account, idx)
|
||||
|
||||
ctx.reopenIterator(snapAccount)
|
||||
idx++
|
||||
ctx.account.Next()
|
||||
checkVal(ctx.account, idx)
|
||||
|
||||
// reopen twice
|
||||
ctx.reopenIterator(snapAccount)
|
||||
ctx.reopenIterator(snapAccount)
|
||||
idx++
|
||||
ctx.account.Next()
|
||||
checkVal(ctx.account, idx)
|
||||
|
||||
// reopen iterator with held value
|
||||
ctx.account.Next()
|
||||
ctx.account.Hold()
|
||||
ctx.reopenIterator(snapAccount)
|
||||
idx++
|
||||
ctx.account.Next()
|
||||
checkVal(ctx.account, idx)
|
||||
|
||||
// reopen twice iterator with held value
|
||||
ctx.account.Next()
|
||||
ctx.account.Hold()
|
||||
ctx.reopenIterator(snapAccount)
|
||||
ctx.reopenIterator(snapAccount)
|
||||
idx++
|
||||
ctx.account.Next()
|
||||
checkVal(ctx.account, idx)
|
||||
|
||||
// shift to the end and reopen
|
||||
ctx.account.Next() // the end
|
||||
ctx.reopenIterator(snapAccount)
|
||||
ctx.account.Next()
|
||||
if ctx.account.Key() != nil {
|
||||
t.Fatal("Unexpected iterated entry")
|
||||
}
|
||||
}
|
||||
@@ -345,3 +345,78 @@ func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
|
||||
log.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root())
|
||||
return base, nil
|
||||
}
|
||||
|
||||
// CheckJournalStorage performs consistency-checks on the journalled
|
||||
// difflayers.
|
||||
func CheckJournalStorage(db ethdb.KeyValueStore) error {
|
||||
journal := rawdb.ReadSnapshotJournal(db)
|
||||
if len(journal) == 0 {
|
||||
log.Warn("Loaded snapshot journal", "diffs", "missing")
|
||||
return nil
|
||||
}
|
||||
r := rlp.NewStream(bytes.NewReader(journal), 0)
|
||||
// Firstly, resolve the first element as the journal version
|
||||
version, err := r.Uint()
|
||||
if err != nil {
|
||||
log.Warn("Failed to resolve the journal version", "error", err)
|
||||
return nil
|
||||
}
|
||||
if version != journalVersion {
|
||||
log.Warn("Discarded the snapshot journal with wrong version", "required", journalVersion, "got", version)
|
||||
return nil
|
||||
}
|
||||
// Secondly, resolve the disk layer root, ensure it's continuous
|
||||
// with disk layer. Note now we can ensure it's the snapshot journal
|
||||
// correct version, so we expect everything can be resolved properly.
|
||||
var root common.Hash
|
||||
if err := r.Decode(&root); err != nil {
|
||||
return errors.New("missing disk layer root")
|
||||
}
|
||||
// The diff journal is not matched with disk, discard them.
|
||||
// It can happen that Geth crashes without persisting the latest
|
||||
// diff journal.
|
||||
// Load all the snapshot diffs from the journal
|
||||
return checkDanglingJournalStorage(r)
|
||||
}
|
||||
|
||||
// loadDiffLayer reads the next sections of a snapshot journal, reconstructing a new
|
||||
// diff and verifying that it can be linked to the requested parent.
|
||||
func checkDanglingJournalStorage(r *rlp.Stream) error {
|
||||
for {
|
||||
// Read the next diff journal entry
|
||||
var root common.Hash
|
||||
if err := r.Decode(&root); err != nil {
|
||||
// The first read may fail with EOF, marking the end of the journal
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("load diff root: %v", err)
|
||||
}
|
||||
var destructs []journalDestruct
|
||||
if err := r.Decode(&destructs); err != nil {
|
||||
return fmt.Errorf("load diff destructs: %v", err)
|
||||
}
|
||||
var accounts []journalAccount
|
||||
if err := r.Decode(&accounts); err != nil {
|
||||
return fmt.Errorf("load diff accounts: %v", err)
|
||||
}
|
||||
accountData := make(map[common.Hash][]byte)
|
||||
for _, entry := range accounts {
|
||||
if len(entry.Blob) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that
|
||||
accountData[entry.Hash] = entry.Blob
|
||||
} else {
|
||||
accountData[entry.Hash] = nil
|
||||
}
|
||||
}
|
||||
var storage []journalStorage
|
||||
if err := r.Decode(&storage); err != nil {
|
||||
return fmt.Errorf("load diff storage: %v", err)
|
||||
}
|
||||
for _, entry := range storage {
|
||||
if _, ok := accountData[entry.Hash]; !ok {
|
||||
log.Error("Dangling storage - missing account", "account", fmt.Sprintf("%#x", entry.Hash), "root", root)
|
||||
return fmt.Errorf("dangling journal snapshot storage account %#x", entry.Hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package snapshot
|
||||
|
||||
import "github.com/ethereum/go-ethereum/metrics"
|
||||
|
||||
// Metrics in generation
|
||||
var (
|
||||
snapGeneratedAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/generated", nil)
|
||||
snapRecoveredAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/recovered", nil)
|
||||
snapWipedAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/wiped", nil)
|
||||
snapMissallAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/missall", nil)
|
||||
snapGeneratedStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/generated", nil)
|
||||
snapRecoveredStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/recovered", nil)
|
||||
snapWipedStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/wiped", nil)
|
||||
snapMissallStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/missall", nil)
|
||||
snapDanglingStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/dangling", nil)
|
||||
snapSuccessfulRangeProofMeter = metrics.NewRegisteredMeter("state/snapshot/generation/proof/success", nil)
|
||||
snapFailedRangeProofMeter = metrics.NewRegisteredMeter("state/snapshot/generation/proof/failure", nil)
|
||||
|
||||
// snapAccountProveCounter measures time spent on the account proving
|
||||
snapAccountProveCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/prove", nil)
|
||||
// snapAccountTrieReadCounter measures time spent on the account trie iteration
|
||||
snapAccountTrieReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/trieread", nil)
|
||||
// snapAccountSnapReadCounter measues time spent on the snapshot account iteration
|
||||
snapAccountSnapReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/snapread", nil)
|
||||
// snapAccountWriteCounter measures time spent on writing/updating/deleting accounts
|
||||
snapAccountWriteCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/write", nil)
|
||||
// snapStorageProveCounter measures time spent on storage proving
|
||||
snapStorageProveCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/prove", nil)
|
||||
// snapStorageTrieReadCounter measures time spent on the storage trie iteration
|
||||
snapStorageTrieReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/trieread", nil)
|
||||
// snapStorageSnapReadCounter measures time spent on the snapshot storage iteration
|
||||
snapStorageSnapReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/snapread", nil)
|
||||
// snapStorageWriteCounter measures time spent on writing/updating storages
|
||||
snapStorageWriteCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/write", nil)
|
||||
// snapStorageCleanCounter measures time spent on deleting storages
|
||||
snapStorageCleanCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/clean", nil)
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
91
core/state/snapshot/wipe.go
Normal file
91
core/state/snapshot/wipe.go
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
// wipeKeyRange deletes a range of keys from the database starting with prefix
|
||||
// and having a specific total key length. The start and limit is optional for
|
||||
// specifying a particular key range for deletion.
|
||||
//
|
||||
// Origin is included for wiping and limit is excluded if they are specified.
|
||||
func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, origin []byte, limit []byte, keylen int, meter metrics.Meter, report bool) error {
|
||||
// Batch deletions together to avoid holding an iterator for too long
|
||||
var (
|
||||
batch = db.NewBatch()
|
||||
items int
|
||||
)
|
||||
// Iterate over the key-range and delete all of them
|
||||
start, logged := time.Now(), time.Now()
|
||||
|
||||
it := db.NewIterator(prefix, origin)
|
||||
var stop []byte
|
||||
if limit != nil {
|
||||
stop = append(prefix, limit...)
|
||||
}
|
||||
for it.Next() {
|
||||
// Skip any keys with the correct prefix but wrong length (trie nodes)
|
||||
key := it.Key()
|
||||
if !bytes.HasPrefix(key, prefix) {
|
||||
break
|
||||
}
|
||||
if len(key) != keylen {
|
||||
continue
|
||||
}
|
||||
if stop != nil && bytes.Compare(key, stop) >= 0 {
|
||||
break
|
||||
}
|
||||
// Delete the key and periodically recreate the batch and iterator
|
||||
batch.Delete(key)
|
||||
items++
|
||||
|
||||
if items%10000 == 0 {
|
||||
// Batch too large (or iterator too long lived, flush and recreate)
|
||||
it.Release()
|
||||
if err := batch.Write(); err != nil {
|
||||
return err
|
||||
}
|
||||
batch.Reset()
|
||||
seekPos := key[len(prefix):]
|
||||
it = db.NewIterator(prefix, seekPos)
|
||||
|
||||
if time.Since(logged) > 8*time.Second && report {
|
||||
log.Info("Deleting state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
logged = time.Now()
|
||||
}
|
||||
}
|
||||
}
|
||||
it.Release()
|
||||
if err := batch.Write(); err != nil {
|
||||
return err
|
||||
}
|
||||
if meter != nil {
|
||||
meter.Mark(int64(items))
|
||||
}
|
||||
if report {
|
||||
log.Info("Deleted state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
79
core/state/snapshot/wipe_test.go
Normal file
79
core/state/snapshot/wipe_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||
)
|
||||
|
||||
// Tests that given a database with random data content, all parts of a snapshot
|
||||
// can be crrectly wiped without touching anything else.
|
||||
func TestWipe(t *testing.T) {
|
||||
// Create a database with some random snapshot data
|
||||
db := memorydb.New()
|
||||
for i := 0; i < 128; i++ {
|
||||
rawdb.WriteAccountSnapshot(db, randomHash(), randomHash().Bytes())
|
||||
}
|
||||
// Add some random non-snapshot data too to make wiping harder
|
||||
for i := 0; i < 500; i++ {
|
||||
// Generate keys with wrong length for a state snapshot item
|
||||
keysuffix := make([]byte, 31)
|
||||
rand.Read(keysuffix)
|
||||
db.Put(append(rawdb.SnapshotAccountPrefix, keysuffix...), randomHash().Bytes())
|
||||
keysuffix = make([]byte, 33)
|
||||
rand.Read(keysuffix)
|
||||
db.Put(append(rawdb.SnapshotAccountPrefix, keysuffix...), randomHash().Bytes())
|
||||
}
|
||||
count := func() (items int) {
|
||||
it := db.NewIterator(rawdb.SnapshotAccountPrefix, nil)
|
||||
defer it.Release()
|
||||
for it.Next() {
|
||||
if len(it.Key()) == len(rawdb.SnapshotAccountPrefix)+common.HashLength {
|
||||
items++
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
// Sanity check that all the keys are present
|
||||
if items := count(); items != 128 {
|
||||
t.Fatalf("snapshot size mismatch: have %d, want %d", items, 128)
|
||||
}
|
||||
// Wipe the accounts
|
||||
if err := wipeKeyRange(db, "accounts", rawdb.SnapshotAccountPrefix, nil, nil,
|
||||
len(rawdb.SnapshotAccountPrefix)+common.HashLength, snapWipedAccountMeter, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Iterate over the database end ensure no snapshot information remains
|
||||
if items := count(); items != 0 {
|
||||
t.Fatalf("snapshot size mismatch: have %d, want %d", items, 0)
|
||||
}
|
||||
// Iterate over the database and ensure miscellaneous items are present
|
||||
items := 0
|
||||
it := db.NewIterator(nil, nil)
|
||||
defer it.Release()
|
||||
for it.Next() {
|
||||
items++
|
||||
}
|
||||
if items != 1000 {
|
||||
t.Fatalf("misc item count mismatch: have %d, want %d", items, 1000)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -223,15 +223,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||
if err != nil || !contract.UseGas(dynamicCost) {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
// Do tracing before memory expansion
|
||||
if in.cfg.Debug {
|
||||
in.cfg.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
|
||||
logged = true
|
||||
}
|
||||
if memorySize > 0 {
|
||||
mem.Resize(memorySize)
|
||||
}
|
||||
} else if in.cfg.Debug {
|
||||
}
|
||||
if in.cfg.Debug {
|
||||
in.cfg.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
|
||||
logged = true
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -134,6 +134,10 @@ func TestCallTracerNative(t *testing.T) {
|
||||
testCallTracer("callTracer", "call_tracer", t)
|
||||
}
|
||||
|
||||
func TestCallTracerLegacyDuktape(t *testing.T) {
|
||||
testCallTracer("callTracerLegacyDuktape", "call_tracer_legacy", t)
|
||||
}
|
||||
|
||||
func testCallTracer(tracerName string, dirPath string, t *testing.T) {
|
||||
files, err := os.ReadDir(filepath.Join("testdata", dirPath))
|
||||
if err != nil {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package js
|
||||
|
||||
import (
|
||||
@@ -31,6 +30,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
jsassets "github.com/ethereum/go-ethereum/eth/tracers/js/internal/tracers"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
var assetTracers = make(map[string]string)
|
||||
@@ -42,7 +42,7 @@ func init() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tracers.RegisterLookup(true, newJsTracer)
|
||||
tracers.RegisterLookup(true, newGojaTracer)
|
||||
}
|
||||
|
||||
// bigIntProgram is compiled once and the exported function mostly invoked to convert
|
||||
@@ -90,9 +90,7 @@ func fromBuf(vm *goja.Runtime, bufType goja.Value, buf goja.Value, allowString b
|
||||
return nil, fmt.Errorf("invalid buffer type")
|
||||
}
|
||||
|
||||
// jsTracer is an implementation of the Tracer interface which evaluates
|
||||
// JS functions on the relevant EVM hooks. It uses Goja as its JS engine.
|
||||
type jsTracer struct {
|
||||
type gojaTracer struct {
|
||||
vm *goja.Runtime
|
||||
env *vm.EVM
|
||||
toBig toBigFn // Converts a hex string into a JS bigint
|
||||
@@ -125,20 +123,14 @@ type jsTracer struct {
|
||||
frameResultValue goja.Value
|
||||
}
|
||||
|
||||
// newJsTracer instantiates a new JS tracer instance. code is either
|
||||
// the name of a built-in JS tracer or a Javascript snippet which
|
||||
// evaluates to an expression returning an object with certain methods.
|
||||
// The methods `result` and `fault` are required to be present.
|
||||
// The methods `step`, `enter`, and `exit` are optional, but note that
|
||||
// `enter` and `exit` always go together.
|
||||
func newJsTracer(code string, ctx *tracers.Context) (tracers.Tracer, error) {
|
||||
func newGojaTracer(code string, ctx *tracers.Context) (tracers.Tracer, error) {
|
||||
if c, ok := assetTracers[code]; ok {
|
||||
code = c
|
||||
}
|
||||
vm := goja.New()
|
||||
// By default field names are exported to JS as is, i.e. capitalized.
|
||||
vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
|
||||
t := &jsTracer{
|
||||
t := &gojaTracer{
|
||||
vm: vm,
|
||||
ctx: make(map[string]goja.Value),
|
||||
}
|
||||
@@ -187,8 +179,8 @@ func newJsTracer(code string, ctx *tracers.Context) (tracers.Tracer, error) {
|
||||
t.log = &steplog{
|
||||
vm: vm,
|
||||
op: &opObj{vm: vm},
|
||||
memory: &memoryObj{vm: vm, toBig: t.toBig, toBuf: t.toBuf},
|
||||
stack: &stackObj{vm: vm, toBig: t.toBig},
|
||||
memory: &memoryObj{w: new(memoryWrapper), vm: vm, toBig: t.toBig, toBuf: t.toBuf},
|
||||
stack: &stackObj{w: new(stackWrapper), vm: vm, toBig: t.toBig},
|
||||
contract: &contractObj{vm: vm, toBig: t.toBig, toBuf: t.toBuf},
|
||||
}
|
||||
t.frame = &callframe{vm: vm, toBig: t.toBig, toBuf: t.toBuf}
|
||||
@@ -201,16 +193,16 @@ func newJsTracer(code string, ctx *tracers.Context) (tracers.Tracer, error) {
|
||||
|
||||
// CaptureTxStart implements the Tracer interface and is invoked at the beginning of
|
||||
// transaction processing.
|
||||
func (t *jsTracer) CaptureTxStart(gasLimit uint64) {
|
||||
func (t *gojaTracer) CaptureTxStart(gasLimit uint64) {
|
||||
t.gasLimit = gasLimit
|
||||
}
|
||||
|
||||
// CaptureTxStart implements the Tracer interface and is invoked at the end of
|
||||
// transaction processing.
|
||||
func (t *jsTracer) CaptureTxEnd(restGas uint64) {}
|
||||
func (t *gojaTracer) CaptureTxEnd(restGas uint64) {}
|
||||
|
||||
// CaptureStart implements the Tracer interface to initialize the tracing operation.
|
||||
func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
func (t *gojaTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
t.env = env
|
||||
db := &dbObj{db: env.StateDB, vm: t.vm, toBig: t.toBig, toBuf: t.toBuf, fromBuf: t.fromBuf}
|
||||
t.dbValue = db.setupObject()
|
||||
@@ -238,7 +230,7 @@ func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr
|
||||
}
|
||||
|
||||
// CaptureState implements the Tracer interface to trace a single step of VM execution.
|
||||
func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
func (t *gojaTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
if !t.traceStep {
|
||||
return
|
||||
}
|
||||
@@ -248,8 +240,8 @@ func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope
|
||||
|
||||
log := t.log
|
||||
log.op.op = op
|
||||
log.memory.memory = scope.Memory
|
||||
log.stack.stack = scope.Stack
|
||||
log.memory.w.memory = scope.Memory
|
||||
log.stack.w.stack = scope.Stack
|
||||
log.contract.contract = scope.Contract
|
||||
log.pc = uint(pc)
|
||||
log.gas = uint(gas)
|
||||
@@ -257,24 +249,24 @@ func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope
|
||||
log.depth = uint(depth)
|
||||
log.err = err
|
||||
if _, err := t.step(t.obj, t.logValue, t.dbValue); err != nil {
|
||||
t.onError("step", err)
|
||||
t.err = wrapError("step", err)
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureFault implements the Tracer interface to trace an execution fault
|
||||
func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
func (t *gojaTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
if t.err != nil {
|
||||
return
|
||||
}
|
||||
// Other log fields have been already set as part of the last CaptureState.
|
||||
t.log.err = err
|
||||
if _, err := t.fault(t.obj, t.logValue, t.dbValue); err != nil {
|
||||
t.onError("fault", err)
|
||||
t.err = wrapError("fault", err)
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureEnd is called after the call finishes to finalize the tracing.
|
||||
func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, duration time.Duration, err error) {
|
||||
func (t *gojaTracer) CaptureEnd(output []byte, gasUsed uint64, duration time.Duration, err error) {
|
||||
t.ctx["output"] = t.vm.ToValue(output)
|
||||
t.ctx["time"] = t.vm.ToValue(duration.String())
|
||||
t.ctx["gasUsed"] = t.vm.ToValue(gasUsed)
|
||||
@@ -284,7 +276,7 @@ func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, duration time.Durat
|
||||
}
|
||||
|
||||
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
|
||||
func (t *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
func (t *gojaTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
if !t.traceFrame {
|
||||
return
|
||||
}
|
||||
@@ -303,13 +295,13 @@ func (t *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Ad
|
||||
}
|
||||
|
||||
if _, err := t.enter(t.obj, t.frameValue); err != nil {
|
||||
t.onError("enter", err)
|
||||
t.err = wrapError("enter", err)
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureExit is called when EVM exits a scope, even if the scope didn't
|
||||
// execute any code.
|
||||
func (t *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
func (t *gojaTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
if !t.traceFrame {
|
||||
return
|
||||
}
|
||||
@@ -319,12 +311,12 @@ func (t *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
t.frameResult.err = err
|
||||
|
||||
if _, err := t.exit(t.obj, t.frameResultValue); err != nil {
|
||||
t.onError("exit", err)
|
||||
t.err = wrapError("exit", err)
|
||||
}
|
||||
}
|
||||
|
||||
// GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
|
||||
func (t *jsTracer) GetResult() (json.RawMessage, error) {
|
||||
func (t *gojaTracer) GetResult() (json.RawMessage, error) {
|
||||
ctx := t.vm.ToValue(t.ctx)
|
||||
res, err := t.result(t.obj, ctx, t.dbValue)
|
||||
if err != nil {
|
||||
@@ -338,34 +330,20 @@ func (t *jsTracer) GetResult() (json.RawMessage, error) {
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
func (t *jsTracer) Stop(err error) {
|
||||
func (t *gojaTracer) Stop(err error) {
|
||||
t.vm.Interrupt(err)
|
||||
}
|
||||
|
||||
// onError is called anytime the running JS code is interrupted
|
||||
// and returns an error. It in turn pings the EVM to cancel its
|
||||
// execution.
|
||||
func (t *jsTracer) onError(context string, err error) {
|
||||
t.err = wrapError(context, err)
|
||||
// `env` is set on CaptureStart which comes before any JS execution.
|
||||
// So it should be non-nil.
|
||||
t.env.Cancel()
|
||||
}
|
||||
|
||||
func wrapError(context string, err error) error {
|
||||
return fmt.Errorf("%v in server-side tracer function '%v'", err, context)
|
||||
}
|
||||
|
||||
// setBuiltinFunctions injects Go functions which are available to tracers into the environment.
|
||||
// It depends on type converters having been set up.
|
||||
func (t *jsTracer) setBuiltinFunctions() {
|
||||
func (t *gojaTracer) setBuiltinFunctions() {
|
||||
vm := t.vm
|
||||
// TODO: load console from goja-nodejs
|
||||
vm.Set("toHex", func(v goja.Value) string {
|
||||
b, err := t.fromBuf(vm, v, false)
|
||||
if err != nil {
|
||||
vm.Interrupt(err)
|
||||
return ""
|
||||
panic(err)
|
||||
}
|
||||
return hexutil.Encode(b)
|
||||
})
|
||||
@@ -373,73 +351,63 @@ func (t *jsTracer) setBuiltinFunctions() {
|
||||
// TODO: add test with []byte len < 32 or > 32
|
||||
b, err := t.fromBuf(vm, v, true)
|
||||
if err != nil {
|
||||
vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
b = common.BytesToHash(b).Bytes()
|
||||
res, err := t.toBuf(vm, b)
|
||||
if err != nil {
|
||||
vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
})
|
||||
vm.Set("toAddress", func(v goja.Value) goja.Value {
|
||||
a, err := t.fromBuf(vm, v, true)
|
||||
if err != nil {
|
||||
vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
a = common.BytesToAddress(a).Bytes()
|
||||
res, err := t.toBuf(vm, a)
|
||||
if err != nil {
|
||||
vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
})
|
||||
vm.Set("toContract", func(from goja.Value, nonce uint) goja.Value {
|
||||
a, err := t.fromBuf(vm, from, true)
|
||||
if err != nil {
|
||||
vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
addr := common.BytesToAddress(a)
|
||||
b := crypto.CreateAddress(addr, uint64(nonce)).Bytes()
|
||||
res, err := t.toBuf(vm, b)
|
||||
if err != nil {
|
||||
vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
})
|
||||
vm.Set("toContract2", func(from goja.Value, salt string, initcode goja.Value) goja.Value {
|
||||
a, err := t.fromBuf(vm, from, true)
|
||||
if err != nil {
|
||||
vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
addr := common.BytesToAddress(a)
|
||||
code, err := t.fromBuf(vm, initcode, true)
|
||||
if err != nil {
|
||||
vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
code = common.CopyBytes(code)
|
||||
codeHash := crypto.Keccak256(code)
|
||||
b := crypto.CreateAddress2(addr, common.HexToHash(salt), codeHash).Bytes()
|
||||
res, err := t.toBuf(vm, b)
|
||||
if err != nil {
|
||||
vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
})
|
||||
vm.Set("isPrecompiled", func(v goja.Value) bool {
|
||||
a, err := t.fromBuf(vm, v, true)
|
||||
if err != nil {
|
||||
vm.Interrupt(err)
|
||||
return false
|
||||
panic(err)
|
||||
}
|
||||
addr := common.BytesToAddress(a)
|
||||
for _, p := range t.activePrecompiles {
|
||||
@@ -452,17 +420,14 @@ func (t *jsTracer) setBuiltinFunctions() {
|
||||
vm.Set("slice", func(slice goja.Value, start, end int) goja.Value {
|
||||
b, err := t.fromBuf(vm, slice, false)
|
||||
if err != nil {
|
||||
vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
if start < 0 || start > end || end > len(b) {
|
||||
vm.Interrupt(fmt.Sprintf("Tracer accessed out of bound memory: available %d, offset %d, size %d", len(b), start, end-start))
|
||||
return nil
|
||||
log.Warn("Tracer accessed out of bound memory", "available", len(b), "offset", start, "size", end-start)
|
||||
}
|
||||
res, err := t.toBuf(vm, b[start:end])
|
||||
if err != nil {
|
||||
vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
})
|
||||
@@ -470,7 +435,7 @@ func (t *jsTracer) setBuiltinFunctions() {
|
||||
|
||||
// setTypeConverters sets up utilities for converting Go types into those
|
||||
// suitable for JS consumption.
|
||||
func (t *jsTracer) setTypeConverters() error {
|
||||
func (t *gojaTracer) setTypeConverters() error {
|
||||
// Inject bigint logic.
|
||||
// TODO: To be replaced after goja adds support for native JS bigint.
|
||||
toBigCode, err := t.vm.RunProgram(bigIntProgram)
|
||||
@@ -528,64 +493,32 @@ func (o *opObj) setupObject() *goja.Object {
|
||||
}
|
||||
|
||||
type memoryObj struct {
|
||||
memory *vm.Memory
|
||||
vm *goja.Runtime
|
||||
toBig toBigFn
|
||||
toBuf toBufFn
|
||||
w *memoryWrapper
|
||||
vm *goja.Runtime
|
||||
toBig toBigFn
|
||||
toBuf toBufFn
|
||||
}
|
||||
|
||||
func (mo *memoryObj) Slice(begin, end int64) goja.Value {
|
||||
b, err := mo.slice(begin, end)
|
||||
if err != nil {
|
||||
mo.vm.Interrupt(err)
|
||||
return nil
|
||||
}
|
||||
b := mo.w.slice(begin, end)
|
||||
res, err := mo.toBuf(mo.vm, b)
|
||||
if err != nil {
|
||||
mo.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// slice returns the requested range of memory as a byte slice.
|
||||
func (mo *memoryObj) slice(begin, end int64) ([]byte, error) {
|
||||
if end == begin {
|
||||
return []byte{}, nil
|
||||
}
|
||||
if end < begin || begin < 0 {
|
||||
return nil, fmt.Errorf("Tracer accessed out of bound memory: offset %d, end %d", begin, end)
|
||||
}
|
||||
if mo.memory.Len() < int(end) {
|
||||
return nil, fmt.Errorf("Tracer accessed out of bound memory: available %d, offset %d, size %d", mo.memory.Len(), begin, end-begin)
|
||||
}
|
||||
return mo.memory.GetCopy(begin, end-begin), nil
|
||||
}
|
||||
|
||||
func (mo *memoryObj) GetUint(addr int64) goja.Value {
|
||||
value, err := mo.getUint(addr)
|
||||
if err != nil {
|
||||
mo.vm.Interrupt(err)
|
||||
return nil
|
||||
}
|
||||
value := mo.w.getUint(addr)
|
||||
res, err := mo.toBig(mo.vm, value.String())
|
||||
if err != nil {
|
||||
mo.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// getUint returns the 32 bytes at the specified address interpreted as a uint.
|
||||
func (mo *memoryObj) getUint(addr int64) (*big.Int, error) {
|
||||
if mo.memory.Len() < int(addr)+32 || addr < 0 {
|
||||
return nil, fmt.Errorf("Tracer accessed out of bound memory: available %d, offset %d, size %d", mo.memory.Len(), addr, 32)
|
||||
}
|
||||
return new(big.Int).SetBytes(mo.memory.GetPtr(addr, 32)), nil
|
||||
}
|
||||
|
||||
func (mo *memoryObj) Length() int {
|
||||
return mo.memory.Len()
|
||||
return mo.w.memory.Len()
|
||||
}
|
||||
|
||||
func (m *memoryObj) setupObject() *goja.Object {
|
||||
@@ -597,35 +530,22 @@ func (m *memoryObj) setupObject() *goja.Object {
|
||||
}
|
||||
|
||||
type stackObj struct {
|
||||
stack *vm.Stack
|
||||
w *stackWrapper
|
||||
vm *goja.Runtime
|
||||
toBig toBigFn
|
||||
}
|
||||
|
||||
func (s *stackObj) Peek(idx int) goja.Value {
|
||||
value, err := s.peek(idx)
|
||||
if err != nil {
|
||||
s.vm.Interrupt(err)
|
||||
return nil
|
||||
}
|
||||
value := s.w.peek(idx)
|
||||
res, err := s.toBig(s.vm, value.String())
|
||||
if err != nil {
|
||||
s.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// peek returns the nth-from-the-top element of the stack.
|
||||
func (s *stackObj) peek(idx int) (*big.Int, error) {
|
||||
if len(s.stack.Data()) <= idx || idx < 0 {
|
||||
return nil, fmt.Errorf("Tracer accessed out of bound stack: size %d, index %d", len(s.stack.Data()), idx)
|
||||
}
|
||||
return s.stack.Back(idx).ToBig(), nil
|
||||
}
|
||||
|
||||
func (s *stackObj) Length() int {
|
||||
return len(s.stack.Data())
|
||||
return len(s.w.stack.Data())
|
||||
}
|
||||
|
||||
func (s *stackObj) setupObject() *goja.Object {
|
||||
@@ -646,15 +566,13 @@ type dbObj struct {
|
||||
func (do *dbObj) GetBalance(addrSlice goja.Value) goja.Value {
|
||||
a, err := do.fromBuf(do.vm, addrSlice, false)
|
||||
if err != nil {
|
||||
do.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
addr := common.BytesToAddress(a)
|
||||
value := do.db.GetBalance(addr)
|
||||
res, err := do.toBig(do.vm, value.String())
|
||||
if err != nil {
|
||||
do.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -662,8 +580,7 @@ func (do *dbObj) GetBalance(addrSlice goja.Value) goja.Value {
|
||||
func (do *dbObj) GetNonce(addrSlice goja.Value) uint64 {
|
||||
a, err := do.fromBuf(do.vm, addrSlice, false)
|
||||
if err != nil {
|
||||
do.vm.Interrupt(err)
|
||||
return 0
|
||||
panic(err)
|
||||
}
|
||||
addr := common.BytesToAddress(a)
|
||||
return do.db.GetNonce(addr)
|
||||
@@ -672,15 +589,13 @@ func (do *dbObj) GetNonce(addrSlice goja.Value) uint64 {
|
||||
func (do *dbObj) GetCode(addrSlice goja.Value) goja.Value {
|
||||
a, err := do.fromBuf(do.vm, addrSlice, false)
|
||||
if err != nil {
|
||||
do.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
addr := common.BytesToAddress(a)
|
||||
code := do.db.GetCode(addr)
|
||||
res, err := do.toBuf(do.vm, code)
|
||||
if err != nil {
|
||||
do.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -688,21 +603,18 @@ func (do *dbObj) GetCode(addrSlice goja.Value) goja.Value {
|
||||
func (do *dbObj) GetState(addrSlice goja.Value, hashSlice goja.Value) goja.Value {
|
||||
a, err := do.fromBuf(do.vm, addrSlice, false)
|
||||
if err != nil {
|
||||
do.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
addr := common.BytesToAddress(a)
|
||||
h, err := do.fromBuf(do.vm, hashSlice, false)
|
||||
if err != nil {
|
||||
do.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
hash := common.BytesToHash(h)
|
||||
state := do.db.GetState(addr, hash).Bytes()
|
||||
res, err := do.toBuf(do.vm, state)
|
||||
if err != nil {
|
||||
do.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -710,8 +622,7 @@ func (do *dbObj) GetState(addrSlice goja.Value, hashSlice goja.Value) goja.Value
|
||||
func (do *dbObj) Exists(addrSlice goja.Value) bool {
|
||||
a, err := do.fromBuf(do.vm, addrSlice, false)
|
||||
if err != nil {
|
||||
do.vm.Interrupt(err)
|
||||
return false
|
||||
panic(err)
|
||||
}
|
||||
addr := common.BytesToAddress(a)
|
||||
return do.db.Exist(addr)
|
||||
@@ -738,8 +649,7 @@ func (co *contractObj) GetCaller() goja.Value {
|
||||
caller := co.contract.Caller().Bytes()
|
||||
res, err := co.toBuf(co.vm, caller)
|
||||
if err != nil {
|
||||
co.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -748,8 +658,7 @@ func (co *contractObj) GetAddress() goja.Value {
|
||||
addr := co.contract.Address().Bytes()
|
||||
res, err := co.toBuf(co.vm, addr)
|
||||
if err != nil {
|
||||
co.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -758,8 +667,7 @@ func (co *contractObj) GetValue() goja.Value {
|
||||
value := co.contract.Value()
|
||||
res, err := co.toBig(co.vm, value.String())
|
||||
if err != nil {
|
||||
co.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -768,8 +676,7 @@ func (co *contractObj) GetInput() goja.Value {
|
||||
input := co.contract.Input
|
||||
res, err := co.toBuf(co.vm, input)
|
||||
if err != nil {
|
||||
co.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -804,8 +711,7 @@ func (f *callframe) GetFrom() goja.Value {
|
||||
from := f.from.Bytes()
|
||||
res, err := f.toBuf(f.vm, from)
|
||||
if err != nil {
|
||||
f.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -814,8 +720,7 @@ func (f *callframe) GetTo() goja.Value {
|
||||
to := f.to.Bytes()
|
||||
res, err := f.toBuf(f.vm, to)
|
||||
if err != nil {
|
||||
f.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -824,8 +729,7 @@ func (f *callframe) GetInput() goja.Value {
|
||||
input := f.input
|
||||
res, err := f.toBuf(f.vm, input)
|
||||
if err != nil {
|
||||
f.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -840,8 +744,7 @@ func (f *callframe) GetValue() goja.Value {
|
||||
}
|
||||
res, err := f.toBig(f.vm, f.value.String())
|
||||
if err != nil {
|
||||
f.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -873,8 +776,7 @@ func (r *callframeResult) GetGasUsed() uint {
|
||||
func (r *callframeResult) GetOutput() goja.Value {
|
||||
res, err := r.toBuf(r.vm, r.output)
|
||||
if err != nil {
|
||||
r.vm.Interrupt(err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
885
eth/tracers/js/tracer.go
Normal file
885
eth/tracers/js/tracer.go
Normal file
@@ -0,0 +1,885 @@
|
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package js is a collection of tracers written in javascript.
|
||||
package js
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
jsassets "github.com/ethereum/go-ethereum/eth/tracers/js/internal/tracers"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"gopkg.in/olebedev/go-duktape.v3"
|
||||
)
|
||||
|
||||
// init retrieves the JavaScript transaction tracers included in go-ethereum.
|
||||
func init() {
|
||||
assetTracers, err := jsassets.Load()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// TODO: Either disable duktape or solve conflicts between goja and duktape
|
||||
tracers.RegisterLookup(false, func(name string, ctx *tracers.Context) (tracers.Tracer, error) {
|
||||
if !strings.HasSuffix(name, "Duktape") {
|
||||
return nil, errors.New("only suffix Duktape supported")
|
||||
}
|
||||
name = strings.TrimSuffix(name, "Duktape")
|
||||
code, ok := assetTracers[name]
|
||||
if !ok {
|
||||
return nil, errors.New("only pre-built tracers supported")
|
||||
}
|
||||
return newJsTracer(code, ctx)
|
||||
})
|
||||
}
|
||||
|
||||
// makeSlice convert an unsafe memory pointer with the given type into a Go byte
|
||||
// slice.
|
||||
//
|
||||
// Note, the returned slice uses the same memory area as the input arguments.
|
||||
// If those are duktape stack items, popping them off **will** make the slice
|
||||
// contents change.
|
||||
func makeSlice(ptr unsafe.Pointer, size uint) []byte {
|
||||
var sl = struct {
|
||||
addr uintptr
|
||||
len int
|
||||
cap int
|
||||
}{uintptr(ptr), int(size), int(size)}
|
||||
|
||||
return *(*[]byte)(unsafe.Pointer(&sl))
|
||||
}
|
||||
|
||||
// popSlice pops a buffer off the JavaScript stack and returns it as a slice.
|
||||
func popSlice(ctx *duktape.Context) []byte {
|
||||
blob := common.CopyBytes(makeSlice(ctx.GetBuffer(-1)))
|
||||
ctx.Pop()
|
||||
return blob
|
||||
}
|
||||
|
||||
// pushBigInt create a JavaScript BigInteger in the VM.
|
||||
func pushBigInt(n *big.Int, ctx *duktape.Context) {
|
||||
ctx.GetGlobalString("bigInt")
|
||||
ctx.PushString(n.String())
|
||||
ctx.Call(1)
|
||||
}
|
||||
|
||||
// opWrapper provides a JavaScript wrapper around OpCode.
|
||||
type opWrapper struct {
|
||||
op vm.OpCode
|
||||
}
|
||||
|
||||
// pushObject assembles a JSVM object wrapping a swappable opcode and pushes it
|
||||
// onto the VM stack.
|
||||
func (ow *opWrapper) pushObject(vm *duktape.Context) {
|
||||
obj := vm.PushObject()
|
||||
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushInt(int(ow.op)); return 1 })
|
||||
vm.PutPropString(obj, "toNumber")
|
||||
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushString(ow.op.String()); return 1 })
|
||||
vm.PutPropString(obj, "toString")
|
||||
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushBoolean(ow.op.IsPush()); return 1 })
|
||||
vm.PutPropString(obj, "isPush")
|
||||
}
|
||||
|
||||
// memoryWrapper provides a JavaScript wrapper around vm.Memory.
|
||||
type memoryWrapper struct {
|
||||
memory *vm.Memory
|
||||
}
|
||||
|
||||
// slice returns the requested range of memory as a byte slice.
|
||||
func (mw *memoryWrapper) slice(begin, end int64) []byte {
|
||||
if end == begin {
|
||||
return []byte{}
|
||||
}
|
||||
if end < begin || begin < 0 {
|
||||
// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
|
||||
// runtime goes belly up https://github.com/golang/go/issues/15639.
|
||||
log.Warn("Tracer accessed out of bound memory", "offset", begin, "end", end)
|
||||
return nil
|
||||
}
|
||||
if mw.memory.Len() < int(end) {
|
||||
// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
|
||||
// runtime goes belly up https://github.com/golang/go/issues/15639.
|
||||
log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", begin, "size", end-begin)
|
||||
return nil
|
||||
}
|
||||
return mw.memory.GetCopy(begin, end-begin)
|
||||
}
|
||||
|
||||
// getUint returns the 32 bytes at the specified address interpreted as a uint.
|
||||
func (mw *memoryWrapper) getUint(addr int64) *big.Int {
|
||||
if mw.memory.Len() < int(addr)+32 || addr < 0 {
|
||||
// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
|
||||
// runtime goes belly up https://github.com/golang/go/issues/15639.
|
||||
log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", addr, "size", 32)
|
||||
return new(big.Int)
|
||||
}
|
||||
return new(big.Int).SetBytes(mw.memory.GetPtr(addr, 32))
|
||||
}
|
||||
|
||||
// pushObject assembles a JSVM object wrapping a swappable memory and pushes it
|
||||
// onto the VM stack.
|
||||
func (mw *memoryWrapper) pushObject(vm *duktape.Context) {
|
||||
obj := vm.PushObject()
|
||||
|
||||
// Generate the `length` method which returns the memory length
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushInt(mw.memory.Len()); return 1 })
|
||||
vm.PutPropString(obj, "length")
|
||||
|
||||
// Generate the `slice` method which takes two ints and returns a buffer
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int {
|
||||
blob := mw.slice(int64(ctx.GetInt(-2)), int64(ctx.GetInt(-1)))
|
||||
ctx.Pop2()
|
||||
|
||||
ptr := ctx.PushFixedBuffer(len(blob))
|
||||
copy(makeSlice(ptr, uint(len(blob))), blob)
|
||||
return 1
|
||||
})
|
||||
vm.PutPropString(obj, "slice")
|
||||
|
||||
// Generate the `getUint` method which takes an int and returns a bigint
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int {
|
||||
offset := int64(ctx.GetInt(-1))
|
||||
ctx.Pop()
|
||||
|
||||
pushBigInt(mw.getUint(offset), ctx)
|
||||
return 1
|
||||
})
|
||||
vm.PutPropString(obj, "getUint")
|
||||
}
|
||||
|
||||
// stackWrapper provides a JavaScript wrapper around vm.Stack.
|
||||
type stackWrapper struct {
|
||||
stack *vm.Stack
|
||||
}
|
||||
|
||||
// peek returns the nth-from-the-top element of the stack.
|
||||
func (sw *stackWrapper) peek(idx int) *big.Int {
|
||||
if len(sw.stack.Data()) <= idx || idx < 0 {
|
||||
// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
|
||||
// runtime goes belly up https://github.com/golang/go/issues/15639.
|
||||
log.Warn("Tracer accessed out of bound stack", "size", len(sw.stack.Data()), "index", idx)
|
||||
return new(big.Int)
|
||||
}
|
||||
return sw.stack.Back(idx).ToBig()
|
||||
}
|
||||
|
||||
// pushObject assembles a JSVM object wrapping a swappable stack and pushes it
|
||||
// onto the VM stack.
|
||||
func (sw *stackWrapper) pushObject(vm *duktape.Context) {
|
||||
obj := vm.PushObject()
|
||||
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushInt(len(sw.stack.Data())); return 1 })
|
||||
vm.PutPropString(obj, "length")
|
||||
|
||||
// Generate the `peek` method which takes an int and returns a bigint
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int {
|
||||
offset := ctx.GetInt(-1)
|
||||
ctx.Pop()
|
||||
|
||||
pushBigInt(sw.peek(offset), ctx)
|
||||
return 1
|
||||
})
|
||||
vm.PutPropString(obj, "peek")
|
||||
}
|
||||
|
||||
// dbWrapper provides a JavaScript wrapper around vm.Database.
|
||||
type dbWrapper struct {
|
||||
db vm.StateDB
|
||||
}
|
||||
|
||||
// pushObject assembles a JSVM object wrapping a swappable database and pushes it
|
||||
// onto the VM stack.
|
||||
func (dw *dbWrapper) pushObject(vm *duktape.Context) {
|
||||
obj := vm.PushObject()
|
||||
|
||||
// Push the wrapper for statedb.GetBalance
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int {
|
||||
pushBigInt(dw.db.GetBalance(common.BytesToAddress(popSlice(ctx))), ctx)
|
||||
return 1
|
||||
})
|
||||
vm.PutPropString(obj, "getBalance")
|
||||
|
||||
// Push the wrapper for statedb.GetNonce
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int {
|
||||
ctx.PushInt(int(dw.db.GetNonce(common.BytesToAddress(popSlice(ctx)))))
|
||||
return 1
|
||||
})
|
||||
vm.PutPropString(obj, "getNonce")
|
||||
|
||||
// Push the wrapper for statedb.GetCode
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int {
|
||||
code := dw.db.GetCode(common.BytesToAddress(popSlice(ctx)))
|
||||
|
||||
ptr := ctx.PushFixedBuffer(len(code))
|
||||
copy(makeSlice(ptr, uint(len(code))), code)
|
||||
return 1
|
||||
})
|
||||
vm.PutPropString(obj, "getCode")
|
||||
|
||||
// Push the wrapper for statedb.GetState
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int {
|
||||
hash := popSlice(ctx)
|
||||
addr := popSlice(ctx)
|
||||
|
||||
state := dw.db.GetState(common.BytesToAddress(addr), common.BytesToHash(hash))
|
||||
|
||||
ptr := ctx.PushFixedBuffer(len(state))
|
||||
copy(makeSlice(ptr, uint(len(state))), state[:])
|
||||
return 1
|
||||
})
|
||||
vm.PutPropString(obj, "getState")
|
||||
|
||||
// Push the wrapper for statedb.Exists
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int {
|
||||
ctx.PushBoolean(dw.db.Exist(common.BytesToAddress(popSlice(ctx))))
|
||||
return 1
|
||||
})
|
||||
vm.PutPropString(obj, "exists")
|
||||
}
|
||||
|
||||
// contractWrapper provides a JavaScript wrapper around vm.Contract
|
||||
type contractWrapper struct {
|
||||
contract *vm.Contract
|
||||
}
|
||||
|
||||
// pushObject assembles a JSVM object wrapping a swappable contract and pushes it
|
||||
// onto the VM stack.
|
||||
func (cw *contractWrapper) pushObject(vm *duktape.Context) {
|
||||
obj := vm.PushObject()
|
||||
|
||||
// Push the wrapper for contract.Caller
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int {
|
||||
ptr := ctx.PushFixedBuffer(20)
|
||||
copy(makeSlice(ptr, 20), cw.contract.Caller().Bytes())
|
||||
return 1
|
||||
})
|
||||
vm.PutPropString(obj, "getCaller")
|
||||
|
||||
// Push the wrapper for contract.Address
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int {
|
||||
ptr := ctx.PushFixedBuffer(20)
|
||||
copy(makeSlice(ptr, 20), cw.contract.Address().Bytes())
|
||||
return 1
|
||||
})
|
||||
vm.PutPropString(obj, "getAddress")
|
||||
|
||||
// Push the wrapper for contract.Value
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int {
|
||||
pushBigInt(cw.contract.Value(), ctx)
|
||||
return 1
|
||||
})
|
||||
vm.PutPropString(obj, "getValue")
|
||||
|
||||
// Push the wrapper for contract.Input
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int {
|
||||
blob := cw.contract.Input
|
||||
|
||||
ptr := ctx.PushFixedBuffer(len(blob))
|
||||
copy(makeSlice(ptr, uint(len(blob))), blob)
|
||||
return 1
|
||||
})
|
||||
vm.PutPropString(obj, "getInput")
|
||||
}
|
||||
|
||||
type frame struct {
|
||||
typ *string
|
||||
from *common.Address
|
||||
to *common.Address
|
||||
input []byte
|
||||
gas *uint
|
||||
value *big.Int
|
||||
}
|
||||
|
||||
func newFrame() *frame {
|
||||
return &frame{
|
||||
typ: new(string),
|
||||
from: new(common.Address),
|
||||
to: new(common.Address),
|
||||
gas: new(uint),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frame) pushObject(vm *duktape.Context) {
|
||||
obj := vm.PushObject()
|
||||
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.typ); return 1 })
|
||||
vm.PutPropString(obj, "getType")
|
||||
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.from); return 1 })
|
||||
vm.PutPropString(obj, "getFrom")
|
||||
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.to); return 1 })
|
||||
vm.PutPropString(obj, "getTo")
|
||||
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, f.input); return 1 })
|
||||
vm.PutPropString(obj, "getInput")
|
||||
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.gas); return 1 })
|
||||
vm.PutPropString(obj, "getGas")
|
||||
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int {
|
||||
if f.value != nil {
|
||||
pushValue(ctx, f.value)
|
||||
} else {
|
||||
ctx.PushUndefined()
|
||||
}
|
||||
return 1
|
||||
})
|
||||
vm.PutPropString(obj, "getValue")
|
||||
}
|
||||
|
||||
type frameResult struct {
|
||||
gasUsed *uint
|
||||
output []byte
|
||||
errorValue *string
|
||||
}
|
||||
|
||||
func newFrameResult() *frameResult {
|
||||
return &frameResult{
|
||||
gasUsed: new(uint),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *frameResult) pushObject(vm *duktape.Context) {
|
||||
obj := vm.PushObject()
|
||||
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *r.gasUsed); return 1 })
|
||||
vm.PutPropString(obj, "getGasUsed")
|
||||
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, r.output); return 1 })
|
||||
vm.PutPropString(obj, "getOutput")
|
||||
|
||||
vm.PushGoFunction(func(ctx *duktape.Context) int {
|
||||
if r.errorValue != nil {
|
||||
pushValue(ctx, *r.errorValue)
|
||||
} else {
|
||||
ctx.PushUndefined()
|
||||
}
|
||||
return 1
|
||||
})
|
||||
vm.PutPropString(obj, "getError")
|
||||
}
|
||||
|
||||
// jsTracer provides an implementation of Tracer that evaluates a Javascript
|
||||
// function for each VM execution step.
|
||||
type jsTracer struct {
|
||||
vm *duktape.Context // Javascript VM instance
|
||||
env *vm.EVM // EVM instance executing the code being traced
|
||||
|
||||
tracerObject int // Stack index of the tracer JavaScript object
|
||||
stateObject int // Stack index of the global state to pull arguments from
|
||||
|
||||
opWrapper *opWrapper // Wrapper around the VM opcode
|
||||
stackWrapper *stackWrapper // Wrapper around the VM stack
|
||||
memoryWrapper *memoryWrapper // Wrapper around the VM memory
|
||||
contractWrapper *contractWrapper // Wrapper around the contract object
|
||||
dbWrapper *dbWrapper // Wrapper around the VM environment
|
||||
|
||||
pcValue *uint // Swappable pc value wrapped by a log accessor
|
||||
gasValue *uint // Swappable gas value wrapped by a log accessor
|
||||
costValue *uint // Swappable cost value wrapped by a log accessor
|
||||
depthValue *uint // Swappable depth value wrapped by a log accessor
|
||||
errorValue *string // Swappable error value wrapped by a log accessor
|
||||
refundValue *uint // Swappable refund value wrapped by a log accessor
|
||||
|
||||
frame *frame // Represents entry into call frame. Fields are swappable
|
||||
frameResult *frameResult // Represents exit from a call frame. Fields are swappable
|
||||
|
||||
ctx map[string]interface{} // Transaction context gathered throughout execution
|
||||
err error // Error, if one has occurred
|
||||
|
||||
interrupt uint32 // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
|
||||
activePrecompiles []common.Address // Updated on CaptureStart based on given rules
|
||||
traceSteps bool // When true, will invoke step() on each opcode
|
||||
traceCallFrames bool // When true, will invoke enter() and exit() js funcs
|
||||
gasLimit uint64 // Amount of gas bought for the whole tx
|
||||
}
|
||||
|
||||
// New instantiates a new tracer instance. code specifies a Javascript snippet,
|
||||
// which must evaluate to an expression returning an object with 'step', 'fault'
|
||||
// and 'result' functions.
|
||||
func newJsTracer(code string, ctx *tracers.Context) (tracers.Tracer, error) {
|
||||
if ctx == nil {
|
||||
ctx = new(tracers.Context)
|
||||
}
|
||||
tracer := &jsTracer{
|
||||
vm: duktape.New(),
|
||||
ctx: make(map[string]interface{}),
|
||||
opWrapper: new(opWrapper),
|
||||
stackWrapper: new(stackWrapper),
|
||||
memoryWrapper: new(memoryWrapper),
|
||||
contractWrapper: new(contractWrapper),
|
||||
dbWrapper: new(dbWrapper),
|
||||
pcValue: new(uint),
|
||||
gasValue: new(uint),
|
||||
costValue: new(uint),
|
||||
depthValue: new(uint),
|
||||
refundValue: new(uint),
|
||||
frame: newFrame(),
|
||||
frameResult: newFrameResult(),
|
||||
}
|
||||
if ctx.BlockHash != (common.Hash{}) {
|
||||
tracer.ctx["blockHash"] = ctx.BlockHash
|
||||
|
||||
if ctx.TxHash != (common.Hash{}) {
|
||||
tracer.ctx["txIndex"] = ctx.TxIndex
|
||||
tracer.ctx["txHash"] = ctx.TxHash
|
||||
}
|
||||
}
|
||||
// Set up builtins for this environment
|
||||
tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int {
|
||||
ctx.PushString(hexutil.Encode(popSlice(ctx)))
|
||||
return 1
|
||||
})
|
||||
tracer.vm.PushGlobalGoFunction("toWord", func(ctx *duktape.Context) int {
|
||||
var word common.Hash
|
||||
if ptr, size := ctx.GetBuffer(-1); ptr != nil {
|
||||
word = common.BytesToHash(makeSlice(ptr, size))
|
||||
} else {
|
||||
word = common.HexToHash(ctx.GetString(-1))
|
||||
}
|
||||
ctx.Pop()
|
||||
copy(makeSlice(ctx.PushFixedBuffer(32), 32), word[:])
|
||||
return 1
|
||||
})
|
||||
tracer.vm.PushGlobalGoFunction("toAddress", func(ctx *duktape.Context) int {
|
||||
var addr common.Address
|
||||
if ptr, size := ctx.GetBuffer(-1); ptr != nil {
|
||||
addr = common.BytesToAddress(makeSlice(ptr, size))
|
||||
} else {
|
||||
addr = common.HexToAddress(ctx.GetString(-1))
|
||||
}
|
||||
ctx.Pop()
|
||||
copy(makeSlice(ctx.PushFixedBuffer(20), 20), addr[:])
|
||||
return 1
|
||||
})
|
||||
tracer.vm.PushGlobalGoFunction("toContract", func(ctx *duktape.Context) int {
|
||||
var from common.Address
|
||||
if ptr, size := ctx.GetBuffer(-2); ptr != nil {
|
||||
from = common.BytesToAddress(makeSlice(ptr, size))
|
||||
} else {
|
||||
from = common.HexToAddress(ctx.GetString(-2))
|
||||
}
|
||||
nonce := uint64(ctx.GetInt(-1))
|
||||
ctx.Pop2()
|
||||
|
||||
contract := crypto.CreateAddress(from, nonce)
|
||||
copy(makeSlice(ctx.PushFixedBuffer(20), 20), contract[:])
|
||||
return 1
|
||||
})
|
||||
tracer.vm.PushGlobalGoFunction("toContract2", func(ctx *duktape.Context) int {
|
||||
var from common.Address
|
||||
if ptr, size := ctx.GetBuffer(-3); ptr != nil {
|
||||
from = common.BytesToAddress(makeSlice(ptr, size))
|
||||
} else {
|
||||
from = common.HexToAddress(ctx.GetString(-3))
|
||||
}
|
||||
// Retrieve salt hex string from js stack
|
||||
salt := common.HexToHash(ctx.GetString(-2))
|
||||
// Retrieve code slice from js stack
|
||||
var code []byte
|
||||
if ptr, size := ctx.GetBuffer(-1); ptr != nil {
|
||||
code = common.CopyBytes(makeSlice(ptr, size))
|
||||
} else {
|
||||
code = common.FromHex(ctx.GetString(-1))
|
||||
}
|
||||
codeHash := crypto.Keccak256(code)
|
||||
ctx.Pop3()
|
||||
contract := crypto.CreateAddress2(from, salt, codeHash)
|
||||
copy(makeSlice(ctx.PushFixedBuffer(20), 20), contract[:])
|
||||
return 1
|
||||
})
|
||||
tracer.vm.PushGlobalGoFunction("isPrecompiled", func(ctx *duktape.Context) int {
|
||||
addr := common.BytesToAddress(popSlice(ctx))
|
||||
for _, p := range tracer.activePrecompiles {
|
||||
if p == addr {
|
||||
ctx.PushBoolean(true)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
ctx.PushBoolean(false)
|
||||
return 1
|
||||
})
|
||||
tracer.vm.PushGlobalGoFunction("slice", func(ctx *duktape.Context) int {
|
||||
start, end := ctx.GetInt(-2), ctx.GetInt(-1)
|
||||
ctx.Pop2()
|
||||
|
||||
blob := popSlice(ctx)
|
||||
size := end - start
|
||||
|
||||
if start < 0 || start > end || end > len(blob) {
|
||||
// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
|
||||
// runtime goes belly up https://github.com/golang/go/issues/15639.
|
||||
log.Warn("Tracer accessed out of bound memory", "available", len(blob), "offset", start, "size", size)
|
||||
ctx.PushFixedBuffer(0)
|
||||
return 1
|
||||
}
|
||||
copy(makeSlice(ctx.PushFixedBuffer(size), uint(size)), blob[start:end])
|
||||
return 1
|
||||
})
|
||||
// Push the JavaScript tracer as object #0 onto the JSVM stack and validate it
|
||||
if err := tracer.vm.PevalString("(" + code + ")"); err != nil {
|
||||
log.Warn("Failed to compile tracer", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
tracer.tracerObject = 0 // yeah, nice, eval can't return the index itself
|
||||
|
||||
hasStep := tracer.vm.GetPropString(tracer.tracerObject, "step")
|
||||
tracer.vm.Pop()
|
||||
|
||||
if !tracer.vm.GetPropString(tracer.tracerObject, "fault") {
|
||||
return nil, fmt.Errorf("trace object must expose a function fault()")
|
||||
}
|
||||
tracer.vm.Pop()
|
||||
|
||||
if !tracer.vm.GetPropString(tracer.tracerObject, "result") {
|
||||
return nil, fmt.Errorf("trace object must expose a function result()")
|
||||
}
|
||||
tracer.vm.Pop()
|
||||
|
||||
hasEnter := tracer.vm.GetPropString(tracer.tracerObject, "enter")
|
||||
tracer.vm.Pop()
|
||||
hasExit := tracer.vm.GetPropString(tracer.tracerObject, "exit")
|
||||
tracer.vm.Pop()
|
||||
if hasEnter != hasExit {
|
||||
return nil, fmt.Errorf("trace object must expose either both or none of enter() and exit()")
|
||||
}
|
||||
tracer.traceCallFrames = hasEnter && hasExit
|
||||
tracer.traceSteps = hasStep
|
||||
|
||||
// Tracer is valid, inject the big int library to access large numbers
|
||||
tracer.vm.EvalString(bigIntegerJS)
|
||||
tracer.vm.PutGlobalString("bigInt")
|
||||
|
||||
// Push the global environment state as object #1 into the JSVM stack
|
||||
tracer.stateObject = tracer.vm.PushObject()
|
||||
|
||||
logObject := tracer.vm.PushObject()
|
||||
|
||||
tracer.opWrapper.pushObject(tracer.vm)
|
||||
tracer.vm.PutPropString(logObject, "op")
|
||||
|
||||
tracer.stackWrapper.pushObject(tracer.vm)
|
||||
tracer.vm.PutPropString(logObject, "stack")
|
||||
|
||||
tracer.memoryWrapper.pushObject(tracer.vm)
|
||||
tracer.vm.PutPropString(logObject, "memory")
|
||||
|
||||
tracer.contractWrapper.pushObject(tracer.vm)
|
||||
tracer.vm.PutPropString(logObject, "contract")
|
||||
|
||||
tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.pcValue); return 1 })
|
||||
tracer.vm.PutPropString(logObject, "getPC")
|
||||
|
||||
tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.gasValue); return 1 })
|
||||
tracer.vm.PutPropString(logObject, "getGas")
|
||||
|
||||
tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.costValue); return 1 })
|
||||
tracer.vm.PutPropString(logObject, "getCost")
|
||||
|
||||
tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.depthValue); return 1 })
|
||||
tracer.vm.PutPropString(logObject, "getDepth")
|
||||
|
||||
tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.refundValue); return 1 })
|
||||
tracer.vm.PutPropString(logObject, "getRefund")
|
||||
|
||||
tracer.vm.PushGoFunction(func(ctx *duktape.Context) int {
|
||||
if tracer.errorValue != nil {
|
||||
ctx.PushString(*tracer.errorValue)
|
||||
} else {
|
||||
ctx.PushUndefined()
|
||||
}
|
||||
return 1
|
||||
})
|
||||
tracer.vm.PutPropString(logObject, "getError")
|
||||
|
||||
tracer.vm.PutPropString(tracer.stateObject, "log")
|
||||
|
||||
tracer.frame.pushObject(tracer.vm)
|
||||
tracer.vm.PutPropString(tracer.stateObject, "frame")
|
||||
|
||||
tracer.frameResult.pushObject(tracer.vm)
|
||||
tracer.vm.PutPropString(tracer.stateObject, "frameResult")
|
||||
|
||||
tracer.dbWrapper.pushObject(tracer.vm)
|
||||
tracer.vm.PutPropString(tracer.stateObject, "db")
|
||||
|
||||
return tracer, nil
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
func (jst *jsTracer) Stop(err error) {
|
||||
jst.reason = err
|
||||
atomic.StoreUint32(&jst.interrupt, 1)
|
||||
}
|
||||
|
||||
// call executes a method on a JS object, catching any errors, formatting and
|
||||
// returning them as error objects.
|
||||
func (jst *jsTracer) call(noret bool, method string, args ...string) (json.RawMessage, error) {
|
||||
// Execute the JavaScript call and return any error
|
||||
jst.vm.PushString(method)
|
||||
for _, arg := range args {
|
||||
jst.vm.GetPropString(jst.stateObject, arg)
|
||||
}
|
||||
code := jst.vm.PcallProp(jst.tracerObject, len(args))
|
||||
defer jst.vm.Pop()
|
||||
|
||||
if code != 0 {
|
||||
err := jst.vm.SafeToString(-1)
|
||||
return nil, errors.New(err)
|
||||
}
|
||||
// No error occurred, extract return value and return
|
||||
if noret {
|
||||
return nil, nil
|
||||
}
|
||||
// Push a JSON marshaller onto the stack. We can't marshal from the out-
|
||||
// side because duktape can crash on large nestings and we can't catch
|
||||
// C++ exceptions ourselves from Go. TODO(karalabe): Yuck, why wrap?!
|
||||
jst.vm.PushString("(JSON.stringify)")
|
||||
jst.vm.Eval()
|
||||
|
||||
jst.vm.Swap(-1, -2)
|
||||
if code = jst.vm.Pcall(1); code != 0 {
|
||||
err := jst.vm.SafeToString(-1)
|
||||
return nil, errors.New(err)
|
||||
}
|
||||
return json.RawMessage(jst.vm.SafeToString(-1)), nil
|
||||
}
|
||||
|
||||
func wrapError(context string, err error) error {
|
||||
return fmt.Errorf("%v in server-side tracer function '%v'", err, context)
|
||||
}
|
||||
|
||||
// CaptureTxStart implements the Tracer interface and is invoked at the beginning of
|
||||
// transaction processing.
|
||||
func (jst *jsTracer) CaptureTxStart(gasLimit uint64) {
|
||||
jst.gasLimit = gasLimit
|
||||
}
|
||||
|
||||
// CaptureTxEnd implements the Tracer interface and is invoked at the end of
|
||||
// transaction processing.
|
||||
func (*jsTracer) CaptureTxEnd(restGas uint64) {}
|
||||
|
||||
// CaptureStart implements the Tracer interface and is invoked before executing the
|
||||
// top-level call frame of a transaction.
|
||||
func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
jst.env = env
|
||||
jst.ctx["type"] = "CALL"
|
||||
if create {
|
||||
jst.ctx["type"] = "CREATE"
|
||||
}
|
||||
jst.ctx["from"] = from
|
||||
jst.ctx["to"] = to
|
||||
jst.ctx["input"] = input
|
||||
jst.ctx["gas"] = gas
|
||||
jst.ctx["gasPrice"] = env.TxContext.GasPrice
|
||||
jst.ctx["value"] = value
|
||||
|
||||
// Initialize the context
|
||||
jst.ctx["block"] = env.Context.BlockNumber.Uint64()
|
||||
jst.dbWrapper.db = env.StateDB
|
||||
// Update list of precompiles based on current block
|
||||
rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil)
|
||||
jst.activePrecompiles = vm.ActivePrecompiles(rules)
|
||||
|
||||
// Intrinsic costs are the only things reduced from initial gas to this point
|
||||
jst.ctx["intrinsicGas"] = jst.gasLimit - gas
|
||||
}
|
||||
|
||||
// CaptureState implements the Tracer interface to trace a single step of VM execution.
|
||||
func (jst *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
if !jst.traceSteps {
|
||||
return
|
||||
}
|
||||
if jst.err != nil {
|
||||
return
|
||||
}
|
||||
// If tracing was interrupted, set the error and stop
|
||||
if atomic.LoadUint32(&jst.interrupt) > 0 {
|
||||
jst.err = jst.reason
|
||||
jst.env.Cancel()
|
||||
return
|
||||
}
|
||||
jst.opWrapper.op = op
|
||||
jst.stackWrapper.stack = scope.Stack
|
||||
jst.memoryWrapper.memory = scope.Memory
|
||||
jst.contractWrapper.contract = scope.Contract
|
||||
|
||||
*jst.pcValue = uint(pc)
|
||||
*jst.gasValue = uint(gas)
|
||||
*jst.costValue = uint(cost)
|
||||
*jst.depthValue = uint(depth)
|
||||
*jst.refundValue = uint(jst.env.StateDB.GetRefund())
|
||||
|
||||
jst.errorValue = nil
|
||||
if err != nil {
|
||||
jst.errorValue = new(string)
|
||||
*jst.errorValue = err.Error()
|
||||
}
|
||||
|
||||
if _, err := jst.call(true, "step", "log", "db"); err != nil {
|
||||
jst.err = wrapError("step", err)
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureFault implements the Tracer interface to trace an execution fault
|
||||
func (jst *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
if jst.err != nil {
|
||||
return
|
||||
}
|
||||
// Apart from the error, everything matches the previous invocation
|
||||
jst.errorValue = new(string)
|
||||
*jst.errorValue = err.Error()
|
||||
|
||||
if _, err := jst.call(true, "fault", "log", "db"); err != nil {
|
||||
jst.err = wrapError("fault", err)
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureEnd is called after the top-level call finishes.
|
||||
func (jst *jsTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
|
||||
jst.ctx["output"] = output
|
||||
jst.ctx["time"] = t.String()
|
||||
jst.ctx["gasUsed"] = gasUsed
|
||||
|
||||
if err != nil {
|
||||
jst.ctx["error"] = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
|
||||
func (jst *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
if !jst.traceCallFrames {
|
||||
return
|
||||
}
|
||||
if jst.err != nil {
|
||||
return
|
||||
}
|
||||
// If tracing was interrupted, set the error and stop
|
||||
if atomic.LoadUint32(&jst.interrupt) > 0 {
|
||||
jst.err = jst.reason
|
||||
return
|
||||
}
|
||||
|
||||
*jst.frame.typ = typ.String()
|
||||
*jst.frame.from = from
|
||||
*jst.frame.to = to
|
||||
jst.frame.input = common.CopyBytes(input)
|
||||
*jst.frame.gas = uint(gas)
|
||||
jst.frame.value = nil
|
||||
if value != nil {
|
||||
jst.frame.value = new(big.Int).SetBytes(value.Bytes())
|
||||
}
|
||||
|
||||
if _, err := jst.call(true, "enter", "frame"); err != nil {
|
||||
jst.err = wrapError("enter", err)
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureExit is called when EVM exits a scope, even if the scope didn't
|
||||
// execute any code.
|
||||
func (jst *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
if !jst.traceCallFrames {
|
||||
return
|
||||
}
|
||||
// If tracing was interrupted, set the error and stop
|
||||
if atomic.LoadUint32(&jst.interrupt) > 0 {
|
||||
jst.err = jst.reason
|
||||
return
|
||||
}
|
||||
|
||||
jst.frameResult.output = common.CopyBytes(output)
|
||||
*jst.frameResult.gasUsed = uint(gasUsed)
|
||||
jst.frameResult.errorValue = nil
|
||||
if err != nil {
|
||||
jst.frameResult.errorValue = new(string)
|
||||
*jst.frameResult.errorValue = err.Error()
|
||||
}
|
||||
|
||||
if _, err := jst.call(true, "exit", "frameResult"); err != nil {
|
||||
jst.err = wrapError("exit", err)
|
||||
}
|
||||
}
|
||||
|
||||
// GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
|
||||
func (jst *jsTracer) GetResult() (json.RawMessage, error) {
|
||||
// Transform the context into a JavaScript object and inject into the state
|
||||
obj := jst.vm.PushObject()
|
||||
|
||||
for key, val := range jst.ctx {
|
||||
jst.addToObj(obj, key, val)
|
||||
}
|
||||
jst.vm.PutPropString(jst.stateObject, "ctx")
|
||||
|
||||
// Finalize the trace and return the results
|
||||
result, err := jst.call(false, "result", "ctx", "db")
|
||||
if err != nil {
|
||||
jst.err = wrapError("result", err)
|
||||
}
|
||||
// Clean up the JavaScript environment
|
||||
jst.vm.DestroyHeap()
|
||||
jst.vm.Destroy()
|
||||
|
||||
return result, jst.err
|
||||
}
|
||||
|
||||
// addToObj pushes a field to a JS object.
|
||||
func (jst *jsTracer) addToObj(obj int, key string, val interface{}) {
|
||||
pushValue(jst.vm, val)
|
||||
jst.vm.PutPropString(obj, key)
|
||||
}
|
||||
|
||||
func pushValue(ctx *duktape.Context, val interface{}) {
|
||||
switch val := val.(type) {
|
||||
case uint64:
|
||||
ctx.PushUint(uint(val))
|
||||
case string:
|
||||
ctx.PushString(val)
|
||||
case []byte:
|
||||
ptr := ctx.PushFixedBuffer(len(val))
|
||||
copy(makeSlice(ptr, uint(len(val))), val)
|
||||
case common.Address:
|
||||
ptr := ctx.PushFixedBuffer(20)
|
||||
copy(makeSlice(ptr, 20), val[:])
|
||||
case *big.Int:
|
||||
pushBigInt(val, ctx)
|
||||
case int:
|
||||
ctx.PushInt(val)
|
||||
case uint:
|
||||
ctx.PushUint(val)
|
||||
case common.Hash:
|
||||
ptr := ctx.PushFixedBuffer(32)
|
||||
copy(makeSlice(ptr, 32), val[:])
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported type: %T", val))
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
@@ -82,10 +82,20 @@ func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCon
|
||||
return tracer.GetResult()
|
||||
}
|
||||
|
||||
func TestTracer(t *testing.T) {
|
||||
type tracerCtor = func(string, *tracers.Context) (tracers.Tracer, error)
|
||||
|
||||
func TestDuktapeTracer(t *testing.T) {
|
||||
testTracer(t, newJsTracer)
|
||||
}
|
||||
|
||||
func TestGojaTracer(t *testing.T) {
|
||||
testTracer(t, newGojaTracer)
|
||||
}
|
||||
|
||||
func testTracer(t *testing.T, newTracer tracerCtor) {
|
||||
execTracer := func(code string) ([]byte, string) {
|
||||
t.Helper()
|
||||
tracer, err := newJsTracer(code, nil)
|
||||
tracer, err := newTracer(code, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -102,16 +112,13 @@ func TestTracer(t *testing.T) {
|
||||
}{
|
||||
{ // tests that we don't panic on bad arguments to memory access
|
||||
code: "{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}",
|
||||
want: ``,
|
||||
fail: "Tracer accessed out of bound memory: offset -1, end -2 at step (<eval>:1:53(15)) in server-side tracer function 'step'",
|
||||
want: `[{},{},{}]`,
|
||||
}, { // tests that we don't panic on bad arguments to stack peeks
|
||||
code: "{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}",
|
||||
want: ``,
|
||||
fail: "Tracer accessed out of bound stack: size 0, index -1 at step (<eval>:1:53(13)) in server-side tracer function 'step'",
|
||||
want: `["0","0","0"]`,
|
||||
}, { // tests that we don't panic on bad arguments to memory getUint
|
||||
code: "{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}",
|
||||
want: ``,
|
||||
fail: "Tracer accessed out of bound memory: available 0, offset -64, size 32 at step (<eval>:1:58(13)) in server-side tracer function 'step'",
|
||||
want: `["0","0","0"]`,
|
||||
}, { // tests some general counting
|
||||
code: "{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}",
|
||||
want: `3`,
|
||||
@@ -147,9 +154,18 @@ func TestTracer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHalt(t *testing.T) {
|
||||
func TestHaltDuktape(t *testing.T) {
|
||||
t.Skip("duktape doesn't support abortion")
|
||||
testHalt(t, newJsTracer)
|
||||
}
|
||||
|
||||
func TestHaltGoja(t *testing.T) {
|
||||
testHalt(t, newGojaTracer)
|
||||
}
|
||||
|
||||
func testHalt(t *testing.T, newTracer tracerCtor) {
|
||||
timeout := errors.New("stahp")
|
||||
tracer, err := newJsTracer("{step: function() { while(1); }, result: function() { return null; }, fault: function(){}}", nil)
|
||||
tracer, err := newTracer("{step: function() { while(1); }, result: function() { return null; }, fault: function(){}}", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -162,8 +178,16 @@ func TestHalt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHaltBetweenSteps(t *testing.T) {
|
||||
tracer, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }}", nil)
|
||||
func TestHaltBetweenStepsDuktape(t *testing.T) {
|
||||
testHaltBetweenSteps(t, newJsTracer)
|
||||
}
|
||||
|
||||
func TestHaltBetweenStepsGoja(t *testing.T) {
|
||||
testHaltBetweenSteps(t, newGojaTracer)
|
||||
}
|
||||
|
||||
func testHaltBetweenSteps(t *testing.T, newTracer tracerCtor) {
|
||||
tracer, err := newTracer("{step: function() {}, fault: function() {}, result: function() { return null; }}", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -182,12 +206,20 @@ func TestHaltBetweenSteps(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoStepExecDuktape(t *testing.T) {
|
||||
testNoStepExec(t, newJsTracer)
|
||||
}
|
||||
|
||||
func TestNoStepExecGoja(t *testing.T) {
|
||||
testNoStepExec(t, newGojaTracer)
|
||||
}
|
||||
|
||||
// testNoStepExec tests a regular value transfer (no exec), and accessing the statedb
|
||||
// in 'result'
|
||||
func TestNoStepExec(t *testing.T) {
|
||||
func testNoStepExec(t *testing.T, newTracer tracerCtor) {
|
||||
execTracer := func(code string) []byte {
|
||||
t.Helper()
|
||||
tracer, err := newJsTracer(code, nil)
|
||||
tracer, err := newTracer(code, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -215,13 +247,21 @@ func TestNoStepExec(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsPrecompile(t *testing.T) {
|
||||
func TestIsPrecompileDuktape(t *testing.T) {
|
||||
testIsPrecompile(t, newJsTracer)
|
||||
}
|
||||
|
||||
func TestIsPrecompileGoja(t *testing.T) {
|
||||
testIsPrecompile(t, newGojaTracer)
|
||||
}
|
||||
|
||||
func testIsPrecompile(t *testing.T, newTracer tracerCtor) {
|
||||
chaincfg := ¶ms.ChainConfig{ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, DAOForkSupport: false, EIP150Block: big.NewInt(0), EIP150Hash: common.Hash{}, EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), ByzantiumBlock: big.NewInt(100), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(200), MuirGlacierBlock: big.NewInt(0), BerlinBlock: big.NewInt(300), LondonBlock: big.NewInt(0), TerminalTotalDifficulty: nil, Ethash: new(params.EthashConfig), Clique: nil}
|
||||
chaincfg.ByzantiumBlock = big.NewInt(100)
|
||||
chaincfg.IstanbulBlock = big.NewInt(200)
|
||||
chaincfg.BerlinBlock = big.NewInt(300)
|
||||
txCtx := vm.TxContext{GasPrice: big.NewInt(100000)}
|
||||
tracer, err := newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
|
||||
tracer, err := newTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -235,7 +275,7 @@ func TestIsPrecompile(t *testing.T) {
|
||||
t.Errorf("Tracer should not consider blake2f as precompile in byzantium")
|
||||
}
|
||||
|
||||
tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
|
||||
tracer, _ = newTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
|
||||
blockCtx = vm.BlockContext{BlockNumber: big.NewInt(250)}
|
||||
res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg)
|
||||
if err != nil {
|
||||
@@ -246,16 +286,24 @@ func TestIsPrecompile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnterExit(t *testing.T) {
|
||||
func TestEnterExitDuktape(t *testing.T) {
|
||||
testEnterExit(t, newJsTracer)
|
||||
}
|
||||
|
||||
func TestEnterExitGoja(t *testing.T) {
|
||||
testEnterExit(t, newGojaTracer)
|
||||
}
|
||||
|
||||
func testEnterExit(t *testing.T, newTracer tracerCtor) {
|
||||
// test that either both or none of enter() and exit() are defined
|
||||
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(tracers.Context)); err == nil {
|
||||
if _, err := newTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(tracers.Context)); err == nil {
|
||||
t.Fatal("tracer creation should've failed without exit() definition")
|
||||
}
|
||||
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(tracers.Context)); err != nil {
|
||||
if _, err := newTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(tracers.Context)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// test that the enter and exit method are correctly invoked and the values passed
|
||||
tracer, err := newJsTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(tracers.Context))
|
||||
tracer, err := newTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(tracers.Context))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -274,3 +322,20 @@ func TestEnterExit(t *testing.T) {
|
||||
t.Errorf("Number of invocations of enter() and exit() is wrong. Have %s, want %s\n", have, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests too deep object / serialization crash for duktape
|
||||
func TestRecursionLimit(t *testing.T) {
|
||||
code := "{step: function() {}, fault: function() {}, result: function() { var o={}; var x=o; for (var i=0; i<1000; i++){ o.foo={}; o=o.foo; } return x; }}"
|
||||
fail := "RangeError: json encode recursion limit in server-side tracer function 'result'"
|
||||
tracer, err := newJsTracer(code, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := ""
|
||||
if _, err := runTrace(tracer, testCtx(), params.TestChainConfig); err != nil {
|
||||
got = err.Error()
|
||||
}
|
||||
if got != fail {
|
||||
t.Errorf("expected error to be '%s' got '%s'\n", fail, got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user