diff --git a/.travis.yml b/.travis.yml index 91f69040..2995ce13 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ dist: trusty language: go go: - - 1.9 + - 1.11 services: - postgresql addons: diff --git a/Gopkg.lock b/Gopkg.lock index edf16139..bad46b33 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -117,6 +117,17 @@ revision = "d460ce9f8df2e77fb1ba55ca87fafed96c607494" version = "v1.0.0" +[[projects]] + branch = "master" + digest = "1:9c776d7d9c54b7ed89f119e449983c3f24c0023e75001d6092442412ebca6b94" + name = "github.com/hashicorp/golang-lru" + packages = [ + ".", + "simplelru", + ] + pruneopts = "" + revision = "0fb14efe8c47ae851c0034ed7a448854d3d34cf3" + [[projects]] digest = "1:d14365c51dd1d34d5c79833ec91413bfbb166be978724f15701e17080dc06dec" name = "github.com/hashicorp/hcl" @@ -552,6 +563,7 @@ "github.com/ethereum/go-ethereum/p2p/discv5", "github.com/ethereum/go-ethereum/params", "github.com/ethereum/go-ethereum/rpc", + "github.com/hashicorp/golang-lru", "github.com/jmoiron/sqlx", "github.com/lib/pq", "github.com/mitchellh/go-homedir", diff --git a/LICENSE b/LICENSE index 261eeb9e..331f7cfa 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,661 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. - 1. Definitions. + Preamble - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. + The GNU Affero General Public License is a free, copyleft license for + software and other kinds of works, specifically designed to ensure + cooperation with the community in the case of network server software. - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. + The licenses for most software and other practical works are designed + to take away your freedom to share and change the works. By contrast, + our General Public Licenses are intended to guarantee your freedom to + share and change all versions of a program--to make sure it remains free + software for all its users. - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. + When we speak of free software, we are referring to freedom, not + price. Our General Public Licenses are designed to make sure that you + have the freedom to distribute copies of free software (and charge for + them if you wish), that you receive source code or can get it if you + want it, that you can change the software or use pieces of it in new + free programs, and that you know you can do these things. - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. + Developers that use our General Public Licenses protect your rights + with two steps: (1) assert copyright on the software, and (2) offer + you this License which gives you legal permission to copy, distribute + and/or modify the software. - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. + A secondary benefit of defending all users' freedom is that + improvements made in alternate versions of the program, if they + receive widespread use, become available for other developers to + incorporate. Many developers of free software are heartened and + encouraged by the resulting cooperation. However, in the case of + software used on network servers, this result may fail to come about. + The GNU General Public License permits making a modified version and + letting the public access it on a server without ever releasing its + source code to the public. - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. + The GNU Affero General Public License is designed specifically to + ensure that, in such cases, the modified source code becomes available + to the community. It requires the operator of a network server to + provide the source code of the modified version running there to the + users of that server. Therefore, public use of a modified version, on + a publicly accessible server, gives the public access to the source + code of the modified version. - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). + An older license, called the Affero General Public License and + published by Affero, was designed to accomplish similar goals. This is + a different license, not a version of the Affero GPL, but Affero has + released a new version of the Affero GPL which permits relicensing under + this license. - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. + The precise terms and conditions for copying, distribution and + modification follow. - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." + TERMS AND CONDITIONS - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. + 0. Definitions. - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. + "This License" refers to version 3 of the GNU Affero General Public License. - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. + "Copyright" also means copyright-like laws that apply to other kinds of + works, such as semiconductor masks. - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: + "The Program" refers to any copyrightable work licensed under this + License. Each licensee is addressed as "you". "Licensees" and + "recipients" may be individuals or organizations. - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and + To "modify" a work means to copy from or adapt all or part of the work + in a fashion requiring copyright permission, other than the making of an + exact copy. The resulting work is called a "modified version" of the + earlier work or a work "based on" the earlier work. - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and + A "covered work" means either the unmodified Program or a work based + on the Program. - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and + To "propagate" a work means to do anything with it that, without + permission, would make you directly or secondarily liable for + infringement under applicable copyright law, except executing it on a + computer or modifying a private copy. Propagation includes copying, + distribution (with or without modification), making available to the + public, and in some countries other activities as well. - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. + To "convey" a work means any kind of propagation that enables other + parties to make or receive copies. Mere interaction with a user through + a computer network, with no transfer of a copy, is not conveying. - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. + An interactive user interface displays "Appropriate Legal Notices" + to the extent that it includes a convenient and prominently visible + feature that (1) displays an appropriate copyright notice, and (2) + tells the user that there is no warranty for the work (except to the + extent that warranties are provided), that licensees may convey the + work under this License, and how to view a copy of this License. If + the interface presents a list of user commands or options, such as a + menu, a prominent item in the list meets this criterion. - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. + 1. Source Code. - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. + The "source code" for a work means the preferred form of the work + for making modifications to it. "Object code" means any non-source + form of a work. - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. + A "Standard Interface" means an interface that either is an official + standard defined by a recognized standards body, or, in the case of + interfaces specified for a particular programming language, one that + is widely used among developers working in that language. - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. + The "System Libraries" of an executable work include anything, other + than the work as a whole, that (a) is included in the normal form of + packaging a Major Component, but which is not part of that Major + Component, and (b) serves only to enable use of the work with that + Major Component, or to implement a Standard Interface for which an + implementation is available to the public in source code form. A + "Major Component", in this context, means a major essential component + (kernel, window system, and so on) of the specific operating system + (if any) on which the executable work runs, or a compiler used to + produce the work, or an object code interpreter used to run it. - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. + The "Corresponding Source" for a work in object code form means all + the source code needed to generate, install, and (for an executable + work) run the object code and to modify the work, including scripts to + control those activities. However, it does not include the work's + System Libraries, or general-purpose tools or generally available free + programs which are used unmodified in performing those activities but + which are not part of the work. For example, Corresponding Source + includes interface definition files associated with source files for + the work, and the source code for shared libraries and dynamically + linked subprograms that the work is specifically designed to require, + such as by intimate data communication or control flow between those + subprograms and other parts of the work. - END OF TERMS AND CONDITIONS + The Corresponding Source need not include anything that users + can regenerate automatically from other parts of the Corresponding + Source. - APPENDIX: How to apply the Apache License to your work. + The Corresponding Source for a work in source code form is that + same work. - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. + 2. Basic Permissions. - Copyright [yyyy] [name of copyright owner] + All rights granted under this License are granted for the term of + copyright on the Program, and are irrevocable provided the stated + conditions are met. This License explicitly affirms your unlimited + permission to run the unmodified Program. The output from running a + covered work is covered by this License only if the output, given its + content, constitutes a covered work. This License acknowledges your + rights of fair use or other equivalent, as provided by copyright law. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at + You may make, run and propagate covered works that you do not + convey, without conditions so long as your license otherwise remains + in force. You may convey covered works to others for the sole purpose + of having them make modifications exclusively for you, or provide you + with facilities for running those works, provided that you comply with + the terms of this License in conveying all material for which you do + not control copyright. Those thus making or running the covered works + for you must do so exclusively on your behalf, under your direction + and control, on terms that prohibit them from making any copies of + your copyrighted material outside their relationship with you. - http://www.apache.org/licenses/LICENSE-2.0 + Conveying under any other circumstances is permitted solely under + the conditions stated below. Sublicensing is not allowed; section 10 + makes it unnecessary. - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological + measure under any applicable law fulfilling obligations under article + 11 of the WIPO copyright treaty adopted on 20 December 1996, or + similar laws prohibiting or restricting circumvention of such + measures. + + When you convey a covered work, you waive any legal power to forbid + circumvention of technological measures to the extent such circumvention + is effected by exercising rights under this License with respect to + the covered work, and you disclaim any intention to limit operation or + modification of the work as a means of enforcing, against the work's + users, your or third parties' legal rights to forbid circumvention of + technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you + receive it, in any medium, provided that you conspicuously and + appropriately publish on each copy an appropriate copyright notice; + keep intact all notices stating that this License and any + non-permissive terms added in accord with section 7 apply to the code; + keep intact all notices of the absence of any warranty; and give all + recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, + and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to + produce it from the Program, in the form of source code under the + terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent + works, which are not by their nature extensions of the covered work, + and which are not combined with it such as to form a larger program, + in or on a volume of a storage or distribution medium, is called an + "aggregate" if the compilation and its resulting copyright are not + used to limit the access or legal rights of the compilation's users + beyond what the individual works permit. Inclusion of a covered work + in an aggregate does not cause this License to apply to the other + parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms + of sections 4 and 5, provided that you also convey the + machine-readable Corresponding Source under the terms of this License, + in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded + from the Corresponding Source as a System Library, need not be + included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any + tangible personal property which is normally used for personal, family, + or household purposes, or (2) anything designed or sold for incorporation + into a dwelling. In determining whether a product is a consumer product, + doubtful cases shall be resolved in favor of coverage. For a particular + product received by a particular user, "normally used" refers to a + typical or common use of that class of product, regardless of the status + of the particular user or of the way in which the particular user + actually uses, or expects or is expected to use, the product. A product + is a consumer product regardless of whether the product has substantial + commercial, industrial or non-consumer uses, unless such uses represent + the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, + procedures, authorization keys, or other information required to install + and execute modified versions of a covered work in that User Product from + a modified version of its Corresponding Source. The information must + suffice to ensure that the continued functioning of the modified object + code is in no case prevented or interfered with solely because + modification has been made. + + If you convey an object code work under this section in, or with, or + specifically for use in, a User Product, and the conveying occurs as + part of a transaction in which the right of possession and use of the + User Product is transferred to the recipient in perpetuity or for a + fixed term (regardless of how the transaction is characterized), the + Corresponding Source conveyed under this section must be accompanied + by the Installation Information. But this requirement does not apply + if neither you nor any third party retains the ability to install + modified object code on the User Product (for example, the work has + been installed in ROM). + + The requirement to provide Installation Information does not include a + requirement to continue to provide support service, warranty, or updates + for a work that has been modified or installed by the recipient, or for + the User Product in which it has been modified or installed. Access to a + network may be denied when the modification itself materially and + adversely affects the operation of the network or violates the rules and + protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, + in accord with this section must be in a format that is publicly + documented (and with an implementation available to the public in + source code form), and must require no special password or key for + unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this + License by making exceptions from one or more of its conditions. + Additional permissions that are applicable to the entire Program shall + be treated as though they were included in this License, to the extent + that they are valid under applicable law. If additional permissions + apply only to part of the Program, that part may be used separately + under those permissions, but the entire Program remains governed by + this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option + remove any additional permissions from that copy, or from any part of + it. (Additional permissions may be written to require their own + removal in certain cases when you modify the work.) You may place + additional permissions on material, added by you to a covered work, + for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you + add to a covered work, you may (if authorized by the copyright holders of + that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further + restrictions" within the meaning of section 10. If the Program as you + received it, or any part of it, contains a notice stating that it is + governed by this License along with a term that is a further + restriction, you may remove that term. If a license document contains + a further restriction but permits relicensing or conveying under this + License, you may add to a covered work material governed by the terms + of that license document, provided that the further restriction does + not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you + must place, in the relevant source files, a statement of the + additional terms that apply to those files, or a notice indicating + where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the + form of a separately written license, or stated as exceptions; + the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly + provided under this License. Any attempt otherwise to propagate or + modify it is void, and will automatically terminate your rights under + this License (including any patent licenses granted under the third + paragraph of section 11). + + However, if you cease all violation of this License, then your + license from a particular copyright holder is reinstated (a) + provisionally, unless and until the copyright holder explicitly and + finally terminates your license, and (b) permanently, if the copyright + holder fails to notify you of the violation by some reasonable means + prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is + reinstated permanently if the copyright holder notifies you of the + violation by some reasonable means, this is the first time you have + received notice of violation of this License (for any work) from that + copyright holder, and you cure the violation prior to 30 days after + your receipt of the notice. + + Termination of your rights under this section does not terminate the + licenses of parties who have received copies or rights from you under + this License. If your rights have been terminated and not permanently + reinstated, you do not qualify to receive new licenses for the same + material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or + run a copy of the Program. Ancillary propagation of a covered work + occurring solely as a consequence of using peer-to-peer transmission + to receive a copy likewise does not require acceptance. However, + nothing other than this License grants you permission to propagate or + modify any covered work. These actions infringe copyright if you do + not accept this License. Therefore, by modifying or propagating a + covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically + receives a license from the original licensors, to run, modify and + propagate that work, subject to this License. You are not responsible + for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an + organization, or substantially all assets of one, or subdividing an + organization, or merging organizations. If propagation of a covered + work results from an entity transaction, each party to that + transaction who receives a copy of the work also receives whatever + licenses to the work the party's predecessor in interest had or could + give under the previous paragraph, plus a right to possession of the + Corresponding Source of the work from the predecessor in interest, if + the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the + rights granted or affirmed under this License. For example, you may + not impose a license fee, royalty, or other charge for exercise of + rights granted under this License, and you may not initiate litigation + (including a cross-claim or counterclaim in a lawsuit) alleging that + any patent claim is infringed by making, using, selling, offering for + sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this + License of the Program or a work on which the Program is based. The + work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims + owned or controlled by the contributor, whether already acquired or + hereafter acquired, that would be infringed by some manner, permitted + by this License, of making, using, or selling its contributor version, + but do not include claims that would be infringed only as a + consequence of further modification of the contributor version. For + purposes of this definition, "control" includes the right to grant + patent sublicenses in a manner consistent with the requirements of + this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free + patent license under the contributor's essential patent claims, to + make, use, sell, offer for sale, import and otherwise run, modify and + propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express + agreement or commitment, however denominated, not to enforce a patent + (such as an express permission to practice a patent or covenant not to + sue for patent infringement). To "grant" such a patent license to a + party means to make such an agreement or commitment not to enforce a + patent against the party. + + If you convey a covered work, knowingly relying on a patent license, + and the Corresponding Source of the work is not available for anyone + to copy, free of charge and under the terms of this License, through a + publicly available network server or other readily accessible means, + then you must either (1) cause the Corresponding Source to be so + available, or (2) arrange to deprive yourself of the benefit of the + patent license for this particular work, or (3) arrange, in a manner + consistent with the requirements of this License, to extend the patent + license to downstream recipients. "Knowingly relying" means you have + actual knowledge that, but for the patent license, your conveying the + covered work in a country, or your recipient's use of the covered work + in a country, would infringe one or more identifiable patents in that + country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or + arrangement, you convey, or propagate by procuring conveyance of, a + covered work, and grant a patent license to some of the parties + receiving the covered work authorizing them to use, propagate, modify + or convey a specific copy of the covered work, then the patent license + you grant is automatically extended to all recipients of the covered + work and works based on it. + + A patent license is "discriminatory" if it does not include within + the scope of its coverage, prohibits the exercise of, or is + conditioned on the non-exercise of one or more of the rights that are + specifically granted under this License. You may not convey a covered + work if you are a party to an arrangement with a third party that is + in the business of distributing software, under which you make payment + to the third party based on the extent of your activity of conveying + the work, and under which the third party grants, to any of the + parties who would receive the covered work from you, a discriminatory + patent license (a) in connection with copies of the covered work + conveyed by you (or copies made from those copies), or (b) primarily + for and in connection with specific products or compilations that + contain the covered work, unless you entered into that arrangement, + or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting + any implied license or other defenses to infringement that may + otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot convey a + covered work so as to satisfy simultaneously your obligations under this + License and any other pertinent obligations, then as a consequence you may + not convey it at all. For example, if you agree to terms that obligate you + to collect a royalty for further conveying from those to whom you convey + the Program, the only way you could satisfy both those terms and this + License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the + Program, your modified version must prominently offer all users + interacting with it remotely through a computer network (if your version + supports such interaction) an opportunity to receive the Corresponding + Source of your version by providing access to the Corresponding Source + from a network server at no charge, through some standard or customary + means of facilitating copying of software. This Corresponding Source + shall include the Corresponding Source for any work covered by version 3 + of the GNU General Public License that is incorporated pursuant to the + following paragraph. + + Notwithstanding any other provision of this License, you have + permission to link or combine any covered work with a work licensed + under version 3 of the GNU General Public License into a single + combined work, and to convey the resulting work. The terms of this + License will continue to apply to the part which is the covered work, + but the work with which it is combined will remain governed by version + 3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of + the GNU Affero General Public License from time to time. Such new versions + will be similar in spirit to the present version, but may differ in detail to + address new problems or concerns. + + Each version is given a distinguishing version number. If the + Program specifies that a certain numbered version of the GNU Affero General + Public License "or any later version" applies to it, you have the + option of following the terms and conditions either of that numbered + version or of any later version published by the Free Software + Foundation. If the Program does not specify a version number of the + GNU Affero General Public License, you may choose any version ever published + by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future + versions of the GNU Affero General Public License can be used, that proxy's + public statement of acceptance of a version permanently authorizes you + to choose that version for the Program. + + Later license versions may give you additional or different + permissions. However, no additional obligations are imposed on any + author or copyright holder as a result of your choosing to follow a + later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY + APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT + HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY + OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM + IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF + ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING + WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS + THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY + GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE + USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF + DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD + PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), + EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF + SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided + above cannot be given local legal effect according to their terms, + reviewing courts shall apply local law that most closely approximates + an absolute waiver of all civil liability in connection with the + Program, unless a warranty or assumption of liability accompanies a + copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest + possible use to the public, the best way to achieve this is to make it + free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest + to attach them to the start of each source file to most effectively + state the exclusion of warranty; and each file should have at least + the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer + network, you should also make sure that it provides a way for users to + get its source. For example, if your program is a web application, its + interface could display a "Source" link that leads users to an archive + of the code. There are many ways you could offer source, and different + solutions will be better for different programs; see section 13 for the + specific requirements. + + You should also get your employer (if you work as a programmer) or school, + if any, to sign a "copyright disclaimer" for the program, if necessary. + For more information on this, and how to apply and follow the GNU AGPL, see + . diff --git a/Makefile b/Makefile index 681f34b3..4746ce75 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,7 @@ lint: .PHONY: test test: | $(GINKGO) $(LINT) + go get -t ./... go vet ./... go fmt ./... $(GINKGO) -r --skipPackage=integration_tests,integration diff --git a/README.md b/README.md index 21810a7a..87b4d2f3 100644 --- a/README.md +++ b/README.md @@ -9,25 +9,53 @@ Vulcanize DB is a set of tools that make it easier for developers to write application-specific indexes and caches for dapps built on Ethereum. ## Dependencies - - Go 1.9+ + - Go 1.11+ - Postgres 10 - Ethereum Node - - [Go Ethereum](https://ethereum.github.io/go-ethereum/downloads/) (1.8+) + - [Go Ethereum](https://ethereum.github.io/go-ethereum/downloads/) (1.8.18+) - [Parity 1.8.11+](https://github.com/paritytech/parity/releases) +## Project Setup + +Using Vulcanize for the first time requires several steps be done in order to allow use of the software. The following instructions will offer a guide through the steps of the process: + +1. Fetching the project +2. Installing dependencies +3. Configuring shell environment +4. Database setup +5. Configuring synced Ethereum node integration +6. Data syncing + ## Installation + +In order to fetch the project codebase for local use or modification, install it to your `GOPATH` via: + `go get github.com/vulcanize/vulcanizedb` `go get gopkg.in/DataDog/dd-trace-go.v1/ddtrace` +Once fetched, dependencies can be installed via `go get` or (the preferred method) at specific versions via `golang/dep`, the prototype golang pakcage manager. Installation instructions are [here](https://golang.github.io/dep/docs/installation.html). + +In order to install packages with `dep`, ensure you are in the project directory now within your `GOPATH` (default location is `~/go/src/github.com/vulcanize/vulcanizedb/`) and run: + +`dep ensure` + +After `dep` finishes, dependencies should be installed within your `GOPATH` at the versions specified in `Gopkg.toml`. + +Lastly, ensure that `GOPATH` is defined in your shell. If necessary, `GOPATH` can be set in `~/.bashrc` or `~/.bash_profile`, depending upon your system. It can be additionally helpful to add `$GOPATH/bin` to your shell's `$PATH`. + ## Setting up the Database 1. Install Postgres 1. Create a superuser for yourself and make sure `psql --list` works without prompting for a password. -1. `createdb vulcanize_public` -1. `cd $GOPATH/src/github.com/vulcanize/vulcanizedb` -1. Run the migrations: `make migrate HOST_NAME=localhost NAME=vulcanize_public PORT=5432` +1. Execute `createdb vulcanize_public` +1. Execute `cd $GOPATH/src/github.com/vulcanize/vulcanizedb` +1. Run the migrations: `make migrate HOST_NAME=localhost NAME=vulcanize_public PORT=` * See below for configuring additional environments +In some cases (such as recent Ubuntu systems), it may be necessary to overcome failures of password authentication from `localhost`. To allow access on Ubuntu, set localhost connections via hostname, ipv4, and ipv6 from `peer`/`md5` to `trust` in: `/etc/postgresql//pg_hba.conf` + +(It should be noted that trusted auth should only be enabled on systems without sensitive data in them: development and local test databases.) + ## Create a migration file (up and down) 1. ./script/create_migrate create_bite_table @@ -35,24 +63,24 @@ Vulcanize DB is a set of tools that make it easier for developers to write appli - To use a local Ethereum node, copy `environments/public.toml.example` to `environments/public.toml` and update the `ipcPath` and `levelDbPath`. - `ipcPath` should match the local node's IPC filepath: - - when using geth: + - For Geth: - The IPC file is called `geth.ipc`. - The geth IPC file path is printed to the console when you start geth. - The default location is: - - Mac: `$HOME/Library/Ethereum` - - Linux: `$HOME/.ethereum` + - Mac: `/Library/Ethereum` + - Linux: `/ethereum/geth.ipc` - - when using parity: + - For Parity: - The IPC file is called `jsonrpc.ipc`. - The default location is: - - Mac: `$HOME/Library/Application\ Support/io.parity.ethereum/` - - Linux: `$HOME/.local/share/io.parity.ethereum/` + - Mac: `/Library/Application\ Support/io.parity.ethereum/` + - Linux: `/local/share/io.parity.ethereum/` - `levelDbPath` should match Geth's chaindata directory path. - The geth LevelDB chaindata path is printed to the console when you start geth. - The default location is: - - Mac: `$HOME/Library/Ethereum/geth/chaindata` - - Linux: `$HOME/.ethereum/geth/chaindata` + - Mac: `/Library/Ethereum/geth/chaindata` + - Linux: `/ethereum/geth/chaindata` - `levelDbPath` is irrelevant (and `coldImport` is currently unavailable) if only running parity. - See `environments/infura.toml` to configure commands to run against infura, if a local node is unavailable. @@ -69,10 +97,10 @@ This command is useful when you want to maintain a broad cache of what's happeni Sync VulcanizeDB from the LevelDB underlying a Geth node. 1. Assure node is not running, and that it has synced to the desired block height. 1. Start vulcanize_db - - `./vulcanizedb coldImport --config --starting-block-number --ending-block-number ` + - `./vulcanizedb coldImport --config ` 1. Optional flags: - - `--starting-block-number`/`-s`: block number to start syncing from - - `--ending-block-number`/`-e`: block number to sync to + - `--starting-block-number `/`-s `: block number to start syncing from + - `--ending-block-number `/`-e `: block number to sync to - `--all`/`-a`: sync all missing blocks ## Alternatively, sync in "light" mode @@ -138,3 +166,37 @@ If you have full rinkeby chaindata you can move it to `rinkeby_vulcanizedb_geth_ 1. you will need to make sure you have ssh agent running and your ssh key added to it. instructions [here](https://developer.github.com/v3/guides/using-ssh-agent-forwarding/#your-key-must-be-available-to-ssh-agent) 1. `go get -u github.com/pressly/sup/cmd/sup` 1. `sup staging deploy` + +## omniWatcher and lightOmniWatcher +These commands require a pre-synced (full or light) vulcanizeDB (see above sections) + +To watch all events of a contract using a light synced vDB: + - Execute `./vulcanizedb omniWatcher --config --contract-address ` + +Or if you are using a full synced vDB, change the mode to full: + - Execute `./vulcanizedb omniWatcher --mode full --config --contract-address ` + +To watch contracts on a network other than mainnet, use the network flag: + - Execute `./vulcanizedb omniWatcher --config --contract-address --network ` + +To watch events starting at a certain block use the starting block flag: + - Execute `./vulcanizedb omniWatcher --config --contract-address --starting-block-number <#>` + +To watch only specified events use the events flag: + - Execute `./vulcanizedb omniWatcher --config --contract-address --events --events ` + +To watch events and poll the specified methods with any addresses and hashes emitted by the watched events utilize the methods flag: + - Execute `./vulcanizedb omniWatcher --config --contract-address --methods --methods ` + +To watch specified events and poll the specified method with any addresses and hashes emitted by the watched events: + - Execute `./vulcanizedb omniWatcher --config --contract-address --events --events --methods ` + +To turn on method piping so that values returned from previous method calls are cached and used as arguments in subsequent method calls: + - Execute `./vulcanizedb omniWatcher --config --piping true --contract-address --events --events --methods ` + +To watch all types of events of the contract but only persist the ones that emit one of the filtered-for argument values: + - Execute `./vulcanizedb omniWatcher --config --contract-address --event-args --event-args ` + +To watch all events of the contract but only poll the specified method with specified argument values (if they are emitted from the watched events): + - Execute `./vulcanizedb omniWatcher --config --contract-address --methods --method-args --method-args ` + diff --git a/cmd/coldImport.go b/cmd/coldImport.go index f9045f06..0d8efdbf 100644 --- a/cmd/coldImport.go +++ b/cmd/coldImport.go @@ -1,16 +1,18 @@ +// VulcanizeDB // Copyright © 2018 Vulcanize -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . package cmd diff --git a/cmd/lightSync.go b/cmd/lightSync.go index 630f487f..f464144a 100644 --- a/cmd/lightSync.go +++ b/cmd/lightSync.go @@ -1,16 +1,18 @@ +// VulcanizeDB // Copyright © 2018 Vulcanize -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . package cmd @@ -35,16 +37,24 @@ var lightSyncCmd = &cobra.Command{ Short: "Syncs VulcanizeDB with local ethereum node's block headers", Long: `Syncs VulcanizeDB with local ethereum node. Populates Postgres with block headers. +<<<<<<< HEAD ./vulcanizedb lightSync --starting-block-number 0 --config public.toml Expects ethereum node to be running and requires a .toml config: +======= +./vulcanizedb lightSync --starting-block-number 0 --config public.toml +Expects ethereum node to be running and requires a .toml config: +>>>>>>> origin/master [database] name = "vulcanize_public" hostname = "localhost" port = 5432 +<<<<<<< HEAD +======= +>>>>>>> origin/master [client] ipcPath = "/Users/user/Library/Ethereum/geth.ipc" `, diff --git a/cmd/omniWatcher.go b/cmd/omniWatcher.go new file mode 100644 index 00000000..a46549ef --- /dev/null +++ b/cmd/omniWatcher.go @@ -0,0 +1,121 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package cmd + +import ( + "fmt" + "log" + "time" + + "github.com/spf13/cobra" + + ft "github.com/vulcanize/vulcanizedb/pkg/omni/full/transformer" + lt "github.com/vulcanize/vulcanizedb/pkg/omni/light/transformer" + st "github.com/vulcanize/vulcanizedb/pkg/omni/shared/transformer" + "github.com/vulcanize/vulcanizedb/utils" +) + +// omniWatcherCmd represents the omniWatcher command +var omniWatcherCmd = &cobra.Command{ + Use: "omniWatcher", + Short: "Watches events at the provided contract address using fully synced vDB", + Long: `Uses input contract address and event filters to watch events + +Expects an ethereum node to be running +Expects an archival node synced into vulcanizeDB +Requires a .toml config file: + + [database] + name = "vulcanize_public" + hostname = "localhost" + port = 5432 + + [client] + ipcPath = "/Users/user/Library/Ethereum/geth.ipc" +`, + Run: func(cmd *cobra.Command, args []string) { + omniWatcher() + }, +} + +var ( + network string + contractAddress string + contractAddresses []string + contractEvents []string + contractMethods []string + eventArgs []string + methodArgs []string + methodPiping bool + mode string +) + +func omniWatcher() { + if contractAddress == "" && len(contractAddresses) == 0 { + log.Fatal("Contract address required") + } + + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + blockChain := getBlockChain() + db := utils.LoadPostgres(databaseConfig, blockChain.Node()) + + var t st.Transformer + switch mode { + case "light": + t = lt.NewTransformer(network, blockChain, &db) + case "full": + t = ft.NewTransformer(network, blockChain, &db) + default: + log.Fatal("Invalid mode") + } + + contractAddresses = append(contractAddresses, contractAddress) + for _, addr := range contractAddresses { + t.SetEvents(addr, contractEvents) + t.SetMethods(addr, contractMethods) + t.SetEventArgs(addr, eventArgs) + t.SetMethodArgs(addr, methodArgs) + t.SetPiping(addr, methodPiping) + t.SetStartingBlock(addr, startingBlockNumber) + } + + err := t.Init() + if err != nil { + log.Fatal(fmt.Sprintf("Failed to initialized transformer\r\nerr: %v\r\n", err)) + } + + for range ticker.C { + t.Execute() + } +} + +func init() { + rootCmd.AddCommand(omniWatcherCmd) + + omniWatcherCmd.Flags().StringVarP(&mode, "mode", "o", "light", "'light' or 'full' mode to work with either light synced or fully synced vDB (default is light)") + omniWatcherCmd.Flags().StringVarP(&contractAddress, "contract-address", "a", "", "Single address to generate watchers for") + omniWatcherCmd.Flags().StringArrayVarP(&contractAddresses, "contract-addresses", "l", []string{}, "list of addresses to use; warning: watcher targets the same events and methods for each address") + omniWatcherCmd.Flags().StringArrayVarP(&contractEvents, "events", "e", []string{}, "Subset of events to watch; by default all events are watched") + omniWatcherCmd.Flags().StringArrayVarP(&contractMethods, "methods", "m", nil, "Subset of methods to poll; by default no methods are polled") + omniWatcherCmd.Flags().StringArrayVarP(&eventArgs, "event-args", "f", []string{}, "Argument values to filter event logs for; will only persist event logs that emit at least one of the value specified") + omniWatcherCmd.Flags().StringArrayVarP(&methodArgs, "method-args", "g", []string{}, "Argument values to limit methods to; will only call methods with emitted values that were specified here") + omniWatcherCmd.Flags().StringVarP(&network, "network", "n", "", `Network the contract is deployed on; options: "ropsten", "kovan", and "rinkeby"; default is mainnet"`) + omniWatcherCmd.Flags().Int64VarP(&startingBlockNumber, "starting-block-number", "s", 0, "Block to begin watching- default is first block the contract exists") + omniWatcherCmd.Flags().BoolVarP(&methodPiping, "piping", "p", false, "Turn on method output piping: methods listed first will be polled first and their output used as input to subsequent methods") +} diff --git a/cmd/root.go b/cmd/root.go index ad425540..5eac5b39 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,16 +1,18 @@ +// VulcanizeDB // Copyright © 2018 Vulcanize -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . package cmd @@ -23,8 +25,8 @@ import ( log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/ethereum/go-ethereum/ethclient" + "github.com/vulcanize/vulcanizedb/pkg/config" "github.com/vulcanize/vulcanizedb/pkg/geth" "github.com/vulcanize/vulcanizedb/pkg/geth/client" @@ -129,3 +131,4 @@ func getBlockChain() *geth.BlockChain { transactionConverter := vRpc.NewRpcTransactionConverter(ethClient) return geth.NewBlockChain(vdbEthClient, rpcClient, vdbNode, transactionConverter) } + diff --git a/cmd/sync.go b/cmd/sync.go index bbf51a9c..243ffb2e 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -1,16 +1,18 @@ +// VulcanizeDB // Copyright © 2018 Vulcanize -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . package cmd diff --git a/db/schema.sql b/db/schema.sql index 94e5e203..c245a293 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -2662,5 +2662,4 @@ ALTER TABLE ONLY public.logs -- -- PostgreSQL database dump complete --- - +-- \ No newline at end of file diff --git a/integration_test/block_rewards_test.go b/integration_test/block_rewards_test.go index 30185d30..f379b870 100644 --- a/integration_test/block_rewards_test.go +++ b/integration_test/block_rewards_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package integration import ( diff --git a/integration_test/contract_test.go b/integration_test/contract_test.go index ad143506..5357859c 100644 --- a/integration_test/contract_test.go +++ b/integration_test/contract_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package integration import ( @@ -8,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/rpc" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/geth" "github.com/vulcanize/vulcanizedb/pkg/geth/client" @@ -79,7 +96,10 @@ var _ = Describe("Reading contracts", func() { contract := testing.SampleContract() var balance = new(big.Int) - args := common.HexToHash("0xd26114cd6ee289accf82350c8d8487fedb8a0c07") + + args := make([]interface{}, 1) + args[0] = common.HexToHash("0xd26114cd6ee289accf82350c8d8487fedb8a0c07") + err = blockChain.FetchContractData(contract.Abi, "0xd26114cd6ee289accf82350c8d8487fedb8a0c07", "balanceOf", args, &balance, 5167471) Expect(err).NotTo(HaveOccurred()) expected := new(big.Int) diff --git a/integration_test/geth_blockchain_test.go b/integration_test/geth_blockchain_test.go index 5435eb14..54719774 100644 --- a/integration_test/geth_blockchain_test.go +++ b/integration_test/geth_blockchain_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package integration_test import ( @@ -5,6 +21,7 @@ import ( "github.com/ethereum/go-ethereum/rpc" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/fakes" "github.com/vulcanize/vulcanizedb/pkg/geth" diff --git a/integration_test/integration_test_suite_test.go b/integration_test/integration_test_suite_test.go index b9c75608..cc2318de 100644 --- a/integration_test/integration_test_suite_test.go +++ b/integration_test/integration_test_suite_test.go @@ -1,10 +1,26 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package integration_test import ( + "testing" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - - "testing" ) func TestIntegrationTest(t *testing.T) { diff --git a/libraries/shared/shared_suite_test.go b/libraries/shared/shared_suite_test.go index 62a8f3d4..8d079e5d 100644 --- a/libraries/shared/shared_suite_test.go +++ b/libraries/shared/shared_suite_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package shared_test import ( diff --git a/libraries/shared/watcher.go b/libraries/shared/watcher.go index 5e1bfdfd..eddf11c9 100644 --- a/libraries/shared/watcher.go +++ b/libraries/shared/watcher.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package shared import ( diff --git a/libraries/shared/watcher_test.go b/libraries/shared/watcher_test.go index 8312c2b9..94e8fb33 100644 --- a/libraries/shared/watcher_test.go +++ b/libraries/shared/watcher_test.go @@ -1,12 +1,30 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package shared_test import ( "errors" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/vulcanize/vulcanizedb/libraries/shared" "github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" diff --git a/pkg/config/client.go b/pkg/config/client.go index 48c57c1a..650bf5b1 100644 --- a/pkg/config/client.go +++ b/pkg/config/client.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package config type Client struct { diff --git a/pkg/config/config.go b/pkg/config/config.go index d53444ea..9b7c9799 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package config type Config struct { diff --git a/pkg/config/config_suite_test.go b/pkg/config/config_suite_test.go index 6508e6da..22df0c6f 100644 --- a/pkg/config/config_suite_test.go +++ b/pkg/config/config_suite_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package config_test import ( diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 1059ec45..fe8dc0aa 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package config_test import ( diff --git a/pkg/config/database.go b/pkg/config/database.go index 84f139fa..62bdd8bf 100644 --- a/pkg/config/database.go +++ b/pkg/config/database.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package config import "fmt" diff --git a/pkg/core/block.go b/pkg/core/block.go index a81bdec6..cf3eb5e6 100644 --- a/pkg/core/block.go +++ b/pkg/core/block.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package core type Block struct { diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index ac9fc6a4..829b144b 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1,9 +1,26 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package core import ( + "math/big" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/core/types" - "math/big" ) type BlockChain interface { @@ -18,5 +35,5 @@ type BlockChain interface { } type ContractDataFetcher interface { - FetchContractData(abiJSON string, address string, method string, methodArg interface{}, result interface{}, blockNumber int64) error + FetchContractData(abiJSON string, address string, method string, methodArgs []interface{}, result interface{}, blockNumber int64) error } diff --git a/pkg/core/contract.go b/pkg/core/contract.go index 8b881e7d..1ca4dffd 100644 --- a/pkg/core/contract.go +++ b/pkg/core/contract.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package core type Contract struct { diff --git a/pkg/core/eth_client.go b/pkg/core/eth_client.go index 186f222d..2ec6c6bb 100644 --- a/pkg/core/eth_client.go +++ b/pkg/core/eth_client.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package core import ( diff --git a/pkg/core/header.go b/pkg/core/header.go index 6d81f122..007e3aad 100644 --- a/pkg/core/header.go +++ b/pkg/core/header.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package core import ( diff --git a/pkg/core/log.go b/pkg/core/log.go index 984e0432..33ff4146 100644 --- a/pkg/core/log.go +++ b/pkg/core/log.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package core type Log struct { diff --git a/pkg/core/node_info.go b/pkg/core/node_info.go index 5477259e..6b84ca36 100644 --- a/pkg/core/node_info.go +++ b/pkg/core/node_info.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package core import ( diff --git a/pkg/core/receipts.go b/pkg/core/receipts.go index 1d8eaeb0..dad982e6 100644 --- a/pkg/core/receipts.go +++ b/pkg/core/receipts.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package core type Receipt struct { diff --git a/pkg/core/rpc_client.go b/pkg/core/rpc_client.go index 51309d93..66b29c00 100644 --- a/pkg/core/rpc_client.go +++ b/pkg/core/rpc_client.go @@ -1,7 +1,24 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package core import ( "context" + "github.com/vulcanize/vulcanizedb/pkg/geth/client" ) diff --git a/pkg/core/topics.go b/pkg/core/topics.go index 2f1acb9d..643579b9 100644 --- a/pkg/core/topics.go +++ b/pkg/core/topics.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package core type Topics [4]string diff --git a/pkg/core/transaction.go b/pkg/core/transaction.go index e1d45dde..ca3beb9a 100644 --- a/pkg/core/transaction.go +++ b/pkg/core/transaction.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package core type Transaction struct { diff --git a/pkg/core/watched_event_log.go b/pkg/core/watched_event_log.go index 04090bcf..fa2a495b 100644 --- a/pkg/core/watched_event_log.go +++ b/pkg/core/watched_event_log.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package core type WatchedEvent struct { diff --git a/pkg/crypto/crypto_suite_test.go b/pkg/crypto/crypto_suite_test.go index e60462b9..4d08b0b0 100644 --- a/pkg/crypto/crypto_suite_test.go +++ b/pkg/crypto/crypto_suite_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package crypto_test import ( diff --git a/pkg/crypto/parser.go b/pkg/crypto/parser.go index a8448233..91af01a8 100644 --- a/pkg/crypto/parser.go +++ b/pkg/crypto/parser.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package crypto import ( diff --git a/pkg/crypto/parser_test.go b/pkg/crypto/parser_test.go index 77c1478e..711e9d83 100644 --- a/pkg/crypto/parser_test.go +++ b/pkg/crypto/parser_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package crypto_test import ( diff --git a/pkg/datastore/ethereum/config.go b/pkg/datastore/ethereum/config.go index 039089ac..589ae819 100644 --- a/pkg/datastore/ethereum/config.go +++ b/pkg/datastore/ethereum/config.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package ethereum type DatabaseType int diff --git a/pkg/datastore/ethereum/database.go b/pkg/datastore/ethereum/database.go index c0676c41..2f4bb577 100644 --- a/pkg/datastore/ethereum/database.go +++ b/pkg/datastore/ethereum/database.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package ethereum import ( diff --git a/pkg/datastore/ethereum/level/database.go b/pkg/datastore/ethereum/level/database.go index 81635468..6deb7125 100644 --- a/pkg/datastore/ethereum/level/database.go +++ b/pkg/datastore/ethereum/level/database.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package level import ( diff --git a/pkg/datastore/ethereum/level/database_reader.go b/pkg/datastore/ethereum/level/database_reader.go index 62caae7f..66f49c8d 100644 --- a/pkg/datastore/ethereum/level/database_reader.go +++ b/pkg/datastore/ethereum/level/database_reader.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package level import ( diff --git a/pkg/datastore/ethereum/level/database_test.go b/pkg/datastore/ethereum/level/database_test.go index 4d2018da..5e9de31e 100644 --- a/pkg/datastore/ethereum/level/database_test.go +++ b/pkg/datastore/ethereum/level/database_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package level_test import ( diff --git a/pkg/datastore/ethereum/level/level_suite_test.go b/pkg/datastore/ethereum/level/level_suite_test.go index 20e571bd..26c6cb49 100644 --- a/pkg/datastore/ethereum/level/level_suite_test.go +++ b/pkg/datastore/ethereum/level/level_suite_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package level_test import ( diff --git a/pkg/datastore/postgres/postgres.go b/pkg/datastore/postgres/postgres.go index 0dcdfce8..82a1b2ac 100644 --- a/pkg/datastore/postgres/postgres.go +++ b/pkg/datastore/postgres/postgres.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package postgres import ( diff --git a/pkg/datastore/postgres/postgres_suite_test.go b/pkg/datastore/postgres/postgres_suite_test.go index 34814fba..48c8c677 100644 --- a/pkg/datastore/postgres/postgres_suite_test.go +++ b/pkg/datastore/postgres/postgres_suite_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package postgres_test import ( diff --git a/pkg/datastore/postgres/postgres_test.go b/pkg/datastore/postgres/postgres_test.go index 9ed26252..447665e3 100644 --- a/pkg/datastore/postgres/postgres_test.go +++ b/pkg/datastore/postgres/postgres_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package postgres_test import ( diff --git a/pkg/datastore/postgres/repositories/block_repository.go b/pkg/datastore/postgres/repositories/block_repository.go index 40570e45..92f57144 100644 --- a/pkg/datastore/postgres/repositories/block_repository.go +++ b/pkg/datastore/postgres/repositories/block_repository.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package repositories import ( diff --git a/pkg/datastore/postgres/repositories/block_repository_test.go b/pkg/datastore/postgres/repositories/block_repository_test.go index 4a7bd636..ae391424 100644 --- a/pkg/datastore/postgres/repositories/block_repository_test.go +++ b/pkg/datastore/postgres/repositories/block_repository_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package repositories_test import ( diff --git a/pkg/datastore/postgres/repositories/contract_repository.go b/pkg/datastore/postgres/repositories/contract_repository.go index 1640fd8a..0ff0afdd 100644 --- a/pkg/datastore/postgres/repositories/contract_repository.go +++ b/pkg/datastore/postgres/repositories/contract_repository.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package repositories import ( diff --git a/pkg/datastore/postgres/repositories/contract_repository_test.go b/pkg/datastore/postgres/repositories/contract_repository_test.go index f08ce105..e386cf99 100644 --- a/pkg/datastore/postgres/repositories/contract_repository_test.go +++ b/pkg/datastore/postgres/repositories/contract_repository_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package repositories_test import ( diff --git a/pkg/datastore/postgres/repositories/header_repository.go b/pkg/datastore/postgres/repositories/header_repository.go index bd9635ca..591496e5 100644 --- a/pkg/datastore/postgres/repositories/header_repository.go +++ b/pkg/datastore/postgres/repositories/header_repository.go @@ -1,9 +1,27 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package repositories import ( "database/sql" "errors" + log "github.com/sirupsen/logrus" + "github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" ) diff --git a/pkg/datastore/postgres/repositories/header_repository_test.go b/pkg/datastore/postgres/repositories/header_repository_test.go index 9aa440df..8bcd1158 100644 --- a/pkg/datastore/postgres/repositories/header_repository_test.go +++ b/pkg/datastore/postgres/repositories/header_repository_test.go @@ -1,17 +1,35 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package repositories_test import ( "database/sql" "encoding/json" + "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories" "github.com/vulcanize/vulcanizedb/test_config" - "math/big" ) var _ = Describe("Block header repository", func() { @@ -43,7 +61,6 @@ var _ = Describe("Block header repository", func() { Describe("creating or updating a header", func() { It("adds a header", func() { _, err = repo.CreateOrUpdateHeader(header) - Expect(err).NotTo(HaveOccurred()) var dbHeader core.Header err = db.Get(&dbHeader, `SELECT block_number, hash, raw, block_timestamp FROM public.headers WHERE block_number = $1`, header.BlockNumber) @@ -56,7 +73,6 @@ var _ = Describe("Block header repository", func() { It("adds node data to header", func() { _, err = repo.CreateOrUpdateHeader(header) - Expect(err).NotTo(HaveOccurred()) var ethNodeId int64 err = db.Get(ðNodeId, `SELECT eth_node_id FROM public.headers WHERE block_number = $1`, header.BlockNumber) diff --git a/pkg/datastore/postgres/repositories/log_filter_repository.go b/pkg/datastore/postgres/repositories/log_filter_repository.go index 6b1d3d7c..faad7ca8 100644 --- a/pkg/datastore/postgres/repositories/log_filter_repository.go +++ b/pkg/datastore/postgres/repositories/log_filter_repository.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package repositories import ( diff --git a/pkg/datastore/postgres/repositories/log_filter_repository_test.go b/pkg/datastore/postgres/repositories/log_filter_repository_test.go index d19ffe18..5426868d 100644 --- a/pkg/datastore/postgres/repositories/log_filter_repository_test.go +++ b/pkg/datastore/postgres/repositories/log_filter_repository_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package repositories_test import ( diff --git a/pkg/datastore/postgres/repositories/logs_repository.go b/pkg/datastore/postgres/repositories/logs_repository.go index 45d29b88..4886e15f 100644 --- a/pkg/datastore/postgres/repositories/logs_repository.go +++ b/pkg/datastore/postgres/repositories/logs_repository.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package repositories import ( diff --git a/pkg/datastore/postgres/repositories/logs_repository_test.go b/pkg/datastore/postgres/repositories/logs_repository_test.go index 49621246..1f88cc6c 100644 --- a/pkg/datastore/postgres/repositories/logs_repository_test.go +++ b/pkg/datastore/postgres/repositories/logs_repository_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package repositories_test import ( diff --git a/pkg/datastore/postgres/repositories/receipt_repository.go b/pkg/datastore/postgres/repositories/receipt_repository.go index 997ee58c..9981559c 100644 --- a/pkg/datastore/postgres/repositories/receipt_repository.go +++ b/pkg/datastore/postgres/repositories/receipt_repository.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package repositories import ( diff --git a/pkg/datastore/postgres/repositories/receipts_repository_test.go b/pkg/datastore/postgres/repositories/receipts_repository_test.go index 07917b15..4ba64db4 100644 --- a/pkg/datastore/postgres/repositories/receipts_repository_test.go +++ b/pkg/datastore/postgres/repositories/receipts_repository_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package repositories_test import ( diff --git a/pkg/datastore/postgres/repositories/repositories_suite_test.go b/pkg/datastore/postgres/repositories/repositories_suite_test.go index b222d38c..bf45707e 100644 --- a/pkg/datastore/postgres/repositories/repositories_suite_test.go +++ b/pkg/datastore/postgres/repositories/repositories_suite_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package repositories_test import ( diff --git a/pkg/datastore/postgres/repositories/watched_events_repository.go b/pkg/datastore/postgres/repositories/watched_events_repository.go index d4682d2a..b5931666 100644 --- a/pkg/datastore/postgres/repositories/watched_events_repository.go +++ b/pkg/datastore/postgres/repositories/watched_events_repository.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package repositories import ( diff --git a/pkg/datastore/postgres/repositories/watched_events_repository_test.go b/pkg/datastore/postgres/repositories/watched_events_repository_test.go index 0b224bf2..100e0f00 100644 --- a/pkg/datastore/postgres/repositories/watched_events_repository_test.go +++ b/pkg/datastore/postgres/repositories/watched_events_repository_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package repositories_test import ( diff --git a/pkg/datastore/repository.go b/pkg/datastore/repository.go index 277b8184..32e6c0a8 100644 --- a/pkg/datastore/repository.go +++ b/pkg/datastore/repository.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package datastore import ( diff --git a/pkg/fakes/data.go b/pkg/fakes/data.go index d8236efc..59a43a2d 100644 --- a/pkg/fakes/data.go +++ b/pkg/fakes/data.go @@ -1,13 +1,31 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package fakes import ( "encoding/json" "errors" + "strconv" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/transformers/shared/constants" - "strconv" ) var ( diff --git a/pkg/fakes/mock_block_repository.go b/pkg/fakes/mock_block_repository.go index 7c05bfc0..b39a034f 100644 --- a/pkg/fakes/mock_block_repository.go +++ b/pkg/fakes/mock_block_repository.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package fakes import ( diff --git a/pkg/fakes/mock_blockchain.go b/pkg/fakes/mock_blockchain.go index 2b7159ee..1cedff56 100644 --- a/pkg/fakes/mock_blockchain.go +++ b/pkg/fakes/mock_blockchain.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package fakes import ( @@ -15,7 +31,7 @@ type MockBlockChain struct { fetchContractDataPassedAbi string fetchContractDataPassedAddress string fetchContractDataPassedMethod string - fetchContractDataPassedMethodArg interface{} + fetchContractDataPassedMethodArgs []interface{} fetchContractDataPassedResult interface{} fetchContractDataPassedBlockNumber int64 getBlockByNumberErr error @@ -52,14 +68,14 @@ func (chain *MockBlockChain) SetGetEthLogsWithCustomQueryReturnLogs(logs []types chain.logQueryReturnLogs = logs } -func (chain *MockBlockChain) FetchContractData(abiJSON, address, method string, methodArg, result interface{}, blockNumber int64) error { - chain.fetchContractDataPassedAbi = abiJSON - chain.fetchContractDataPassedAddress = address - chain.fetchContractDataPassedMethod = method - chain.fetchContractDataPassedMethodArg = methodArg - chain.fetchContractDataPassedResult = result - chain.fetchContractDataPassedBlockNumber = blockNumber - return chain.fetchContractDataErr +func (blockChain *MockBlockChain) FetchContractData(abiJSON string, address string, method string, methodArgs []interface{}, result interface{}, blockNumber int64) error { + blockChain.fetchContractDataPassedAbi = abiJSON + blockChain.fetchContractDataPassedAddress = address + blockChain.fetchContractDataPassedMethod = method + blockChain.fetchContractDataPassedMethodArgs = methodArgs + blockChain.fetchContractDataPassedResult = result + blockChain.fetchContractDataPassedBlockNumber = blockNumber + return blockChain.fetchContractDataErr } func (chain *MockBlockChain) GetBlockByNumber(blockNumber int64) (core.Block, error) { @@ -100,12 +116,12 @@ func (chain *MockBlockChain) Node() core.Node { return chain.node } -func (chain *MockBlockChain) AssertFetchContractDataCalledWith(abiJSON string, address string, method string, methodArg interface{}, result interface{}, blockNumber int64) { +func (chain *MockBlockChain) AssertFetchContractDataCalledWith(abiJSON string, address string, method string, methodArgs []interface{}, result interface{}, blockNumber int64) { Expect(chain.fetchContractDataPassedAbi).To(Equal(abiJSON)) Expect(chain.fetchContractDataPassedAddress).To(Equal(address)) Expect(chain.fetchContractDataPassedMethod).To(Equal(method)) - if methodArg != nil { - Expect(chain.fetchContractDataPassedMethodArg).To(Equal(methodArg)) + if methodArgs != nil { + Expect(chain.fetchContractDataPassedMethodArgs).To(Equal(methodArgs)) } Expect(chain.fetchContractDataPassedResult).To(BeAssignableToTypeOf(result)) Expect(chain.fetchContractDataPassedBlockNumber).To(Equal(blockNumber)) diff --git a/pkg/fakes/mock_crypto_parser.go b/pkg/fakes/mock_crypto_parser.go index 98b21f80..e4de379c 100644 --- a/pkg/fakes/mock_crypto_parser.go +++ b/pkg/fakes/mock_crypto_parser.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package fakes import . "github.com/onsi/gomega" diff --git a/pkg/fakes/mock_eth_client.go b/pkg/fakes/mock_eth_client.go index fd040b58..7023e2bd 100644 --- a/pkg/fakes/mock_eth_client.go +++ b/pkg/fakes/mock_eth_client.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package fakes import ( @@ -8,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" . "github.com/onsi/gomega" + "github.com/vulcanize/vulcanizedb/pkg/geth/client" ) diff --git a/pkg/fakes/mock_ethereum_database.go b/pkg/fakes/mock_ethereum_database.go index a25d2b5c..038d04ba 100644 --- a/pkg/fakes/mock_ethereum_database.go +++ b/pkg/fakes/mock_ethereum_database.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package fakes import ( diff --git a/pkg/fakes/mock_fs_reader.go b/pkg/fakes/mock_fs_reader.go index c9a74466..e967a2cd 100644 --- a/pkg/fakes/mock_fs_reader.go +++ b/pkg/fakes/mock_fs_reader.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package fakes import . "github.com/onsi/gomega" diff --git a/pkg/fakes/mock_header_repository.go b/pkg/fakes/mock_header_repository.go index 97fad441..1a9bf630 100644 --- a/pkg/fakes/mock_header_repository.go +++ b/pkg/fakes/mock_header_repository.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package fakes import ( diff --git a/pkg/fakes/mock_level_database_reader.go b/pkg/fakes/mock_level_database_reader.go index f6d2b7df..261d6be0 100644 --- a/pkg/fakes/mock_level_database_reader.go +++ b/pkg/fakes/mock_level_database_reader.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package fakes import ( diff --git a/pkg/fakes/mock_receipt_repository.go b/pkg/fakes/mock_receipt_repository.go index b5d379b3..5663eeed 100644 --- a/pkg/fakes/mock_receipt_repository.go +++ b/pkg/fakes/mock_receipt_repository.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package fakes import ( diff --git a/pkg/fakes/mock_rpc_client.go b/pkg/fakes/mock_rpc_client.go index 8f25ef87..01e2cf8e 100644 --- a/pkg/fakes/mock_rpc_client.go +++ b/pkg/fakes/mock_rpc_client.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package fakes import ( @@ -77,7 +93,6 @@ func (client *MockRpcClient) CallContext(ctx context.Context, result interface{} if client.callContextErr != nil { return client.callContextErr } - case "parity_versionInfo": if p, ok := result.(*core.ParityNodeInfo); ok { *p = core.ParityNodeInfo{ @@ -143,3 +158,4 @@ func (client *MockRpcClient) AssertBatchCalledWith(method string, lengthOfBatch } Expect(client.passedMethod).To(Equal(method)) } + diff --git a/pkg/fakes/mock_transaction_converter.go b/pkg/fakes/mock_transaction_converter.go index 8595dec0..b36c390e 100644 --- a/pkg/fakes/mock_transaction_converter.go +++ b/pkg/fakes/mock_transaction_converter.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package fakes import ( diff --git a/pkg/filters/filter_query.go b/pkg/filters/filter_query.go index 60956ec8..26e3f428 100644 --- a/pkg/filters/filter_query.go +++ b/pkg/filters/filter_query.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package filters import ( diff --git a/pkg/filters/filter_test.go b/pkg/filters/filter_test.go index 474f0311..ea475f8c 100644 --- a/pkg/filters/filter_test.go +++ b/pkg/filters/filter_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package filters_test import ( diff --git a/pkg/filters/query_builder_suite_test.go b/pkg/filters/query_builder_suite_test.go index 42029812..7f49afb6 100644 --- a/pkg/filters/query_builder_suite_test.go +++ b/pkg/filters/query_builder_suite_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package filters_test import ( diff --git a/pkg/fs/reader.go b/pkg/fs/reader.go index 4029b8a7..fe9df6ac 100644 --- a/pkg/fs/reader.go +++ b/pkg/fs/reader.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package fs import "io/ioutil" diff --git a/pkg/geth/abi.go b/pkg/geth/abi.go index 5211dd98..d6943597 100644 --- a/pkg/geth/abi.go +++ b/pkg/geth/abi.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package geth import ( diff --git a/pkg/geth/abi_test.go b/pkg/geth/abi_test.go index 8843564e..3e5a78dc 100644 --- a/pkg/geth/abi_test.go +++ b/pkg/geth/abi_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package geth_test import ( diff --git a/pkg/geth/blockchain.go b/pkg/geth/blockchain.go index 7b9c49dd..347aa7c4 100644 --- a/pkg/geth/blockchain.go +++ b/pkg/geth/blockchain.go @@ -1,18 +1,35 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package geth import ( "errors" "math/big" + "strconv" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + "golang.org/x/net/context" + "github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/geth/client" vulcCommon "github.com/vulcanize/vulcanizedb/pkg/geth/converters/common" - "golang.org/x/net/context" - "strconv" ) var ErrEmptyHeader = errors.New("empty header returned over RPC") diff --git a/pkg/geth/blockchain_test.go b/pkg/geth/blockchain_test.go index 5f81ef78..a1ea45e6 100644 --- a/pkg/geth/blockchain_test.go +++ b/pkg/geth/blockchain_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package geth_test import ( @@ -7,10 +23,10 @@ import ( "github.com/ethereum/go-ethereum" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + vulcCore "github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/fakes" "github.com/vulcanize/vulcanizedb/pkg/geth" diff --git a/pkg/geth/client/eth_client.go b/pkg/geth/client/eth_client.go index 21b7cd3d..c4af6009 100644 --- a/pkg/geth/client/eth_client.go +++ b/pkg/geth/client/eth_client.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package client import ( diff --git a/pkg/geth/client/rpc_client.go b/pkg/geth/client/rpc_client.go index e2adf7c5..3f7e8fd8 100644 --- a/pkg/geth/client/rpc_client.go +++ b/pkg/geth/client/rpc_client.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package client import ( diff --git a/pkg/geth/cold_import/cold_import_suite_test.go b/pkg/geth/cold_import/cold_import_suite_test.go index 8c524782..22f9632c 100644 --- a/pkg/geth/cold_import/cold_import_suite_test.go +++ b/pkg/geth/cold_import/cold_import_suite_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package cold_import_test import ( diff --git a/pkg/geth/cold_import/importer.go b/pkg/geth/cold_import/importer.go index fbf4f90f..9ebeb0dc 100644 --- a/pkg/geth/cold_import/importer.go +++ b/pkg/geth/cold_import/importer.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package cold_import import ( diff --git a/pkg/geth/cold_import/importer_test.go b/pkg/geth/cold_import/importer_test.go index 5f9bf58c..3e50b110 100644 --- a/pkg/geth/cold_import/importer_test.go +++ b/pkg/geth/cold_import/importer_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package cold_import_test import ( diff --git a/pkg/geth/cold_import/node_builder.go b/pkg/geth/cold_import/node_builder.go index f3f79899..4e39bf69 100644 --- a/pkg/geth/cold_import/node_builder.go +++ b/pkg/geth/cold_import/node_builder.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package cold_import import ( diff --git a/pkg/geth/cold_import/node_builder_test.go b/pkg/geth/cold_import/node_builder_test.go index ec19a009..83300078 100644 --- a/pkg/geth/cold_import/node_builder_test.go +++ b/pkg/geth/cold_import/node_builder_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package cold_import_test import ( diff --git a/pkg/geth/contract.go b/pkg/geth/contract.go index 8e47f02f..4c49f422 100644 --- a/pkg/geth/contract.go +++ b/pkg/geth/contract.go @@ -1,9 +1,24 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package geth import ( - "errors" - "context" + "errors" "math/big" "github.com/ethereum/go-ethereum" @@ -14,14 +29,14 @@ var ( ErrInvalidStateAttribute = errors.New("invalid state attribute") ) -func (blockChain *BlockChain) FetchContractData(abiJSON string, address string, method string, methodArg interface{}, result interface{}, blockNumber int64) error { +func (blockChain *BlockChain) FetchContractData(abiJSON string, address string, method string, methodArgs []interface{}, result interface{}, blockNumber int64) error { parsed, err := ParseAbi(abiJSON) if err != nil { return err } var input []byte - if methodArg != nil { - input, err = parsed.Pack(method, methodArg) + if methodArgs != nil { + input, err = parsed.Pack(method, methodArgs...) } else { input, err = parsed.Pack(method) } diff --git a/pkg/geth/converters/cold_db/transaction_converter.go b/pkg/geth/converters/cold_db/transaction_converter.go index 044e6226..3b095754 100644 --- a/pkg/geth/converters/cold_db/transaction_converter.go +++ b/pkg/geth/converters/cold_db/transaction_converter.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package cold_db import ( diff --git a/pkg/geth/converters/common/block_converter.go b/pkg/geth/converters/common/block_converter.go index 6e13f474..8976a164 100644 --- a/pkg/geth/converters/common/block_converter.go +++ b/pkg/geth/converters/common/block_converter.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package common import ( diff --git a/pkg/geth/converters/common/block_converter_test.go b/pkg/geth/converters/common/block_converter_test.go index bf4fe0da..0df05be4 100644 --- a/pkg/geth/converters/common/block_converter_test.go +++ b/pkg/geth/converters/common/block_converter_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package common_test import ( diff --git a/pkg/geth/converters/common/block_rewards.go b/pkg/geth/converters/common/block_rewards.go index f37a938a..51c967fc 100644 --- a/pkg/geth/converters/common/block_rewards.go +++ b/pkg/geth/converters/common/block_rewards.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package common import ( diff --git a/pkg/geth/converters/common/common_suite_test.go b/pkg/geth/converters/common/common_suite_test.go index 690b5549..3d8de4d8 100644 --- a/pkg/geth/converters/common/common_suite_test.go +++ b/pkg/geth/converters/common/common_suite_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package common_test import ( diff --git a/pkg/geth/converters/common/header_converter.go b/pkg/geth/converters/common/header_converter.go index a6cd2eaf..c160e427 100644 --- a/pkg/geth/converters/common/header_converter.go +++ b/pkg/geth/converters/common/header_converter.go @@ -1,7 +1,24 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package common import ( "encoding/json" + "github.com/ethereum/go-ethereum/core/types" "github.com/vulcanize/vulcanizedb/pkg/core" ) diff --git a/pkg/geth/converters/common/header_converter_test.go b/pkg/geth/converters/common/header_converter_test.go index f25b02eb..7eb12d83 100644 --- a/pkg/geth/converters/common/header_converter_test.go +++ b/pkg/geth/converters/common/header_converter_test.go @@ -1,8 +1,23 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package common_test import ( "encoding/json" - "github.com/vulcanize/vulcanizedb/pkg/fakes" "math/big" "github.com/ethereum/go-ethereum/common" @@ -10,6 +25,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/vulcanize/vulcanizedb/pkg/fakes" common2 "github.com/vulcanize/vulcanizedb/pkg/geth/converters/common" ) diff --git a/pkg/geth/converters/common/log_converter.go b/pkg/geth/converters/common/log_converter.go index 47c0d0e9..11dce787 100644 --- a/pkg/geth/converters/common/log_converter.go +++ b/pkg/geth/converters/common/log_converter.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package common import ( diff --git a/pkg/geth/converters/common/log_converter_test.go b/pkg/geth/converters/common/log_converter_test.go index b6e37385..d382aa25 100644 --- a/pkg/geth/converters/common/log_converter_test.go +++ b/pkg/geth/converters/common/log_converter_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package common_test import ( diff --git a/pkg/geth/converters/common/receipt_converter.go b/pkg/geth/converters/common/receipt_converter.go index aff58fa1..5d558948 100644 --- a/pkg/geth/converters/common/receipt_converter.go +++ b/pkg/geth/converters/common/receipt_converter.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package common import ( diff --git a/pkg/geth/converters/common/receipt_converter_test.go b/pkg/geth/converters/common/receipt_converter_test.go index 08a7c5eb..b43b6152 100644 --- a/pkg/geth/converters/common/receipt_converter_test.go +++ b/pkg/geth/converters/common/receipt_converter_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package common_test import ( diff --git a/pkg/geth/converters/common/transaction_converter.go b/pkg/geth/converters/common/transaction_converter.go index 6bc1a8fc..320dc975 100644 --- a/pkg/geth/converters/common/transaction_converter.go +++ b/pkg/geth/converters/common/transaction_converter.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package common import ( diff --git a/pkg/geth/converters/rpc/transaction_converter.go b/pkg/geth/converters/rpc/transaction_converter.go index c1dda565..d741f908 100644 --- a/pkg/geth/converters/rpc/transaction_converter.go +++ b/pkg/geth/converters/rpc/transaction_converter.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package rpc import ( diff --git a/pkg/geth/geth_suite_test.go b/pkg/geth/geth_suite_test.go index 2d293f6c..c4078ffc 100644 --- a/pkg/geth/geth_suite_test.go +++ b/pkg/geth/geth_suite_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package geth_test import ( diff --git a/pkg/geth/node/node.go b/pkg/geth/node/node.go index ae70ada0..7c276097 100644 --- a/pkg/geth/node/node.go +++ b/pkg/geth/node/node.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package node import ( diff --git a/pkg/geth/node/node_suite_test.go b/pkg/geth/node/node_suite_test.go index 102033dc..27139341 100644 --- a/pkg/geth/node/node_suite_test.go +++ b/pkg/geth/node/node_suite_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package node_test import ( diff --git a/pkg/geth/node/node_test.go b/pkg/geth/node/node_test.go index c63214ca..58ad5ca9 100644 --- a/pkg/geth/node/node_test.go +++ b/pkg/geth/node/node_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package node_test import ( diff --git a/pkg/geth/testing/helpers.go b/pkg/geth/testing/helpers.go index ae289962..1a612703 100644 --- a/pkg/geth/testing/helpers.go +++ b/pkg/geth/testing/helpers.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package testing import ( diff --git a/pkg/history/block_validator.go b/pkg/history/block_validator.go index 4f98cbca..6f7791a2 100644 --- a/pkg/history/block_validator.go +++ b/pkg/history/block_validator.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package history import ( diff --git a/pkg/history/block_validator_test.go b/pkg/history/block_validator_test.go index e05e6501..3509e20e 100644 --- a/pkg/history/block_validator_test.go +++ b/pkg/history/block_validator_test.go @@ -1,12 +1,29 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package history_test import ( + "math/big" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/vulcanize/vulcanizedb/pkg/fakes" "github.com/vulcanize/vulcanizedb/pkg/history" - "math/big" ) var _ = Describe("Blocks validator", func() { diff --git a/pkg/history/header_validator.go b/pkg/history/header_validator.go index 5d10eab0..c475473d 100644 --- a/pkg/history/header_validator.go +++ b/pkg/history/header_validator.go @@ -1,7 +1,24 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package history import ( log "github.com/sirupsen/logrus" + "github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/datastore" ) diff --git a/pkg/history/header_validator_test.go b/pkg/history/header_validator_test.go index 937c04ef..674c4ab6 100644 --- a/pkg/history/header_validator_test.go +++ b/pkg/history/header_validator_test.go @@ -1,12 +1,30 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package history_test import ( "errors" + "math/big" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/vulcanize/vulcanizedb/pkg/fakes" "github.com/vulcanize/vulcanizedb/pkg/history" - "math/big" ) var _ = Describe("Header validator", func() { diff --git a/pkg/history/history_suite_test.go b/pkg/history/history_suite_test.go index 96068ec7..3e99e488 100644 --- a/pkg/history/history_suite_test.go +++ b/pkg/history/history_suite_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package history_test import ( diff --git a/pkg/history/populate_blocks.go b/pkg/history/populate_blocks.go index 884e5521..72f38840 100644 --- a/pkg/history/populate_blocks.go +++ b/pkg/history/populate_blocks.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package history import ( diff --git a/pkg/history/populate_blocks_test.go b/pkg/history/populate_blocks_test.go index e80687a3..5ff67a13 100644 --- a/pkg/history/populate_blocks_test.go +++ b/pkg/history/populate_blocks_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package history_test import ( diff --git a/pkg/history/populate_headers.go b/pkg/history/populate_headers.go index 1339e9e0..499dcb86 100644 --- a/pkg/history/populate_headers.go +++ b/pkg/history/populate_headers.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package history import ( @@ -44,6 +60,5 @@ func RetrieveAndUpdateHeaders(chain core.BlockChain, headerRepository datastore. return 0, err } } - return len(blockNumbers), nil } diff --git a/pkg/history/populate_headers_test.go b/pkg/history/populate_headers_test.go index b2bd569b..6b06fbb4 100644 --- a/pkg/history/populate_headers_test.go +++ b/pkg/history/populate_headers_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package history_test import ( diff --git a/pkg/history/validation_window.go b/pkg/history/validation_window.go index e10253c3..fb843907 100644 --- a/pkg/history/validation_window.go +++ b/pkg/history/validation_window.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package history import ( diff --git a/pkg/history/validation_window_test.go b/pkg/history/validation_window_test.go index 354af33e..55434f34 100644 --- a/pkg/history/validation_window_test.go +++ b/pkg/history/validation_window_test.go @@ -1,3 +1,19 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package history_test import ( diff --git a/pkg/omni/full/converter/converter.go b/pkg/omni/full/converter/converter.go new file mode 100644 index 00000000..dd5b9725 --- /dev/null +++ b/pkg/omni/full/converter/converter.go @@ -0,0 +1,122 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package converter + +import ( + "errors" + "fmt" + "math/big" + "strconv" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" +) + +// Converter is used to convert watched event logs to +// custom logs containing event input name => value maps +type Converter interface { + Convert(watchedEvent core.WatchedEvent, event types.Event) (*types.Log, error) + Update(info *contract.Contract) +} + +type converter struct { + ContractInfo *contract.Contract +} + +func NewConverter(info *contract.Contract) *converter { + return &converter{ + ContractInfo: info, + } +} + +func (c *converter) Update(info *contract.Contract) { + c.ContractInfo = info +} + +// Convert the given watched event log into a types.Log for the given event +func (c *converter) Convert(watchedEvent core.WatchedEvent, event types.Event) (*types.Log, error) { + contract := bind.NewBoundContract(common.HexToAddress(c.ContractInfo.Address), c.ContractInfo.ParsedAbi, nil, nil, nil) + values := make(map[string]interface{}) + log := helpers.ConvertToLog(watchedEvent) + err := contract.UnpackLogIntoMap(values, event.Name, log) + if err != nil { + return nil, err + } + + strValues := make(map[string]string, len(values)) + seenAddrs := make([]interface{}, 0, len(values)) + seenHashes := make([]interface{}, 0, len(values)) + for fieldName, input := range values { + // Postgres cannot handle custom types, resolve to strings + switch input.(type) { + case *big.Int: + b := input.(*big.Int) + strValues[fieldName] = b.String() + case common.Address: + a := input.(common.Address) + strValues[fieldName] = a.String() + seenAddrs = append(seenAddrs, a) + case common.Hash: + h := input.(common.Hash) + strValues[fieldName] = h.String() + seenHashes = append(seenHashes, h) + case string: + strValues[fieldName] = input.(string) + case bool: + strValues[fieldName] = strconv.FormatBool(input.(bool)) + case []byte: + b := input.([]byte) + strValues[fieldName] = hexutil.Encode(b) + if len(b) == 32 { // collect byte arrays of size 32 as hashes + seenHashes = append(seenHashes, common.HexToHash(strValues[fieldName])) + } + case byte: + b := input.(byte) + strValues[fieldName] = string(b) + default: + return nil, errors.New(fmt.Sprintf("error: unhandled abi type %T", input)) + } + } + + // Only hold onto logs that pass our address filter, if any + if c.ContractInfo.PassesEventFilter(strValues) { + eventLog := &types.Log{ + Id: watchedEvent.LogID, + Values: strValues, + Block: watchedEvent.BlockNumber, + Tx: watchedEvent.TxHash, + } + + // Cache emitted values if their caching is turned on + if c.ContractInfo.EmittedAddrs != nil { + c.ContractInfo.AddEmittedAddr(seenAddrs...) + } + if c.ContractInfo.EmittedHashes != nil { + c.ContractInfo.AddEmittedHash(seenHashes...) + } + + return eventLog, nil + } + + return nil, nil +} diff --git a/pkg/omni/full/converter/converter_suite_test.go b/pkg/omni/full/converter/converter_suite_test.go new file mode 100644 index 00000000..93fd8aef --- /dev/null +++ b/pkg/omni/full/converter/converter_suite_test.go @@ -0,0 +1,35 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package converter_test + +import ( + "io/ioutil" + "log" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestConverter(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Full Converter Suite Test") +} + +var _ = BeforeSuite(func() { + log.SetOutput(ioutil.Discard) +}) diff --git a/pkg/omni/full/converter/converter_test.go b/pkg/omni/full/converter/converter_test.go new file mode 100644 index 00000000..e35780b8 --- /dev/null +++ b/pkg/omni/full/converter/converter_test.go @@ -0,0 +1,109 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package converter_test + +import ( + "github.com/ethereum/go-ethereum/common" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/omni/full/converter" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks" +) + +var _ = Describe("Converter", func() { + var con *contract.Contract + var wantedEvents = []string{"Transfer"} + var err error + + BeforeEach(func() { + con = test_helpers.SetupTusdContract(wantedEvents, []string{"balanceOf"}) + }) + + Describe("Update", func() { + It("Updates contract con held by the converter", func() { + c := converter.NewConverter(con) + Expect(c.ContractInfo).To(Equal(con)) + + con := test_helpers.SetupTusdContract([]string{}, []string{}) + c.Update(con) + Expect(c.ContractInfo).To(Equal(con)) + }) + }) + + Describe("Convert", func() { + It("Converts a watched event log to mapping of event input names to values", func() { + _, ok := con.Events["Approval"] + Expect(ok).To(Equal(false)) + + event, ok := con.Events["Transfer"] + Expect(ok).To(Equal(true)) + err = con.GenerateFilters() + Expect(err).ToNot(HaveOccurred()) + + c := converter.NewConverter(con) + log, err := c.Convert(mocks.MockTranferEvent, event) + Expect(err).ToNot(HaveOccurred()) + + from := common.HexToAddress("0x000000000000000000000000000000000000000000000000000000000000af21") + to := common.HexToAddress("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391") + value := helpers.BigFromString("1097077688018008265106216665536940668749033598146") + + v := log.Values["value"] + + Expect(log.Values["to"]).To(Equal(to.String())) + Expect(log.Values["from"]).To(Equal(from.String())) + Expect(v).To(Equal(value.String())) + }) + + It("Keeps track of addresses it sees to grow a token holder address list for the contract", func() { + event, ok := con.Events["Transfer"] + Expect(ok).To(Equal(true)) + + c := converter.NewConverter(con) + _, err := c.Convert(mocks.MockTranferEvent, event) + Expect(err).ToNot(HaveOccurred()) + + b, ok := con.EmittedAddrs[common.HexToAddress("0x000000000000000000000000000000000000Af21")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + b, ok = con.EmittedAddrs[common.HexToAddress("0x09BbBBE21a5975cAc061D82f7b843bCE061BA391")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + _, ok = con.EmittedAddrs[common.HexToAddress("0x")] + Expect(ok).To(Equal(false)) + + _, ok = con.EmittedAddrs[""] + Expect(ok).To(Equal(false)) + + _, ok = con.EmittedAddrs[common.HexToAddress("0x09THISE21a5IS5cFAKE1D82fAND43bCE06MADEUP")] + Expect(ok).To(Equal(false)) + }) + + It("Fails with an empty contract", func() { + event := con.Events["Transfer"] + c := converter.NewConverter(&contract.Contract{}) + _, err = c.Convert(mocks.MockTranferEvent, event) + Expect(err).To(HaveOccurred()) + }) + }) +}) diff --git a/pkg/omni/full/retriever/block_retriever.go b/pkg/omni/full/retriever/block_retriever.go new file mode 100644 index 00000000..f4c3ac5d --- /dev/null +++ b/pkg/omni/full/retriever/block_retriever.go @@ -0,0 +1,87 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package retriever + +import ( + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" +) + +// Block retriever is used to retrieve the first block for a given contract and the most recent block +// It requires a vDB synced database with blocks, transactions, receipts, and logs +type BlockRetriever interface { + RetrieveFirstBlock(contractAddr string) (int64, error) + RetrieveMostRecentBlock() (int64, error) +} + +type blockRetriever struct { + db *postgres.DB +} + +func NewBlockRetriever(db *postgres.DB) (r *blockRetriever) { + return &blockRetriever{ + db: db, + } +} + +// Try both methods of finding the first block, with the receipt method taking precedence +func (r *blockRetriever) RetrieveFirstBlock(contractAddr string) (int64, error) { + i, err := r.retrieveFirstBlockFromReceipts(contractAddr) + if err != nil { + i, err = r.retrieveFirstBlockFromLogs(contractAddr) + } + + return i, err +} + +// For some contracts the contract creation transaction receipt doesn't have the contract address so this doesn't work (e.g. Sai) +func (r *blockRetriever) retrieveFirstBlockFromReceipts(contractAddr string) (int64, error) { + var firstBlock int + err := r.db.Get( + &firstBlock, + `SELECT number FROM blocks + WHERE id = (SELECT block_id FROM receipts + WHERE lower(contract_address) = $1 + ORDER BY block_id ASC + LIMIT 1)`, + contractAddr, + ) + + return int64(firstBlock), err +} + +// In which case this servers as a heuristic to find the first block by finding the first contract event log +func (r *blockRetriever) retrieveFirstBlockFromLogs(contractAddr string) (int64, error) { + var firstBlock int + err := r.db.Get( + &firstBlock, + "SELECT block_number FROM logs WHERE lower(address) = $1 ORDER BY block_number ASC LIMIT 1", + contractAddr, + ) + + return int64(firstBlock), err +} + +// Method to retrieve the most recent block in vDB +func (r *blockRetriever) RetrieveMostRecentBlock() (int64, error) { + var lastBlock int64 + err := r.db.Get( + &lastBlock, + "SELECT number FROM blocks ORDER BY number DESC LIMIT 1", + ) + + return lastBlock, err +} diff --git a/pkg/omni/full/retriever/block_retriever_test.go b/pkg/omni/full/retriever/block_retriever_test.go new file mode 100644 index 00000000..caa1a1ed --- /dev/null +++ b/pkg/omni/full/retriever/block_retriever_test.go @@ -0,0 +1,218 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package retriever_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "strings" + + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories" + "github.com/vulcanize/vulcanizedb/pkg/omni/full/retriever" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers" +) + +var _ = Describe("Block Retriever", func() { + var db *postgres.DB + var r retriever.BlockRetriever + var blockRepository repositories.BlockRepository + + // Contains no contract address + var block1 = core.Block{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad123ert", + Number: 1, + Transactions: []core.Transaction{}, + } + + BeforeEach(func() { + db, _ = test_helpers.SetupDBandBC() + blockRepository = *repositories.NewBlockRepository(db) + r = retriever.NewBlockRetriever(db) + }) + + AfterEach(func() { + test_helpers.TearDown(db) + }) + + Describe("RetrieveFirstBlock", func() { + It("Retrieves block number where contract first appears in receipt, if available", func() { + + // Contains the address in the receipt + block2 := core.Block{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad123ert", + Number: 2, + Transactions: []core.Transaction{{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae", + Receipt: core.Receipt{ + TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae", + ContractAddress: constants.TusdContractAddress, + Logs: []core.Log{}, + }, + }}, + } + + // Contains address in logs + block3 := core.Block{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad456yui", + Number: 3, + Transactions: []core.Transaction{{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad234hfs", + Receipt: core.Receipt{ + TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad234hfs", + ContractAddress: constants.TusdContractAddress, + Logs: []core.Log{{ + BlockNumber: 3, + TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad234hfs", + Address: constants.TusdContractAddress, + Topics: core.Topics{ + constants.TransferEvent.Signature(), + "0x000000000000000000000000000000000000000000000000000000000000af21", + "0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391", + "", + }, + Index: 1, + Data: "0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe", + }}, + }, + }}, + } + + blockRepository.CreateOrUpdateBlock(block1) + blockRepository.CreateOrUpdateBlock(block2) + blockRepository.CreateOrUpdateBlock(block3) + + i, err := r.RetrieveFirstBlock(strings.ToLower(constants.TusdContractAddress)) + Expect(err).NotTo(HaveOccurred()) + Expect(i).To(Equal(int64(2))) + }) + + It("Retrieves block number where contract first appears in event logs if it cannot find the address in a receipt", func() { + + block2 := core.Block{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad123ert", + Number: 2, + Transactions: []core.Transaction{{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae", + Receipt: core.Receipt{ + TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae", + ContractAddress: "", + Logs: []core.Log{{ + BlockNumber: 2, + TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae", + Address: constants.DaiContractAddress, + Topics: core.Topics{ + constants.TransferEvent.Signature(), + "0x000000000000000000000000000000000000000000000000000000000000af21", + "0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391", + "", + }, + Index: 1, + Data: "0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe", + }}, + }, + }}, + } + + block3 := core.Block{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad456yui", + Number: 3, + Transactions: []core.Transaction{{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad234hfs", + Receipt: core.Receipt{ + TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad234hfs", + ContractAddress: "", + Logs: []core.Log{{ + BlockNumber: 3, + TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad234hfs", + Address: constants.DaiContractAddress, + Topics: core.Topics{ + constants.TransferEvent.Signature(), + "0x000000000000000000000000000000000000000000000000000000000000af21", + "0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391", + "", + }, + Index: 1, + Data: "0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe", + }}, + }, + }}, + } + + blockRepository.CreateOrUpdateBlock(block1) + blockRepository.CreateOrUpdateBlock(block2) + blockRepository.CreateOrUpdateBlock(block3) + + i, err := r.RetrieveFirstBlock(constants.DaiContractAddress) + Expect(err).NotTo(HaveOccurred()) + Expect(i).To(Equal(int64(2))) + }) + + It("Fails if the contract address cannot be found in any blocks", func() { + block2 := core.Block{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad123ert", + Number: 2, + Transactions: []core.Transaction{}, + } + + block3 := core.Block{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad456yui", + Number: 3, + Transactions: []core.Transaction{}, + } + + blockRepository.CreateOrUpdateBlock(block1) + blockRepository.CreateOrUpdateBlock(block2) + blockRepository.CreateOrUpdateBlock(block3) + + _, err := r.RetrieveFirstBlock(constants.DaiContractAddress) + Expect(err).To(HaveOccurred()) + }) + }) + + Describe("RetrieveMostRecentBlock", func() { + It("Retrieves the latest block", func() { + block2 := core.Block{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad123ert", + Number: 2, + Transactions: []core.Transaction{}, + } + + block3 := core.Block{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad456yui", + Number: 3, + Transactions: []core.Transaction{}, + } + + blockRepository.CreateOrUpdateBlock(block1) + blockRepository.CreateOrUpdateBlock(block2) + blockRepository.CreateOrUpdateBlock(block3) + + i, err := r.RetrieveMostRecentBlock() + Expect(err).ToNot(HaveOccurred()) + Expect(i).To(Equal(int64(3))) + }) + + It("Fails if it cannot retrieve the latest block", func() { + i, err := r.RetrieveMostRecentBlock() + Expect(err).To(HaveOccurred()) + Expect(i).To(Equal(int64(0))) + }) + }) +}) diff --git a/pkg/omni/full/retriever/retriever_suite_test.go b/pkg/omni/full/retriever/retriever_suite_test.go new file mode 100644 index 00000000..948f5e8e --- /dev/null +++ b/pkg/omni/full/retriever/retriever_suite_test.go @@ -0,0 +1,35 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package retriever_test + +import ( + "io/ioutil" + "log" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestRetriever(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Full Block Number Retriever Suite Test") +} + +var _ = BeforeSuite(func() { + log.SetOutput(ioutil.Discard) +}) diff --git a/pkg/omni/full/transformer/transformer.go b/pkg/omni/full/transformer/transformer.go new file mode 100644 index 00000000..5c8a4337 --- /dev/null +++ b/pkg/omni/full/transformer/transformer.go @@ -0,0 +1,271 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package transformer + +import ( + "errors" + "strings" + + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories" + "github.com/vulcanize/vulcanizedb/pkg/omni/full/converter" + "github.com/vulcanize/vulcanizedb/pkg/omni/full/retriever" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/parser" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/poller" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/repository" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" +) + +// Requires a fully synced vDB and a running eth node (or infura) +type transformer struct { + // Database interfaces + datastore.FilterRepository // Log filters repo; accepts filters generated by Contract.GenerateFilters() + datastore.WatchedEventRepository // Watched event log views, created by the log filters + repository.EventRepository // Holds transformed watched event log data + + // Pre-processing interfaces + parser.Parser // Parses events and methods out of contract abi fetched using contract address + retriever.BlockRetriever // Retrieves first block for contract and current block height + + // Processing interfaces + converter.Converter // Converts watched event logs into custom log + poller.Poller // Polls methods using contract's token holder addresses and persists them using method datastore + + // Ethereum network name; default "" is mainnet + Network string + + // Store contract info as mapping to contract address + Contracts map[string]*contract.Contract + + // Targeted subset of events/methods + // Stored as map sof contract address to events/method names of interest + WatchedEvents map[string][]string // Default/empty event list means all are watched + WantedMethods map[string][]string // Default/empty method list means none are polled + + // Starting block for contracts + ContractStart map[string]int64 + + // Lists of addresses to filter event or method data + // before persisting; if empty no filter is applied + EventArgs map[string][]string + MethodArgs map[string][]string + + // Whether or not to create a list of emitted address or hashes for the contract in postgres + CreateAddrList map[string]bool + CreateHashList map[string]bool + + // Method piping on/off for a contract + Piping map[string]bool +} + +// Transformer takes in config for blockchain, database, and network id +func NewTransformer(network string, BC core.BlockChain, DB *postgres.DB) *transformer { + return &transformer{ + Poller: poller.NewPoller(BC, DB, types.FullSync), + Parser: parser.NewParser(network), + BlockRetriever: retriever.NewBlockRetriever(DB), + Converter: converter.NewConverter(&contract.Contract{}), + Contracts: map[string]*contract.Contract{}, + WatchedEventRepository: repositories.WatchedEventRepository{DB: DB}, + FilterRepository: repositories.FilterRepository{DB: DB}, + EventRepository: repository.NewEventRepository(DB, types.FullSync), + WatchedEvents: map[string][]string{}, + WantedMethods: map[string][]string{}, + ContractStart: map[string]int64{}, + EventArgs: map[string][]string{}, + MethodArgs: map[string][]string{}, + CreateAddrList: map[string]bool{}, + CreateHashList: map[string]bool{}, + Piping: map[string]bool{}, + } +} + +// Use after creating and setting transformer +// Loops over all of the addr => filter sets +// Uses parser to pull event info from abi +// Use this info to generate event filters +func (t *transformer) Init() error { + for contractAddr, subset := range t.WatchedEvents { + // Get Abi + err := t.Parser.Parse(contractAddr) + if err != nil { + return err + } + + // Get first block and most recent block number in the header repo + firstBlock, err := t.BlockRetriever.RetrieveFirstBlock(contractAddr) + if err != nil { + return err + } + lastBlock, err := t.BlockRetriever.RetrieveMostRecentBlock() + if err != nil { + return err + } + + // Set to specified range if it falls within the bounds + if firstBlock < t.ContractStart[contractAddr] { + firstBlock = t.ContractStart[contractAddr] + } + + // Get contract name if it has one + var name = new(string) + t.FetchContractData(t.Abi(), contractAddr, "name", nil, &name, lastBlock) + + // Remove any potential accidental duplicate inputs in arg filter values + eventArgs := map[string]bool{} + for _, arg := range t.EventArgs[contractAddr] { + eventArgs[arg] = true + } + methodArgs := map[string]bool{} + for _, arg := range t.MethodArgs[contractAddr] { + methodArgs[arg] = true + } + + // Aggregate info into contract object + info := contract.Contract{ + Name: *name, + Network: t.Network, + Address: contractAddr, + Abi: t.Parser.Abi(), + ParsedAbi: t.Parser.ParsedAbi(), + StartingBlock: firstBlock, + LastBlock: lastBlock, + Events: t.Parser.GetEvents(subset), + Methods: t.Parser.GetSelectMethods(t.WantedMethods[contractAddr]), + FilterArgs: eventArgs, + MethodArgs: methodArgs, + CreateAddrList: t.CreateAddrList[contractAddr], + CreateHashList: t.CreateHashList[contractAddr], + Piping: t.Piping[contractAddr], + }.Init() + + // Use info to create filters + err = info.GenerateFilters() + if err != nil { + return err + } + + // Iterate over filters and push them to the repo using filter repository interface + for _, filter := range info.Filters { + err = t.CreateFilter(filter) + if err != nil { + return err + } + } + + // Store contract info for further processing + t.Contracts[contractAddr] = info + } + + return nil +} + +// Iterates through stored, initialized contract objects +// Iterates through contract's event filters, grabbing watched event logs +// Uses converter to convert logs into custom log type +// Persists converted logs into custuom postgres tables +// Calls selected methods, using token holder address generated during event log conversion +func (tr transformer) Execute() error { + if len(tr.Contracts) == 0 { + return errors.New("error: transformer has no initialized contracts to work with") + } + // Iterate through all internal contracts + for _, con := range tr.Contracts { + // Update converter with current contract + tr.Update(con) + + // Iterate through contract filters and get watched event logs + for eventSig, filter := range con.Filters { + watchedEvents, err := tr.GetWatchedEvents(filter.Name) + if err != nil { + return err + } + + // Iterate over watched event logs + for _, we := range watchedEvents { + // Convert them to our custom log type + cstm, err := tr.Converter.Convert(*we, con.Events[eventSig]) + if err != nil { + return err + } + if cstm == nil { + continue + } + + // If log is not empty, immediately persist in repo + // Run this in seperate goroutine? + err = tr.PersistLogs([]types.Log{*cstm}, con.Events[eventSig], con.Address, con.Name) + if err != nil { + return err + } + } + } + + // After persisting all watched event logs + // poller polls select contract methods + // and persists the results into custom pg tables + // Run this in seperate goroutine? + if err := tr.PollContract(*con); err != nil { + return err + } + } + + return nil +} + +// Used to set which contract addresses and which of their events to watch +func (tr *transformer) SetEvents(contractAddr string, filterSet []string) { + tr.WatchedEvents[strings.ToLower(contractAddr)] = filterSet +} + +// Used to set subset of account addresses to watch events for +func (tr *transformer) SetEventArgs(contractAddr string, filterSet []string) { + tr.EventArgs[strings.ToLower(contractAddr)] = filterSet +} + +// Used to set which contract addresses and which of their methods to call +func (tr *transformer) SetMethods(contractAddr string, filterSet []string) { + tr.WantedMethods[strings.ToLower(contractAddr)] = filterSet +} + +// Used to set subset of account addresses to poll methods on +func (tr *transformer) SetMethodArgs(contractAddr string, filterSet []string) { + tr.MethodArgs[strings.ToLower(contractAddr)] = filterSet +} + +// Used to set the block range to watch for a given address +func (tr *transformer) SetStartingBlock(contractAddr string, start int64) { + tr.ContractStart[strings.ToLower(contractAddr)] = start +} + +// Used to set whether or not to persist an account address list +func (tr *transformer) SetCreateAddrList(contractAddr string, on bool) { + tr.CreateAddrList[strings.ToLower(contractAddr)] = on +} + +// Used to set whether or not to persist an hash list +func (tr *transformer) SetCreateHashList(contractAddr string, on bool) { + tr.CreateHashList[strings.ToLower(contractAddr)] = on +} + +// Used to turn method piping on for a contract +func (tr *transformer) SetPiping(contractAddr string, on bool) { + tr.Piping[strings.ToLower(contractAddr)] = on +} diff --git a/pkg/omni/full/transformer/transformer_suite_test.go b/pkg/omni/full/transformer/transformer_suite_test.go new file mode 100644 index 00000000..1f362f2b --- /dev/null +++ b/pkg/omni/full/transformer/transformer_suite_test.go @@ -0,0 +1,35 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package transformer_test + +import ( + "io/ioutil" + "log" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestTransformer(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Full Transformer Suite Test") +} + +var _ = BeforeSuite(func() { + log.SetOutput(ioutil.Discard) +}) diff --git a/pkg/omni/full/transformer/transformer_test.go b/pkg/omni/full/transformer/transformer_test.go new file mode 100644 index 00000000..9d68143c --- /dev/null +++ b/pkg/omni/full/transformer/transformer_test.go @@ -0,0 +1,369 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package transformer_test + +import ( + "fmt" + "math/rand" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories" + "github.com/vulcanize/vulcanizedb/pkg/omni/full/transformer" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks" +) + +var _ = Describe("Transformer", func() { + var db *postgres.DB + var err error + var blockChain core.BlockChain + var blockRepository repositories.BlockRepository + var ensAddr = strings.ToLower(constants.EnsContractAddress) + var tusdAddr = strings.ToLower(constants.TusdContractAddress) + rand.Seed(time.Now().UnixNano()) + + BeforeEach(func() { + db, blockChain = test_helpers.SetupDBandBC() + blockRepository = *repositories.NewBlockRepository(db) + }) + + AfterEach(func() { + test_helpers.TearDown(db) + }) + + Describe("SetEvents", func() { + It("Sets which events to watch from the given contract address", func() { + watchedEvents := []string{"Transfer", "Mint"} + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.TusdContractAddress, watchedEvents) + Expect(t.WatchedEvents[tusdAddr]).To(Equal(watchedEvents)) + }) + }) + + Describe("SetEventAddrs", func() { + It("Sets which account addresses to watch events for", func() { + eventAddrs := []string{"test1", "test2"} + t := transformer.NewTransformer("", blockChain, db) + t.SetEventArgs(constants.TusdContractAddress, eventAddrs) + Expect(t.EventArgs[tusdAddr]).To(Equal(eventAddrs)) + }) + }) + + Describe("SetMethods", func() { + It("Sets which methods to poll at the given contract address", func() { + watchedMethods := []string{"balanceOf", "totalSupply"} + t := transformer.NewTransformer("", blockChain, db) + t.SetMethods(constants.TusdContractAddress, watchedMethods) + Expect(t.WantedMethods[tusdAddr]).To(Equal(watchedMethods)) + }) + }) + + Describe("SetMethodAddrs", func() { + It("Sets which account addresses to poll methods against", func() { + methodAddrs := []string{"test1", "test2"} + t := transformer.NewTransformer("", blockChain, db) + t.SetMethodArgs(constants.TusdContractAddress, methodAddrs) + Expect(t.MethodArgs[tusdAddr]).To(Equal(methodAddrs)) + }) + }) + + Describe("SetStartingBlock", func() { + It("Sets the block range that the contract should be watched within", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetStartingBlock(constants.TusdContractAddress, 11) + Expect(t.ContractStart[tusdAddr]).To(Equal(int64(11))) + }) + }) + + Describe("SetCreateAddrList", func() { + It("Sets the block range that the contract should be watched within", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetCreateAddrList(constants.TusdContractAddress, true) + Expect(t.CreateAddrList[tusdAddr]).To(Equal(true)) + }) + }) + + Describe("SetCreateHashList", func() { + It("Sets the block range that the contract should be watched within", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetCreateHashList(constants.TusdContractAddress, true) + Expect(t.CreateHashList[tusdAddr]).To(Equal(true)) + }) + }) + + Describe("Init", func() { + It("Initializes transformer's contract objects", func() { + blockRepository.CreateOrUpdateBlock(mocks.TransferBlock1) + blockRepository.CreateOrUpdateBlock(mocks.TransferBlock2) + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + + c, ok := t.Contracts[tusdAddr] + Expect(ok).To(Equal(true)) + + Expect(c.StartingBlock).To(Equal(int64(6194633))) + Expect(c.LastBlock).To(Equal(int64(6194634))) + Expect(c.Abi).To(Equal(constants.TusdAbiString)) + Expect(c.Name).To(Equal("TrueUSD")) + Expect(c.Address).To(Equal(tusdAddr)) + }) + + It("Fails to initialize if first and most recent blocks cannot be fetched from vDB", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) + err = t.Init() + Expect(err).To(HaveOccurred()) + }) + + It("Does nothing if watched events are unset", func() { + blockRepository.CreateOrUpdateBlock(mocks.TransferBlock1) + blockRepository.CreateOrUpdateBlock(mocks.TransferBlock2) + t := transformer.NewTransformer("", blockChain, db) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + + _, ok := t.Contracts[tusdAddr] + Expect(ok).To(Equal(false)) + }) + }) + + Describe("Execute", func() { + BeforeEach(func() { + blockRepository.CreateOrUpdateBlock(mocks.TransferBlock1) + blockRepository.CreateOrUpdateBlock(mocks.TransferBlock2) + }) + + It("Transforms watched contract data into custom repositories", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) + t.SetMethods(constants.TusdContractAddress, nil) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + + log := test_helpers.TransferLog{} + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.transfer_event WHERE block = 6194634", tusdAddr)).StructScan(&log) + + // We don't know vulcID, so compare individual fields instead of complete structures + Expect(log.Tx).To(Equal("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654eee")) + Expect(log.Block).To(Equal(int64(6194634))) + Expect(log.From).To(Equal("0x000000000000000000000000000000000000Af21")) + Expect(log.To).To(Equal("0x09BbBBE21a5975cAc061D82f7b843bCE061BA391")) + Expect(log.Value).To(Equal("1097077688018008265106216665536940668749033598146")) + }) + + It("Keeps track of contract-related addresses while transforming event data if they need to be used for later method polling", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) + t.SetMethods(constants.TusdContractAddress, []string{"balanceOf"}) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + + c, ok := t.Contracts[tusdAddr] + Expect(ok).To(Equal(true)) + + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + + b, ok := c.EmittedAddrs[common.HexToAddress("0x000000000000000000000000000000000000Af21")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + b, ok = c.EmittedAddrs[common.HexToAddress("0x09BbBBE21a5975cAc061D82f7b843bCE061BA391")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + _, ok = c.EmittedAddrs[common.HexToAddress("0x09BbBBE21a5975cAc061D82f7b843b1234567890")] + Expect(ok).To(Equal(false)) + + _, ok = c.EmittedAddrs[common.HexToAddress("0x")] + Expect(ok).To(Equal(false)) + + _, ok = c.EmittedAddrs[""] + Expect(ok).To(Equal(false)) + + _, ok = c.EmittedAddrs[common.HexToAddress("0x09THISE21a5IS5cFAKE1D82fAND43bCE06MADEUP")] + Expect(ok).To(Equal(false)) + }) + + It("Polls given methods using generated token holder address", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) + t.SetMethods(constants.TusdContractAddress, []string{"balanceOf"}) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + + res := test_helpers.BalanceOf{} + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0x000000000000000000000000000000000000Af21' AND block = '6194634'", tusdAddr)).StructScan(&res) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Balance).To(Equal("0")) + Expect(res.TokenName).To(Equal("TrueUSD")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0x09BbBBE21a5975cAc061D82f7b843bCE061BA391' AND block = '6194634'", tusdAddr)).StructScan(&res) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Balance).To(Equal("0")) + Expect(res.TokenName).To(Equal("TrueUSD")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0xfE9e8709d3215310075d67E3ed32A380CCf451C8' AND block = '6194634'", tusdAddr)).StructScan(&res) + Expect(err).To(HaveOccurred()) + }) + + It("Fails if initialization has not been done", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) + t.SetMethods(constants.TusdContractAddress, nil) + + err = t.Execute() + Expect(err).To(HaveOccurred()) + }) + }) + + Describe("Execute- against ENS registry contract", func() { + BeforeEach(func() { + blockRepository.CreateOrUpdateBlock(mocks.NewOwnerBlock1) + blockRepository.CreateOrUpdateBlock(mocks.NewOwnerBlock2) + }) + + It("Transforms watched contract data into custom repositories", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) + t.SetMethods(constants.EnsContractAddress, nil) + + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + + log := test_helpers.NewOwnerLog{} + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.newowner_event", ensAddr)).StructScan(&log) + + // We don't know vulcID, so compare individual fields instead of complete structures + Expect(log.Tx).To(Equal("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654bbb")) + Expect(log.Block).To(Equal(int64(6194635))) + Expect(log.Node).To(Equal("0x0000000000000000000000000000000000000000000000000000c02aaa39b223")) + Expect(log.Label).To(Equal("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391")) + Expect(log.Owner).To(Equal("0x000000000000000000000000000000000000Af21")) + }) + + It("Keeps track of contract-related hashes while transforming event data if they need to be used for later method polling", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) + t.SetMethods(constants.EnsContractAddress, []string{"owner"}) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + + c, ok := t.Contracts[ensAddr] + Expect(ok).To(Equal(true)) + + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + Expect(len(c.EmittedHashes)).To(Equal(3)) + + b, ok := c.EmittedHashes[common.HexToHash("0x0000000000000000000000000000000000000000000000000000c02aaa39b223")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + b, ok = c.EmittedHashes[common.HexToHash("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + // Doesn't keep track of address since it wouldn't be used in calling the 'owner' method + _, ok = c.EmittedAddrs[common.HexToAddress("0x000000000000000000000000000000000000Af21")] + Expect(ok).To(Equal(false)) + }) + + It("Polls given methods using generated token holder address", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) + t.SetMethods(constants.EnsContractAddress, []string{"owner"}) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + + res := test_helpers.Owner{} + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.owner_method WHERE node_ = '0x0000000000000000000000000000000000000000000000000000c02aaa39b223' AND block = '6194636'", ensAddr)).StructScan(&res) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Address).To(Equal("0x0000000000000000000000000000000000000000")) + Expect(res.TokenName).To(Equal("")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.owner_method WHERE node_ = '0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391' AND block = '6194636'", ensAddr)).StructScan(&res) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Address).To(Equal("0x0000000000000000000000000000000000000000")) + Expect(res.TokenName).To(Equal("")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.owner_method WHERE node_ = '0x95832c7a47ff8a7840e28b78ceMADEUPaaf4HASHc186badTHIS288IS625bFAKE' AND block = '6194636'", ensAddr)).StructScan(&res) + Expect(err).To(HaveOccurred()) + }) + + It("It does not perist events if they do not pass the emitted arg filter", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) + t.SetMethods(constants.EnsContractAddress, nil) + t.SetEventArgs(constants.EnsContractAddress, []string{"fake_filter_value"}) + + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + + log := test_helpers.LightNewOwnerLog{} + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.newowner_event", ensAddr)).StructScan(&log) + Expect(err).To(HaveOccurred()) + }) + + It("If a method arg filter is applied, only those arguments are used in polling", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) + t.SetMethods(constants.EnsContractAddress, []string{"owner"}) + t.SetMethodArgs(constants.EnsContractAddress, []string{"0x0000000000000000000000000000000000000000000000000000c02aaa39b223"}) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + + res := test_helpers.Owner{} + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.owner_method WHERE node_ = '0x0000000000000000000000000000000000000000000000000000c02aaa39b223' AND block = '6194636'", ensAddr)).StructScan(&res) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Address).To(Equal("0x0000000000000000000000000000000000000000")) + Expect(res.TokenName).To(Equal("")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.owner_method WHERE node_ = '0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391' AND block = '6194636'", ensAddr)).StructScan(&res) + Expect(err).To(HaveOccurred()) + }) + }) +}) diff --git a/pkg/omni/light/converter/converter.go b/pkg/omni/light/converter/converter.go new file mode 100644 index 00000000..3deba288 --- /dev/null +++ b/pkg/omni/light/converter/converter.go @@ -0,0 +1,213 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package converter + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "strconv" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + gethTypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" +) + +type Converter interface { + Convert(logs []gethTypes.Log, event types.Event, headerID int64) ([]types.Log, error) + ConvertBatch(logs []gethTypes.Log, events map[string]types.Event, headerID int64) (map[string][]types.Log, error) + Update(info *contract.Contract) +} + +type converter struct { + ContractInfo *contract.Contract +} + +func NewConverter(info *contract.Contract) *converter { + return &converter{ + ContractInfo: info, + } +} + +func (c *converter) Update(info *contract.Contract) { + c.ContractInfo = info +} + +// Convert the given watched event log into a types.Log for the given event +func (c *converter) Convert(logs []gethTypes.Log, event types.Event, headerID int64) ([]types.Log, error) { + contract := bind.NewBoundContract(common.HexToAddress(c.ContractInfo.Address), c.ContractInfo.ParsedAbi, nil, nil, nil) + returnLogs := make([]types.Log, 0, len(logs)) + for _, log := range logs { + values := make(map[string]interface{}) + for _, field := range event.Fields { + var i interface{} + values[field.Name] = i + } + + err := contract.UnpackLogIntoMap(values, event.Name, log) + if err != nil { + return nil, err + } + + strValues := make(map[string]string, len(values)) + seenAddrs := make([]interface{}, 0, len(values)) + seenHashes := make([]interface{}, 0, len(values)) + for fieldName, input := range values { + // Postgres cannot handle custom types, resolve everything to strings + switch input.(type) { + case *big.Int: + b := input.(*big.Int) + strValues[fieldName] = b.String() + case common.Address: + a := input.(common.Address) + strValues[fieldName] = a.String() + seenAddrs = append(seenAddrs, a) + case common.Hash: + h := input.(common.Hash) + strValues[fieldName] = h.String() + seenHashes = append(seenHashes, h) + case string: + strValues[fieldName] = input.(string) + case bool: + strValues[fieldName] = strconv.FormatBool(input.(bool)) + case []byte: + b := input.([]byte) + strValues[fieldName] = hexutil.Encode(b) + if len(b) == 32 { + seenHashes = append(seenHashes, common.HexToHash(strValues[fieldName])) + } + case byte: + b := input.(byte) + strValues[fieldName] = string(b) + default: + return nil, errors.New(fmt.Sprintf("error: unhandled abi type %T", input)) + } + } + + // Only hold onto logs that pass our address filter, if any + if c.ContractInfo.PassesEventFilter(strValues) { + raw, err := json.Marshal(log) + if err != nil { + return nil, err + } + + returnLogs = append(returnLogs, types.Log{ + LogIndex: log.Index, + Values: strValues, + Raw: raw, + TransactionIndex: log.TxIndex, + Id: headerID, + }) + + // Cache emitted values if their caching is turned on + if c.ContractInfo.EmittedAddrs != nil { + c.ContractInfo.AddEmittedAddr(seenAddrs...) + } + if c.ContractInfo.EmittedHashes != nil { + c.ContractInfo.AddEmittedHash(seenHashes...) + } + } + } + + return returnLogs, nil +} + +// Convert the given watched event logs into types.Logs; returns a map of event names to a slice of their converted logs +func (c *converter) ConvertBatch(logs []gethTypes.Log, events map[string]types.Event, headerID int64) (map[string][]types.Log, error) { + contract := bind.NewBoundContract(common.HexToAddress(c.ContractInfo.Address), c.ContractInfo.ParsedAbi, nil, nil, nil) + eventsToLogs := make(map[string][]types.Log) + for _, event := range events { + eventsToLogs[event.Name] = make([]types.Log, 0, len(logs)) + // Iterate through all event logs + for _, log := range logs { + // If the log is of this event type, process it as such + if event.Sig() == log.Topics[0] { + values := make(map[string]interface{}) + err := contract.UnpackLogIntoMap(values, event.Name, log) + if err != nil { + return nil, err + } + // Postgres cannot handle custom types, so we will resolve everything to strings + strValues := make(map[string]string, len(values)) + // Keep track of addresses and hashes emitted from events + seenAddrs := make([]interface{}, 0, len(values)) + seenHashes := make([]interface{}, 0, len(values)) + for fieldName, input := range values { + switch input.(type) { + case *big.Int: + b := input.(*big.Int) + strValues[fieldName] = b.String() + case common.Address: + a := input.(common.Address) + strValues[fieldName] = a.String() + seenAddrs = append(seenAddrs, a) + case common.Hash: + h := input.(common.Hash) + strValues[fieldName] = h.String() + seenHashes = append(seenHashes, h) + case string: + strValues[fieldName] = input.(string) + case bool: + strValues[fieldName] = strconv.FormatBool(input.(bool)) + case []byte: + b := input.([]byte) + strValues[fieldName] = hexutil.Encode(b) + if len(b) == 32 { // collect byte arrays of size 32 as hashes + seenHashes = append(seenHashes, common.BytesToHash(b)) + } + case byte: + b := input.(byte) + strValues[fieldName] = string(b) + default: + return nil, errors.New(fmt.Sprintf("error: unhandled abi type %T", input)) + } + } + + // Only hold onto logs that pass our argument filter, if any + if c.ContractInfo.PassesEventFilter(strValues) { + raw, err := json.Marshal(log) + if err != nil { + return nil, err + } + + eventsToLogs[event.Name] = append(eventsToLogs[event.Name], types.Log{ + LogIndex: log.Index, + Values: strValues, + Raw: raw, + TransactionIndex: log.TxIndex, + Id: headerID, + }) + + // Cache emitted values that pass the argument filter if their caching is turned on + if c.ContractInfo.EmittedAddrs != nil { + c.ContractInfo.AddEmittedAddr(seenAddrs...) + } + if c.ContractInfo.EmittedHashes != nil { + c.ContractInfo.AddEmittedHash(seenHashes...) + } + } + } + } + } + + return eventsToLogs, nil +} diff --git a/pkg/omni/light/converter/converter_suite_test.go b/pkg/omni/light/converter/converter_suite_test.go new file mode 100644 index 00000000..d69367df --- /dev/null +++ b/pkg/omni/light/converter/converter_suite_test.go @@ -0,0 +1,35 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package converter_test + +import ( + "io/ioutil" + "log" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestConverter(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Light Converter Suite Test") +} + +var _ = BeforeSuite(func() { + log.SetOutput(ioutil.Discard) +}) diff --git a/pkg/omni/light/converter/converter_test.go b/pkg/omni/light/converter/converter_test.go new file mode 100644 index 00000000..374bb699 --- /dev/null +++ b/pkg/omni/light/converter/converter_test.go @@ -0,0 +1,151 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package converter_test + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/omni/light/converter" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks" +) + +var _ = Describe("Converter", func() { + var con *contract.Contract + var tusdWantedEvents = []string{"Transfer", "Mint"} + var ensWantedEvents = []string{"NewOwner"} + var err error + + Describe("Update", func() { + It("Updates contract info held by the converter", func() { + con = test_helpers.SetupTusdContract(tusdWantedEvents, []string{}) + c := converter.NewConverter(con) + Expect(c.ContractInfo).To(Equal(con)) + + info := test_helpers.SetupTusdContract([]string{}, []string{}) + c.Update(info) + Expect(c.ContractInfo).To(Equal(info)) + }) + }) + + Describe("Convert", func() { + It("Converts a watched event log to mapping of event input names to values", func() { + con = test_helpers.SetupTusdContract(tusdWantedEvents, []string{}) + _, ok := con.Events["Approval"] + Expect(ok).To(Equal(false)) + + event, ok := con.Events["Transfer"] + Expect(ok).To(Equal(true)) + + c := converter.NewConverter(con) + logs, err := c.Convert([]types.Log{mocks.MockTransferLog1, mocks.MockTransferLog2}, event, 232) + Expect(err).ToNot(HaveOccurred()) + Expect(len(logs)).To(Equal(2)) + + sender1 := common.HexToAddress("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391") + sender2 := common.HexToAddress("0x000000000000000000000000000000000000000000000000000000000000af21") + value := helpers.BigFromString("1097077688018008265106216665536940668749033598146") + + Expect(logs[0].Values["to"]).To(Equal(sender1.String())) + Expect(logs[0].Values["from"]).To(Equal(sender2.String())) + Expect(logs[0].Values["value"]).To(Equal(value.String())) + Expect(logs[0].Id).To(Equal(int64(232))) + Expect(logs[1].Values["to"]).To(Equal(sender2.String())) + Expect(logs[1].Values["from"]).To(Equal(sender1.String())) + Expect(logs[1].Values["value"]).To(Equal(value.String())) + Expect(logs[1].Id).To(Equal(int64(232))) + }) + + It("Keeps track of addresses it sees if they will be used for method polling", func() { + con = test_helpers.SetupTusdContract(tusdWantedEvents, []string{"balanceOf"}) + event, ok := con.Events["Transfer"] + Expect(ok).To(Equal(true)) + + c := converter.NewConverter(con) + _, err := c.Convert([]types.Log{mocks.MockTransferLog1, mocks.MockTransferLog2}, event, 232) + Expect(err).ToNot(HaveOccurred()) + + b, ok := con.EmittedAddrs[common.HexToAddress("0x000000000000000000000000000000000000Af21")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + b, ok = con.EmittedAddrs[common.HexToAddress("0x09BbBBE21a5975cAc061D82f7b843bCE061BA391")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + _, ok = con.EmittedAddrs[common.HexToAddress("0x")] + Expect(ok).To(Equal(false)) + + _, ok = con.EmittedAddrs[""] + Expect(ok).To(Equal(false)) + + _, ok = con.EmittedAddrs[common.HexToAddress("0x09THISE21a5IS5cFAKE1D82fAND43bCE06MADEUP")] + Expect(ok).To(Equal(false)) + + _, ok = con.EmittedHashes[common.HexToHash("0x000000000000000000000000c02aaa39b223helloa0e5c4f27ead9083c752553")] + Expect(ok).To(Equal(false)) + }) + + It("Keeps track of hashes it sees if they will be used for method polling", func() { + con = test_helpers.SetupENSContract(ensWantedEvents, []string{"owner"}) + event, ok := con.Events["NewOwner"] + Expect(ok).To(Equal(true)) + + c := converter.NewConverter(con) + _, err := c.Convert([]types.Log{mocks.MockNewOwnerLog1, mocks.MockNewOwnerLog2}, event, 232) + Expect(err).ToNot(HaveOccurred()) + Expect(len(con.EmittedHashes)).To(Equal(3)) + + b, ok := con.EmittedHashes[common.HexToHash("0x000000000000000000000000c02aaa39b223helloa0e5c4f27ead9083c752553")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + b, ok = con.EmittedHashes[common.HexToHash("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + b, ok = con.EmittedHashes[common.HexToHash("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba400")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + _, ok = con.EmittedHashes[common.HexToHash("0x9dd48thiscc444isc242510c0made03upa5975cac061dhashb843bce061ba400")] + Expect(ok).To(Equal(false)) + + _, ok = con.EmittedHashes[common.HexToAddress("0x")] + Expect(ok).To(Equal(false)) + + _, ok = con.EmittedHashes[""] + Expect(ok).To(Equal(false)) + + // Does not keep track of emitted addresses if the methods provided will not use them + _, ok = con.EmittedAddrs[common.HexToAddress("0x000000000000000000000000000000000000Af21")] + Expect(ok).To(Equal(false)) + }) + + It("Fails with an empty contract", func() { + event := con.Events["Transfer"] + c := converter.NewConverter(&contract.Contract{}) + _, err = c.Convert([]types.Log{mocks.MockTransferLog1}, event, 232) + Expect(err).To(HaveOccurred()) + }) + }) +}) diff --git a/pkg/omni/light/fetcher/fetcher.go b/pkg/omni/light/fetcher/fetcher.go new file mode 100644 index 00000000..77162f0e --- /dev/null +++ b/pkg/omni/light/fetcher/fetcher.go @@ -0,0 +1,69 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package fetcher + +import ( + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + + "github.com/vulcanize/vulcanizedb/pkg/core" +) + +type Fetcher interface { + FetchLogs(contractAddresses []string, topics []common.Hash, missingHeader core.Header) ([]types.Log, error) +} + +type fetcher struct { + blockChain core.BlockChain +} + +func NewFetcher(blockchain core.BlockChain) *fetcher { + return &fetcher{ + blockChain: blockchain, + } +} + +// Checks all topic0s, on all addresses, fetching matching logs for the given header +func (fetcher *fetcher) FetchLogs(contractAddresses []string, topic0s []common.Hash, header core.Header) ([]types.Log, error) { + addresses := hexStringsToAddresses(contractAddresses) + blockHash := common.HexToHash(header.Hash) + query := ethereum.FilterQuery{ + BlockHash: &blockHash, + Addresses: addresses, + // Search for _any_ of the topics in topic0 position; see docs on `FilterQuery` + Topics: [][]common.Hash{topic0s}, + } + + logs, err := fetcher.blockChain.GetEthLogsWithCustomQuery(query) + if err != nil { + // TODO review aggregate fetching error handling + return []types.Log{}, err + } + + return logs, nil +} + +func hexStringsToAddresses(hexStrings []string) []common.Address { + var addresses []common.Address + for _, hexString := range hexStrings { + address := common.HexToAddress(hexString) + addresses = append(addresses, address) + } + + return addresses +} diff --git a/pkg/omni/light/fetcher/fetcher_suite_test.go b/pkg/omni/light/fetcher/fetcher_suite_test.go new file mode 100644 index 00000000..3b60a906 --- /dev/null +++ b/pkg/omni/light/fetcher/fetcher_suite_test.go @@ -0,0 +1,35 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package fetcher_test + +import ( + "io/ioutil" + "log" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestFetcher(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Fetcher Suite Test") +} + +var _ = BeforeSuite(func() { + log.SetOutput(ioutil.Discard) +}) diff --git a/pkg/omni/light/fetcher/fetcher_test.go b/pkg/omni/light/fetcher/fetcher_test.go new file mode 100644 index 00000000..8dca9ca8 --- /dev/null +++ b/pkg/omni/light/fetcher/fetcher_test.go @@ -0,0 +1,66 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package fetcher_test + +import ( + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/fakes" + "github.com/vulcanize/vulcanizedb/pkg/omni/light/fetcher" +) + +var _ = Describe("Fetcher", func() { + Describe("FetchLogs", func() { + It("fetches logs based on the given query", func() { + blockChain := fakes.NewMockBlockChain() + fetcher := fetcher.NewFetcher(blockChain) + header := fakes.FakeHeader + + addresses := []string{"0xfakeAddress", "0xanotherFakeAddress"} + topicZeros := [][]common.Hash{{common.BytesToHash([]byte{1, 2, 3, 4, 5})}} + + _, err := fetcher.FetchLogs(addresses, []common.Hash{common.BytesToHash([]byte{1, 2, 3, 4, 5})}, header) + + address1 := common.HexToAddress("0xfakeAddress") + address2 := common.HexToAddress("0xanotherFakeAddress") + Expect(err).NotTo(HaveOccurred()) + + blockHash := common.HexToHash(header.Hash) + expectedQuery := ethereum.FilterQuery{ + BlockHash: &blockHash, + Addresses: []common.Address{address1, address2}, + Topics: topicZeros, + } + blockChain.AssertGetEthLogsWithCustomQueryCalledWith(expectedQuery) + }) + + It("returns an error if fetching the logs fails", func() { + blockChain := fakes.NewMockBlockChain() + blockChain.SetGetEthLogsWithCustomQueryErr(fakes.FakeError) + fetcher := fetcher.NewFetcher(blockChain) + + _, err := fetcher.FetchLogs([]string{}, []common.Hash{}, core.Header{}) + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + }) +}) diff --git a/pkg/omni/light/repository/header_repository.go b/pkg/omni/light/repository/header_repository.go new file mode 100644 index 00000000..c26b3e60 --- /dev/null +++ b/pkg/omni/light/repository/header_repository.go @@ -0,0 +1,259 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package repository + +import ( + "database/sql" + "fmt" + + "github.com/hashicorp/golang-lru" + + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" +) + +const columnCacheSize = 1000 + +type HeaderRepository interface { + AddCheckColumn(id string) error + AddCheckColumns(ids []string) error + MarkHeaderChecked(headerID int64, eventID string) error + MarkHeaderCheckedForAll(headerID int64, ids []string) error + MarkHeadersCheckedForAll(headers []core.Header, ids []string) error + MissingHeaders(startingBlockNumber int64, endingBlockNumber int64, eventID string) ([]core.Header, error) + MissingMethodsCheckedEventsIntersection(startingBlockNumber, endingBlockNumber int64, methodIds, eventIds []string) ([]core.Header, error) + MissingHeadersForAll(startingBlockNumber, endingBlockNumber int64, ids []string) ([]core.Header, error) + CheckCache(key string) (interface{}, bool) +} + +type headerRepository struct { + db *postgres.DB + columns *lru.Cache // Cache created columns to minimize db connections +} + +func NewHeaderRepository(db *postgres.DB) *headerRepository { + ccs, _ := lru.New(columnCacheSize) + return &headerRepository{ + db: db, + columns: ccs, + } +} + +func (r *headerRepository) AddCheckColumn(id string) error { + // Check cache to see if column already exists before querying pg + _, ok := r.columns.Get(id) + if ok { + return nil + } + + pgStr := "ALTER TABLE public.checked_headers ADD COLUMN IF NOT EXISTS " + pgStr = pgStr + id + " BOOLEAN NOT NULL DEFAULT FALSE" + _, err := r.db.Exec(pgStr) + if err != nil { + return err + } + + // Add column name to cache + r.columns.Add(id, true) + + return nil +} + +func (r *headerRepository) AddCheckColumns(ids []string) error { + var err error + baseQuery := "ALTER TABLE public.checked_headers" + input := make([]string, 0, len(ids)) + for _, id := range ids { + _, ok := r.columns.Get(id) + if !ok { + baseQuery += " ADD COLUMN IF NOT EXISTS " + id + " BOOLEAN NOT NULL DEFAULT FALSE," + input = append(input, id) + } + } + if len(input) > 0 { + _, err = r.db.Exec(baseQuery[:len(baseQuery)-1]) + if err == nil { + for _, id := range input { + r.columns.Add(id, true) + } + } + } + + return err +} + +func (r *headerRepository) MarkHeaderChecked(headerID int64, id string) error { + _, err := r.db.Exec(`INSERT INTO public.checked_headers (header_id, `+id+`) + VALUES ($1, $2) + ON CONFLICT (header_id) DO + UPDATE SET `+id+` = $2`, headerID, true) + + return err +} + +func (r *headerRepository) MarkHeaderCheckedForAll(headerID int64, ids []string) error { + pgStr := "INSERT INTO public.checked_headers (header_id, " + for _, id := range ids { + pgStr += id + ", " + } + pgStr = pgStr[:len(pgStr)-2] + ") VALUES ($1, " + for i := 0; i < len(ids); i++ { + pgStr += "true, " + } + pgStr = pgStr[:len(pgStr)-2] + ") ON CONFLICT (header_id) DO UPDATE SET " + for _, id := range ids { + pgStr += fmt.Sprintf("%s = true, ", id) + } + pgStr = pgStr[:len(pgStr)-2] + _, err := r.db.Exec(pgStr, headerID) + + return err +} + +func (r *headerRepository) MarkHeadersCheckedForAll(headers []core.Header, ids []string) error { + tx, err := r.db.Begin() + if err != nil { + return err + } + + for _, header := range headers { + pgStr := "INSERT INTO public.checked_headers (header_id, " + for _, id := range ids { + pgStr += id + ", " + } + pgStr = pgStr[:len(pgStr)-2] + ") VALUES ($1, " + for i := 0; i < len(ids); i++ { + pgStr += "true, " + } + pgStr = pgStr[:len(pgStr)-2] + ") ON CONFLICT (header_id) DO UPDATE SET " + for _, id := range ids { + pgStr += fmt.Sprintf("%s = true, ", id) + } + pgStr = pgStr[:len(pgStr)-2] + _, err = tx.Exec(pgStr, header.Id) + if err != nil { + tx.Rollback() + return err + } + } + + return tx.Commit() +} + +func (r *headerRepository) MissingHeaders(startingBlockNumber, endingBlockNumber int64, id string) ([]core.Header, error) { + var result []core.Header + var query string + var err error + + if endingBlockNumber == -1 { + query = `SELECT headers.id, headers.block_number, headers.hash FROM headers + LEFT JOIN checked_headers on headers.id = header_id + WHERE (header_id ISNULL OR ` + id + ` IS FALSE) + AND headers.block_number >= $1 + AND headers.eth_node_fingerprint = $2 + ORDER BY headers.block_number` + err = r.db.Select(&result, query, startingBlockNumber, r.db.Node.ID) + } else { + query = `SELECT headers.id, headers.block_number, headers.hash FROM headers + LEFT JOIN checked_headers on headers.id = header_id + WHERE (header_id ISNULL OR ` + id + ` IS FALSE) + AND headers.block_number >= $1 + AND headers.block_number <= $2 + AND headers.eth_node_fingerprint = $3 + ORDER BY headers.block_number` + err = r.db.Select(&result, query, startingBlockNumber, endingBlockNumber, r.db.Node.ID) + } + + return result, err +} + +func (r *headerRepository) MissingHeadersForAll(startingBlockNumber, endingBlockNumber int64, ids []string) ([]core.Header, error) { + var result []core.Header + var query string + var err error + + baseQuery := `SELECT headers.id, headers.block_number, headers.hash FROM headers + LEFT JOIN checked_headers on headers.id = header_id + WHERE (header_id ISNULL` + for _, id := range ids { + baseQuery += ` OR ` + id + ` IS FALSE` + } + + if endingBlockNumber == -1 { + endStr := `) AND headers.block_number >= $1 + AND headers.eth_node_fingerprint = $2 + ORDER BY headers.block_number` + query = baseQuery + endStr + err = r.db.Select(&result, query, startingBlockNumber, r.db.Node.ID) + } else { + endStr := `) AND headers.block_number >= $1 + AND headers.block_number <= $2 + AND headers.eth_node_fingerprint = $3 + ORDER BY headers.block_number` + query = baseQuery + endStr + err = r.db.Select(&result, query, startingBlockNumber, endingBlockNumber, r.db.Node.ID) + } + + return result, err +} + +func (r *headerRepository) MissingMethodsCheckedEventsIntersection(startingBlockNumber, endingBlockNumber int64, methodIds, eventIds []string) ([]core.Header, error) { + var result []core.Header + var query string + var err error + + baseQuery := `SELECT headers.id, headers.block_number, headers.hash FROM headers + LEFT JOIN checked_headers on headers.id = header_id + WHERE (header_id IS NOT NULL` + for _, id := range eventIds { + baseQuery += ` AND ` + id + ` IS TRUE` + } + baseQuery += `) AND (` + for _, id := range methodIds { + baseQuery += id + ` IS FALSE AND ` + } + baseQuery = baseQuery[:len(baseQuery)-5] + `) ` + + if endingBlockNumber == -1 { + endStr := `AND headers.block_number >= $1 + AND headers.eth_node_fingerprint = $2 + ORDER BY headers.block_number` + query = baseQuery + endStr + err = r.db.Select(&result, query, startingBlockNumber, r.db.Node.ID) + } else { + endStr := `AND headers.block_number >= $1 + AND headers.block_number <= $2 + AND headers.eth_node_fingerprint = $3 + ORDER BY headers.block_number` + query = baseQuery + endStr + err = r.db.Select(&result, query, startingBlockNumber, endingBlockNumber, r.db.Node.ID) + } + + return result, err +} + +func (r *headerRepository) CheckCache(key string) (interface{}, bool) { + return r.columns.Get(key) +} + +func MarkHeaderCheckedInTransaction(headerID int64, tx *sql.Tx, eventID string) error { + _, err := tx.Exec(`INSERT INTO public.checked_headers (header_id, `+eventID+`) + VALUES ($1, $2) + ON CONFLICT (header_id) DO + UPDATE SET `+eventID+` = $2`, headerID, true) + return err +} diff --git a/pkg/omni/light/repository/header_repository_test.go b/pkg/omni/light/repository/header_repository_test.go new file mode 100644 index 00000000..4f25cabc --- /dev/null +++ b/pkg/omni/light/repository/header_repository_test.go @@ -0,0 +1,317 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package repository_test + +import ( + "fmt" + "github.com/vulcanize/vulcanizedb/pkg/core" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories" + "github.com/vulcanize/vulcanizedb/pkg/omni/light/repository" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks" +) + +var _ = Describe("Repository", func() { + var db *postgres.DB + var omniHeaderRepo repository.HeaderRepository // omni/light header repository + var coreHeaderRepo repositories.HeaderRepository // pkg/datastore header repository + var eventIDs = []string{ + "eventName_contractAddr", + "eventName_contractAddr2", + "eventName_contractAddr3", + } + var methodIDs = []string{ + "methodName_contractAddr", + "methodName_contractAddr2", + "methodName_contractAddr3", + } + + BeforeEach(func() { + db, _ = test_helpers.SetupDBandBC() + omniHeaderRepo = repository.NewHeaderRepository(db) + coreHeaderRepo = repositories.NewHeaderRepository(db) + }) + + AfterEach(func() { + test_helpers.TearDown(db) + }) + + Describe("AddCheckColumn", func() { + It("Creates a column for the given eventID to mark if the header has been checked for that event", func() { + query := fmt.Sprintf("SELECT %s FROM checked_headers", eventIDs[0]) + _, err := db.Exec(query) + Expect(err).To(HaveOccurred()) + + err = omniHeaderRepo.AddCheckColumn(eventIDs[0]) + Expect(err).ToNot(HaveOccurred()) + + _, err = db.Exec(query) + Expect(err).ToNot(HaveOccurred()) + }) + + It("Caches column it creates so that it does not need to repeatedly query the database to check for it's existence", func() { + _, ok := omniHeaderRepo.CheckCache(eventIDs[0]) + Expect(ok).To(Equal(false)) + + err := omniHeaderRepo.AddCheckColumn(eventIDs[0]) + Expect(err).ToNot(HaveOccurred()) + + v, ok := omniHeaderRepo.CheckCache(eventIDs[0]) + Expect(ok).To(Equal(true)) + Expect(v).To(Equal(true)) + }) + }) + + Describe("AddCheckColumns", func() { + It("Creates a column for the given eventIDs to mark if the header has been checked for those events", func() { + for _, id := range eventIDs { + _, err := db.Exec(fmt.Sprintf("SELECT %s FROM checked_headers", id)) + Expect(err).To(HaveOccurred()) + } + + err := omniHeaderRepo.AddCheckColumns(eventIDs) + Expect(err).ToNot(HaveOccurred()) + + for _, id := range eventIDs { + _, err := db.Exec(fmt.Sprintf("SELECT %s FROM checked_headers", id)) + Expect(err).ToNot(HaveOccurred()) + } + }) + + It("Caches columns it creates so that it does not need to repeatedly query the database to check for it's existence", func() { + for _, id := range eventIDs { + _, ok := omniHeaderRepo.CheckCache(id) + Expect(ok).To(Equal(false)) + } + + err := omniHeaderRepo.AddCheckColumns(eventIDs) + Expect(err).ToNot(HaveOccurred()) + + for _, id := range eventIDs { + v, ok := omniHeaderRepo.CheckCache(id) + Expect(ok).To(Equal(true)) + Expect(v).To(Equal(true)) + } + }) + }) + + Describe("MissingHeaders", func() { + It("Returns all unchecked headers for the given eventID", func() { + addHeaders(coreHeaderRepo) + err := omniHeaderRepo.AddCheckColumn(eventIDs[0]) + Expect(err).ToNot(HaveOccurred()) + + missingHeaders, err := omniHeaderRepo.MissingHeaders(6194630, 6194635, eventIDs[0]) + Expect(err).ToNot(HaveOccurred()) + Expect(len(missingHeaders)).To(Equal(3)) + }) + + It("Returns unchecked headers in ascending order", func() { + addHeaders(coreHeaderRepo) + err := omniHeaderRepo.AddCheckColumn(eventIDs[0]) + Expect(err).ToNot(HaveOccurred()) + + missingHeaders, err := omniHeaderRepo.MissingHeaders(6194630, 6194635, eventIDs[0]) + Expect(err).ToNot(HaveOccurred()) + Expect(len(missingHeaders)).To(Equal(3)) + + h1 := missingHeaders[0] + h2 := missingHeaders[1] + h3 := missingHeaders[2] + Expect(h1.BlockNumber).To(Equal(int64(6194632))) + Expect(h2.BlockNumber).To(Equal(int64(6194633))) + Expect(h3.BlockNumber).To(Equal(int64(6194634))) + }) + + It("Fails if eventID does not yet exist in check_headers table", func() { + addHeaders(coreHeaderRepo) + err := omniHeaderRepo.AddCheckColumn(eventIDs[0]) + Expect(err).ToNot(HaveOccurred()) + + _, err = omniHeaderRepo.MissingHeaders(6194630, 6194635, "notEventId") + Expect(err).To(HaveOccurred()) + }) + }) + + Describe("MissingHeadersForAll", func() { // HERE + It("Returns all headers that have not been checked for all of the ids provided", func() { + addHeaders(coreHeaderRepo) + err := omniHeaderRepo.AddCheckColumns(eventIDs) + Expect(err).ToNot(HaveOccurred()) + + missingHeaders, err := omniHeaderRepo.MissingHeadersForAll(6194630, 6194635, eventIDs) + Expect(err).ToNot(HaveOccurred()) + Expect(len(missingHeaders)).To(Equal(3)) + + err = omniHeaderRepo.MarkHeaderChecked(missingHeaders[0].Id, eventIDs[0]) + Expect(err).ToNot(HaveOccurred()) + + missingHeaders, err = omniHeaderRepo.MissingHeadersForAll(6194630, 6194635, eventIDs) + Expect(err).ToNot(HaveOccurred()) + Expect(len(missingHeaders)).To(Equal(3)) + + err = omniHeaderRepo.MarkHeaderChecked(missingHeaders[0].Id, eventIDs[1]) + Expect(err).ToNot(HaveOccurred()) + err = omniHeaderRepo.MarkHeaderChecked(missingHeaders[0].Id, eventIDs[2]) + Expect(err).ToNot(HaveOccurred()) + + missingHeaders, err = omniHeaderRepo.MissingHeadersForAll(6194630, 6194635, eventIDs) + Expect(err).ToNot(HaveOccurred()) + Expect(len(missingHeaders)).To(Equal(2)) + }) + + It("Fails if one of the eventIDs does not yet exist in check_headers table", func() { + addHeaders(coreHeaderRepo) + err := omniHeaderRepo.AddCheckColumns(eventIDs) + Expect(err).ToNot(HaveOccurred()) + badEventIDs := append(eventIDs, "notEventId") + + _, err = omniHeaderRepo.MissingHeadersForAll(6194630, 6194635, badEventIDs) + Expect(err).To(HaveOccurred()) + }) + }) + + Describe("MarkHeaderChecked", func() { + It("Marks the header checked for the given eventID", func() { + addHeaders(coreHeaderRepo) + err := omniHeaderRepo.AddCheckColumn(eventIDs[0]) + Expect(err).ToNot(HaveOccurred()) + + missingHeaders, err := omniHeaderRepo.MissingHeaders(6194630, 6194635, eventIDs[0]) + Expect(err).ToNot(HaveOccurred()) + Expect(len(missingHeaders)).To(Equal(3)) + + headerID := missingHeaders[0].Id + err = omniHeaderRepo.MarkHeaderChecked(headerID, eventIDs[0]) + Expect(err).ToNot(HaveOccurred()) + + missingHeaders, err = omniHeaderRepo.MissingHeaders(6194630, 6194635, eventIDs[0]) + Expect(err).ToNot(HaveOccurred()) + Expect(len(missingHeaders)).To(Equal(2)) + }) + + It("Fails if eventID does not yet exist in check_headers table", func() { + addHeaders(coreHeaderRepo) + err := omniHeaderRepo.AddCheckColumn(eventIDs[0]) + Expect(err).ToNot(HaveOccurred()) + + missingHeaders, err := omniHeaderRepo.MissingHeaders(6194630, 6194635, eventIDs[0]) + Expect(err).ToNot(HaveOccurred()) + Expect(len(missingHeaders)).To(Equal(3)) + + headerID := missingHeaders[0].Id + err = omniHeaderRepo.MarkHeaderChecked(headerID, "notEventId") + Expect(err).To(HaveOccurred()) + + missingHeaders, err = omniHeaderRepo.MissingHeaders(6194630, 6194635, eventIDs[0]) + Expect(err).ToNot(HaveOccurred()) + Expect(len(missingHeaders)).To(Equal(3)) + }) + }) + + Describe("MarkHeaderCheckedForAll", func() { + It("Marks the header checked for all provided column ids", func() { + addHeaders(coreHeaderRepo) + err := omniHeaderRepo.AddCheckColumns(eventIDs) + Expect(err).ToNot(HaveOccurred()) + + missingHeaders, err := omniHeaderRepo.MissingHeadersForAll(6194630, 6194635, eventIDs) + Expect(err).ToNot(HaveOccurred()) + Expect(len(missingHeaders)).To(Equal(3)) + + headerID := missingHeaders[0].Id + err = omniHeaderRepo.MarkHeaderCheckedForAll(headerID, eventIDs) + Expect(err).ToNot(HaveOccurred()) + + missingHeaders, err = omniHeaderRepo.MissingHeaders(6194630, 6194635, eventIDs[0]) + Expect(err).ToNot(HaveOccurred()) + Expect(len(missingHeaders)).To(Equal(2)) + }) + }) + + Describe("MarkHeadersCheckedForAll", func() { + It("Marks the headers checked for all provided column ids", func() { + addHeaders(coreHeaderRepo) + methodIDs := []string{ + "methodName_contractAddr", + "methodName_contractAddr2", + "methodName_contractAddr3", + } + + var missingHeaders []core.Header + for _, id := range methodIDs { + err := omniHeaderRepo.AddCheckColumn(id) + Expect(err).ToNot(HaveOccurred()) + missingHeaders, err = omniHeaderRepo.MissingHeaders(6194630, 6194635, id) + Expect(err).ToNot(HaveOccurred()) + Expect(len(missingHeaders)).To(Equal(3)) + } + + err := omniHeaderRepo.MarkHeadersCheckedForAll(missingHeaders, methodIDs) + Expect(err).ToNot(HaveOccurred()) + for _, id := range methodIDs { + missingHeaders, err = omniHeaderRepo.MissingHeaders(6194630, 6194635, id) + Expect(err).ToNot(HaveOccurred()) + Expect(len(missingHeaders)).To(Equal(0)) + } + }) + }) + + Describe("MissingMethodsCheckedEventsIntersection", func() { + It("Returns headers that have been checked for all the provided events but have not been checked for all the provided methods", func() { + addHeaders(coreHeaderRepo) + for i, id := range eventIDs { + err := omniHeaderRepo.AddCheckColumn(id) + Expect(err).ToNot(HaveOccurred()) + err = omniHeaderRepo.AddCheckColumn(methodIDs[i]) + Expect(err).ToNot(HaveOccurred()) + } + + missingHeaders, err := omniHeaderRepo.MissingHeaders(6194630, 6194635, eventIDs[0]) + Expect(err).ToNot(HaveOccurred()) + Expect(len(missingHeaders)).To(Equal(3)) + + headerID := missingHeaders[0].Id + headerID2 := missingHeaders[1].Id + for i, id := range eventIDs { + err = omniHeaderRepo.MarkHeaderChecked(headerID, id) + Expect(err).ToNot(HaveOccurred()) + err = omniHeaderRepo.MarkHeaderChecked(headerID2, id) + Expect(err).ToNot(HaveOccurred()) + err = omniHeaderRepo.MarkHeaderChecked(headerID, methodIDs[i]) + Expect(err).ToNot(HaveOccurred()) + } + + intersectionHeaders, err := omniHeaderRepo.MissingMethodsCheckedEventsIntersection(6194630, 6194635, methodIDs, eventIDs) + Expect(err).ToNot(HaveOccurred()) + Expect(len(intersectionHeaders)).To(Equal(1)) + Expect(intersectionHeaders[0].Id).To(Equal(headerID2)) + + }) + }) +}) + +func addHeaders(coreHeaderRepo repositories.HeaderRepository) { + coreHeaderRepo.CreateOrUpdateHeader(mocks.MockHeader1) + coreHeaderRepo.CreateOrUpdateHeader(mocks.MockHeader2) + coreHeaderRepo.CreateOrUpdateHeader(mocks.MockHeader3) +} diff --git a/pkg/omni/light/repository/repository_suite_test.go b/pkg/omni/light/repository/repository_suite_test.go new file mode 100644 index 00000000..80215953 --- /dev/null +++ b/pkg/omni/light/repository/repository_suite_test.go @@ -0,0 +1,35 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package repository_test + +import ( + "io/ioutil" + "log" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestRepository(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Light Repository Suite Test") +} + +var _ = BeforeSuite(func() { + log.SetOutput(ioutil.Discard) +}) diff --git a/pkg/omni/light/retriever/block_retriever.go b/pkg/omni/light/retriever/block_retriever.go new file mode 100644 index 00000000..a871278e --- /dev/null +++ b/pkg/omni/light/retriever/block_retriever.go @@ -0,0 +1,60 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package retriever + +import ( + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" +) + +// Block retriever is used to retrieve the first block for a given contract and the most recent block +// It requires a vDB synced database with blocks, transactions, receipts, and logs +type BlockRetriever interface { + RetrieveFirstBlock() (int64, error) + RetrieveMostRecentBlock() (int64, error) +} + +type blockRetriever struct { + db *postgres.DB +} + +func NewBlockRetriever(db *postgres.DB) (r *blockRetriever) { + return &blockRetriever{ + db: db, + } +} + +// Retrieve block number of earliest header in repo +func (r *blockRetriever) RetrieveFirstBlock() (int64, error) { + var firstBlock int + err := r.db.Get( + &firstBlock, + "SELECT block_number FROM headers ORDER BY block_number LIMIT 1", + ) + + return int64(firstBlock), err +} + +// Retrieve block number of latest header in repo +func (r *blockRetriever) RetrieveMostRecentBlock() (int64, error) { + var lastBlock int + err := r.db.Get( + &lastBlock, + "SELECT block_number FROM headers ORDER BY block_number DESC LIMIT 1", + ) + + return int64(lastBlock), err +} diff --git a/pkg/omni/light/retriever/block_retriever_test.go b/pkg/omni/light/retriever/block_retriever_test.go new file mode 100644 index 00000000..563e3738 --- /dev/null +++ b/pkg/omni/light/retriever/block_retriever_test.go @@ -0,0 +1,79 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package retriever_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories" + "github.com/vulcanize/vulcanizedb/pkg/omni/light/retriever" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks" +) + +var _ = Describe("Block Retriever", func() { + var db *postgres.DB + var r retriever.BlockRetriever + var headerRepository repositories.HeaderRepository + + BeforeEach(func() { + db, _ = test_helpers.SetupDBandBC() + headerRepository = repositories.NewHeaderRepository(db) + r = retriever.NewBlockRetriever(db) + }) + + AfterEach(func() { + test_helpers.TearDown(db) + }) + + Describe("RetrieveFirstBlock", func() { + It("Retrieves block number of earliest header in the database", func() { + headerRepository.CreateOrUpdateHeader(mocks.MockHeader1) + headerRepository.CreateOrUpdateHeader(mocks.MockHeader2) + headerRepository.CreateOrUpdateHeader(mocks.MockHeader3) + + i, err := r.RetrieveFirstBlock() + Expect(err).NotTo(HaveOccurred()) + Expect(i).To(Equal(int64(6194632))) + }) + + It("Fails if no headers can be found in the database", func() { + _, err := r.RetrieveFirstBlock() + Expect(err).To(HaveOccurred()) + }) + }) + + Describe("RetrieveMostRecentBlock", func() { + It("Retrieves the latest header's block number", func() { + headerRepository.CreateOrUpdateHeader(mocks.MockHeader1) + headerRepository.CreateOrUpdateHeader(mocks.MockHeader2) + headerRepository.CreateOrUpdateHeader(mocks.MockHeader3) + + i, err := r.RetrieveMostRecentBlock() + Expect(err).ToNot(HaveOccurred()) + Expect(i).To(Equal(int64(6194634))) + }) + + It("Fails if no headers can be found in the database", func() { + i, err := r.RetrieveMostRecentBlock() + Expect(err).To(HaveOccurred()) + Expect(i).To(Equal(int64(0))) + }) + }) +}) diff --git a/pkg/omni/light/retriever/retriever_suite_test.go b/pkg/omni/light/retriever/retriever_suite_test.go new file mode 100644 index 00000000..cb5a7313 --- /dev/null +++ b/pkg/omni/light/retriever/retriever_suite_test.go @@ -0,0 +1,35 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package retriever_test + +import ( + "io/ioutil" + "log" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestRetriever(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Light BLock Number Retriever Suite Test") +} + +var _ = BeforeSuite(func() { + log.SetOutput(ioutil.Discard) +}) diff --git a/pkg/omni/light/transformer/transformer.go b/pkg/omni/light/transformer/transformer.go new file mode 100644 index 00000000..565e6966 --- /dev/null +++ b/pkg/omni/light/transformer/transformer.go @@ -0,0 +1,376 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package transformer + +import ( + "errors" + "strings" + + "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/omni/light/converter" + "github.com/vulcanize/vulcanizedb/pkg/omni/light/fetcher" + "github.com/vulcanize/vulcanizedb/pkg/omni/light/repository" + "github.com/vulcanize/vulcanizedb/pkg/omni/light/retriever" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/parser" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/poller" + srep "github.com/vulcanize/vulcanizedb/pkg/omni/shared/repository" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" +) + +// Requires a light synced vDB (headers) and a running eth node (or infura) +type transformer struct { + // Database interfaces + srep.EventRepository // Holds transformed watched event log data + repository.HeaderRepository // Interface for interaction with header repositories + + // Pre-processing interfaces + parser.Parser // Parses events and methods out of contract abi fetched using contract address + retriever.BlockRetriever // Retrieves first block for contract and current block height + + // Processing interfaces + fetcher.Fetcher // Fetches event logs, using header hashes + converter.Converter // Converts watched event logs into custom log + poller.Poller // Polls methods using arguments collected from events and persists them using a method datastore + + // Ethereum network name; default "" is mainnet + Network string + + // Store contract info as mapping to contract address + Contracts map[string]*contract.Contract + + // Targeted subset of events/methods + // Stored as maps of contract address to events/method names of interest + WatchedEvents map[string][]string // Default/empty event list means all are watched + WantedMethods map[string][]string // Default/empty method list means none are polled + + // Starting block number for each contract + ContractStart map[string]int64 + + // Lists of argument values to filter event or + // method data with; if empty no filter is applied + EventArgs map[string][]string + MethodArgs map[string][]string + + // Whether or not to create a list of emitted address or hashes for the contract in postgres + CreateAddrList map[string]bool + CreateHashList map[string]bool + + // Method piping on/off for a contract + Piping map[string]bool + + // Internally configured transformer variables + contractAddresses []string // Holds all contract addresses, for batch fetching of logs + sortedEventIds map[string][]string // Map to sort event column ids by contract, for post fetch processing and persisting of logs + sortedMethodIds map[string][]string // Map to sort method column ids by contract, for post fetch method polling + eventIds []string // Holds event column ids across all contract, for batch fetching of headers + eventFilters []common.Hash // Holds topic0 hashes across all contracts, for batch fetching of logs + start int64 // Hold the lowest starting block and the highest ending block +} + +// Order-of-operations: +// 1. Create new transformer +// 2. Load contract addresses and their parameters +// 3. Init +// 4. Execute + +// Transformer takes in config for blockchain, database, and network id +func NewTransformer(network string, bc core.BlockChain, db *postgres.DB) *transformer { + + return &transformer{ + Poller: poller.NewPoller(bc, db, types.LightSync), + Fetcher: fetcher.NewFetcher(bc), + Parser: parser.NewParser(network), + HeaderRepository: repository.NewHeaderRepository(db), + BlockRetriever: retriever.NewBlockRetriever(db), + Converter: converter.NewConverter(&contract.Contract{}), + Contracts: map[string]*contract.Contract{}, + EventRepository: srep.NewEventRepository(db, types.LightSync), + WatchedEvents: map[string][]string{}, + WantedMethods: map[string][]string{}, + ContractStart: map[string]int64{}, + EventArgs: map[string][]string{}, + MethodArgs: map[string][]string{}, + CreateAddrList: map[string]bool{}, + CreateHashList: map[string]bool{}, + Piping: map[string]bool{}, + Network: network, + } +} + +// Use after creating and setting transformer +// Loops over all of the addr => filter sets +// Uses parser to pull event info from abi +// Use this info to generate event filters +func (tr *transformer) Init() error { + // Initialize internally configured transformer settings + tr.contractAddresses = make([]string, 0) // Holds all contract addresses, for batch fetching of logs + tr.sortedEventIds = make(map[string][]string) // Map to sort event column ids by contract, for post fetch processing and persisting of logs + tr.sortedMethodIds = make(map[string][]string) // Map to sort method column ids by contract, for post fetch method polling + tr.eventIds = make([]string, 0) // Holds event column ids across all contract, for batch fetching of headers + tr.eventFilters = make([]common.Hash, 0) // Holds topic0 hashes across all contracts, for batch fetching of logs + tr.start = 100000000000 // Hold the lowest starting block and the highest ending block + + // Iterate through all internal contract addresses + for contractAddr, subset := range tr.WatchedEvents { + // Get Abi + err := tr.Parser.Parse(contractAddr) + if err != nil { + return err + } + + // Get first block and most recent block number in the header repo + firstBlock, err := tr.BlockRetriever.RetrieveFirstBlock() + if err != nil { + return err + } + lastBlock, err := tr.BlockRetriever.RetrieveMostRecentBlock() + if err != nil { + return err + } + + // Set to specified range if it falls within the bounds + if firstBlock < tr.ContractStart[contractAddr] { + firstBlock = tr.ContractStart[contractAddr] + } + + // Get contract name if it has one + var name = new(string) + tr.FetchContractData(tr.Abi(), contractAddr, "name", nil, &name, lastBlock) + + // Remove any potential accidental duplicate inputs in arg filter values + eventArgs := map[string]bool{} + for _, arg := range tr.EventArgs[contractAddr] { + eventArgs[arg] = true + } + methodArgs := map[string]bool{} + for _, arg := range tr.MethodArgs[contractAddr] { + methodArgs[arg] = true + } + + // Aggregate info into contract object and store for execution + con := contract.Contract{ + Name: *name, + Network: tr.Network, + Address: contractAddr, + Abi: tr.Parser.Abi(), + ParsedAbi: tr.Parser.ParsedAbi(), + StartingBlock: firstBlock, + LastBlock: -1, + Events: tr.Parser.GetEvents(subset), + Methods: tr.Parser.GetSelectMethods(tr.WantedMethods[contractAddr]), + FilterArgs: eventArgs, + MethodArgs: methodArgs, + CreateAddrList: tr.CreateAddrList[contractAddr], + CreateHashList: tr.CreateHashList[contractAddr], + Piping: tr.Piping[contractAddr], + }.Init() + tr.Contracts[contractAddr] = con + tr.contractAddresses = append(tr.contractAddresses, con.Address) + + // Create checked_headers columns for each event id and append to list of all event ids + tr.sortedEventIds[con.Address] = make([]string, 0, len(con.Events)) + for _, event := range con.Events { + eventId := strings.ToLower(event.Name + "_" + con.Address) + err := tr.HeaderRepository.AddCheckColumn(eventId) + if err != nil { + return err + } + // Keep track of this event id; sorted and unsorted + tr.sortedEventIds[con.Address] = append(tr.sortedEventIds[con.Address], eventId) + tr.eventIds = append(tr.eventIds, eventId) + // Append this event sig to the filters + tr.eventFilters = append(tr.eventFilters, event.Sig()) + } + + // Create checked_headers columns for each method id and append list of all method ids + tr.sortedMethodIds[con.Address] = make([]string, 0, len(con.Methods)) + for _, m := range con.Methods { + methodId := strings.ToLower(m.Name + "_" + con.Address) + err := tr.HeaderRepository.AddCheckColumn(methodId) + if err != nil { + return err + } + tr.sortedMethodIds[con.Address] = append(tr.sortedMethodIds[con.Address], methodId) + } + + // Update start to the lowest block + if con.StartingBlock < tr.start { + tr.start = con.StartingBlock + } + } + + return nil +} + +func (tr *transformer) Execute() error { + if len(tr.Contracts) == 0 { + return errors.New("error: transformer has no initialized contracts") + } + + // Map to sort batch fetched logs by which contract they belong to, for post fetch processing + sortedLogs := make(map[string][]gethTypes.Log) + for _, con := range tr.Contracts { + sortedLogs[con.Address] = []gethTypes.Log{} + } + + // Find unchecked headers for all events across all contracts; these are returned in asc order + missingHeaders, err := tr.HeaderRepository.MissingHeadersForAll(tr.start, -1, tr.eventIds) + if err != nil { + return err + } + + // Iterate over headers + for _, header := range missingHeaders { + // And fetch all event logs across contracts at this header + allLogs, err := tr.Fetcher.FetchLogs(tr.contractAddresses, tr.eventFilters, header) + if err != nil { + return err + } + + // If no logs are found mark the header checked for all of these eventIDs + // and continue to method polling and onto the next iteration + if len(allLogs) < 1 { + err = tr.HeaderRepository.MarkHeaderCheckedForAll(header.Id, tr.eventIds) + if err != nil { + return err + } + err = tr.methodPolling(header, tr.sortedMethodIds) + if err != nil { + return err + } + continue + } + + // Sort logs by the contract they belong to + for _, log := range allLogs { + addr := strings.ToLower(log.Address.Hex()) + sortedLogs[addr] = append(sortedLogs[addr], log) + } + + // Process logs for each contract + for conAddr, logs := range sortedLogs { + if logs == nil { + continue + } + // Configure converter with this contract + con := tr.Contracts[conAddr] + tr.Converter.Update(con) + + // Convert logs into batches of log mappings (eventName => []types.Logs + convertedLogs, err := tr.Converter.ConvertBatch(logs, con.Events, header.Id) + if err != nil { + return err + } + // Cycle through each type of event log and persist them + for eventName, logs := range convertedLogs { + // If logs for this event are empty, mark them checked at this header and continue + if len(logs) < 1 { + eventId := strings.ToLower(eventName + "_" + con.Address) + err = tr.HeaderRepository.MarkHeaderChecked(header.Id, eventId) + if err != nil { + return err + } + continue + } + // If logs aren't empty, persist them + // Header is marked checked in the transactions + err = tr.EventRepository.PersistLogs(logs, con.Events[eventName], con.Address, con.Name) + if err != nil { + return err + } + } + } + + // Poll contracts at this block height + err = tr.methodPolling(header, tr.sortedMethodIds) + if err != nil { + return err + } + } + + return nil +} + +// Used to poll contract methods at a given header +func (tr *transformer) methodPolling(header core.Header, sortedMethodIds map[string][]string) error { + for _, con := range tr.Contracts { + // Skip method polling processes if no methods are specified + // Also don't try to poll methods below this contract's specified starting block + if len(con.Methods) == 0 || header.BlockNumber < con.StartingBlock { + continue + } + + // Poll all methods for this contract at this header + err := tr.Poller.PollContractAt(*con, header.BlockNumber) + if err != nil { + return err + } + + // Mark this header checked for the methods + err = tr.HeaderRepository.MarkHeaderCheckedForAll(header.Id, sortedMethodIds[con.Address]) + if err != nil { + return err + } + } + + return nil +} + +// Used to set which contract addresses and which of their events to watch +func (tr *transformer) SetEvents(contractAddr string, filterSet []string) { + tr.WatchedEvents[strings.ToLower(contractAddr)] = filterSet +} + +// Used to set subset of account addresses to watch events for +func (tr *transformer) SetEventArgs(contractAddr string, filterSet []string) { + tr.EventArgs[strings.ToLower(contractAddr)] = filterSet +} + +// Used to set which contract addresses and which of their methods to call +func (tr *transformer) SetMethods(contractAddr string, filterSet []string) { + tr.WantedMethods[strings.ToLower(contractAddr)] = filterSet +} + +// Used to set subset of account addresses to poll methods on +func (tr *transformer) SetMethodArgs(contractAddr string, filterSet []string) { + tr.MethodArgs[strings.ToLower(contractAddr)] = filterSet +} + +// Used to set the block range to watch for a given address +func (tr *transformer) SetStartingBlock(contractAddr string, start int64) { + tr.ContractStart[strings.ToLower(contractAddr)] = start +} + +// Used to set whether or not to persist an account address list +func (tr *transformer) SetCreateAddrList(contractAddr string, on bool) { + tr.CreateAddrList[strings.ToLower(contractAddr)] = on +} + +// Used to set whether or not to persist an hash list +func (tr *transformer) SetCreateHashList(contractAddr string, on bool) { + tr.CreateHashList[strings.ToLower(contractAddr)] = on +} + +// Used to turn method piping on for a contract +func (tr *transformer) SetPiping(contractAddr string, on bool) { + tr.Piping[strings.ToLower(contractAddr)] = on +} diff --git a/pkg/omni/light/transformer/transformer_suite_test.go b/pkg/omni/light/transformer/transformer_suite_test.go new file mode 100644 index 00000000..52ab1197 --- /dev/null +++ b/pkg/omni/light/transformer/transformer_suite_test.go @@ -0,0 +1,35 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package transformer_test + +import ( + "io/ioutil" + "log" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestTransformer(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Light Transformer Suite Test") +} + +var _ = BeforeSuite(func() { + log.SetOutput(ioutil.Discard) +}) diff --git a/pkg/omni/light/transformer/transformer_test.go b/pkg/omni/light/transformer/transformer_test.go new file mode 100644 index 00000000..db805532 --- /dev/null +++ b/pkg/omni/light/transformer/transformer_test.go @@ -0,0 +1,507 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package transformer_test + +import ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/common" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories" + "github.com/vulcanize/vulcanizedb/pkg/omni/light/transformer" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks" +) + +var _ = Describe("Transformer", func() { + var db *postgres.DB + var err error + var blockChain core.BlockChain + var headerRepository repositories.HeaderRepository + var headerID, headerID2 int64 + var ensAddr = strings.ToLower(constants.EnsContractAddress) + var tusdAddr = strings.ToLower(constants.TusdContractAddress) + + BeforeEach(func() { + db, blockChain = test_helpers.SetupDBandBC() + headerRepository = repositories.NewHeaderRepository(db) + }) + + AfterEach(func() { + test_helpers.TearDown(db) + }) + + Describe("SetEvents", func() { + It("Sets which events to watch from the given contract address", func() { + watchedEvents := []string{"Transfer", "Mint"} + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.TusdContractAddress, watchedEvents) + Expect(t.WatchedEvents[tusdAddr]).To(Equal(watchedEvents)) + }) + }) + + Describe("SetEventAddrs", func() { + It("Sets which account addresses to watch events for", func() { + eventAddrs := []string{"test1", "test2"} + t := transformer.NewTransformer("", blockChain, db) + t.SetEventArgs(constants.TusdContractAddress, eventAddrs) + Expect(t.EventArgs[tusdAddr]).To(Equal(eventAddrs)) + }) + }) + + Describe("SetMethods", func() { + It("Sets which methods to poll at the given contract address", func() { + watchedMethods := []string{"balanceOf", "totalSupply"} + t := transformer.NewTransformer("", blockChain, db) + t.SetMethods(constants.TusdContractAddress, watchedMethods) + Expect(t.WantedMethods[tusdAddr]).To(Equal(watchedMethods)) + }) + }) + + Describe("SetMethodAddrs", func() { + It("Sets which account addresses to poll methods against", func() { + methodAddrs := []string{"test1", "test2"} + t := transformer.NewTransformer("", blockChain, db) + t.SetMethodArgs(constants.TusdContractAddress, methodAddrs) + Expect(t.MethodArgs[tusdAddr]).To(Equal(methodAddrs)) + }) + }) + + Describe("SetStartingBlock", func() { + It("Sets the block range that the contract should be watched within", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetStartingBlock(constants.TusdContractAddress, 11) + Expect(t.ContractStart[tusdAddr]).To(Equal(int64(11))) + }) + }) + + Describe("SetCreateAddrList", func() { + It("Sets the block range that the contract should be watched within", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetCreateAddrList(constants.TusdContractAddress, true) + Expect(t.CreateAddrList[tusdAddr]).To(Equal(true)) + }) + }) + + Describe("SetCreateHashList", func() { + It("Sets the block range that the contract should be watched within", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetCreateHashList(constants.TusdContractAddress, true) + Expect(t.CreateHashList[tusdAddr]).To(Equal(true)) + }) + }) + + Describe("Init", func() { + It("Initializes transformer's contract objects", func() { + headerRepository.CreateOrUpdateHeader(mocks.MockHeader1) + headerRepository.CreateOrUpdateHeader(mocks.MockHeader3) + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + + c, ok := t.Contracts[tusdAddr] + Expect(ok).To(Equal(true)) + + Expect(c.StartingBlock).To(Equal(int64(6194632))) + Expect(c.LastBlock).To(Equal(int64(-1))) + Expect(c.Abi).To(Equal(constants.TusdAbiString)) + Expect(c.Name).To(Equal("TrueUSD")) + Expect(c.Address).To(Equal(tusdAddr)) + }) + + It("Fails to initialize if first and most recent block numbers cannot be fetched from vDB headers table", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) + err = t.Init() + Expect(err).To(HaveOccurred()) + }) + + It("Does nothing if watched events are unset", func() { + headerRepository.CreateOrUpdateHeader(mocks.MockHeader1) + headerRepository.CreateOrUpdateHeader(mocks.MockHeader3) + t := transformer.NewTransformer("", blockChain, db) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + + _, ok := t.Contracts[tusdAddr] + Expect(ok).To(Equal(false)) + }) + }) + + Describe("Execute- against TrueUSD contract", func() { + BeforeEach(func() { + header1, err := blockChain.GetHeaderByNumber(6791668) + Expect(err).ToNot(HaveOccurred()) + header2, err := blockChain.GetHeaderByNumber(6791669) + Expect(err).ToNot(HaveOccurred()) + header3, err := blockChain.GetHeaderByNumber(6791670) + Expect(err).ToNot(HaveOccurred()) + headerRepository.CreateOrUpdateHeader(header1) + headerID, err = headerRepository.CreateOrUpdateHeader(header2) + Expect(err).ToNot(HaveOccurred()) + headerRepository.CreateOrUpdateHeader(header3) + }) + + It("Transforms watched contract data into custom repositories", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) + t.SetMethods(constants.TusdContractAddress, nil) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + + log := test_helpers.LightTransferLog{} + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.transfer_event", tusdAddr)).StructScan(&log) + Expect(err).ToNot(HaveOccurred()) + // We don't know vulcID, so compare individual fields instead of complete structures + Expect(log.HeaderID).To(Equal(headerID)) + Expect(log.From).To(Equal("0x1062a747393198f70F71ec65A582423Dba7E5Ab3")) + Expect(log.To).To(Equal("0x2930096dB16b4A44Ecd4084EA4bd26F7EeF1AEf0")) + Expect(log.Value).To(Equal("9998940000000000000000")) + }) + + It("Keeps track of contract-related addresses while transforming event data if they need to be used for later method polling", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) + t.SetMethods(constants.TusdContractAddress, []string{"balanceOf"}) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + c, ok := t.Contracts[tusdAddr] + Expect(ok).To(Equal(true)) + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + Expect(len(c.EmittedAddrs)).To(Equal(4)) + Expect(len(c.EmittedHashes)).To(Equal(0)) + + b, ok := c.EmittedAddrs[common.HexToAddress("0x1062a747393198f70F71ec65A582423Dba7E5Ab3")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + b, ok = c.EmittedAddrs[common.HexToAddress("0x2930096dB16b4A44Ecd4084EA4bd26F7EeF1AEf0")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + b, ok = c.EmittedAddrs[common.HexToAddress("0x571A326f5B15E16917dC17761c340c1ec5d06f6d")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + b, ok = c.EmittedAddrs[common.HexToAddress("0xFBb1b73C4f0BDa4f67dcA266ce6Ef42f520fBB98")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + _, ok = c.EmittedAddrs[common.HexToAddress("0x09BbBBE21a5975cAc061D82f7b843b1234567890")] + Expect(ok).To(Equal(false)) + + _, ok = c.EmittedAddrs[common.HexToAddress("0x")] + Expect(ok).To(Equal(false)) + + _, ok = c.EmittedAddrs[""] + Expect(ok).To(Equal(false)) + + _, ok = c.EmittedAddrs[common.HexToAddress("0x09THISE21a5IS5cFAKE1D82fAND43bCE06MADEUP")] + Expect(ok).To(Equal(false)) + }) + + It("Polls given methods using generated token holder address", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) + t.SetMethods(constants.TusdContractAddress, []string{"balanceOf"}) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + + res := test_helpers.BalanceOf{} + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method WHERE who_ = '0x1062a747393198f70F71ec65A582423Dba7E5Ab3' AND block = '6791669'", tusdAddr)).StructScan(&res) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Balance).To(Equal("55849938025000000000000")) + Expect(res.TokenName).To(Equal("TrueUSD")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method WHERE who_ = '0x09BbBBE21a5975cAc061D82f7b843b1234567890' AND block = '6791669'", tusdAddr)).StructScan(&res) + Expect(err).To(HaveOccurred()) + }) + + It("Fails if initialization has not been done", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) + t.SetMethods(constants.TusdContractAddress, nil) + err = t.Execute() + Expect(err).To(HaveOccurred()) + }) + }) + + Describe("Execute- against ENS registry contract", func() { + BeforeEach(func() { + header1, err := blockChain.GetHeaderByNumber(6885695) + Expect(err).ToNot(HaveOccurred()) + header2, err := blockChain.GetHeaderByNumber(6885696) + Expect(err).ToNot(HaveOccurred()) + header3, err := blockChain.GetHeaderByNumber(6885697) + Expect(err).ToNot(HaveOccurred()) + headerRepository.CreateOrUpdateHeader(header1) + headerID, err = headerRepository.CreateOrUpdateHeader(header2) + Expect(err).ToNot(HaveOccurred()) + headerRepository.CreateOrUpdateHeader(header3) + }) + + It("Transforms watched contract data into custom repositories", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) + t.SetMethods(constants.EnsContractAddress, nil) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + + log := test_helpers.LightNewOwnerLog{} + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.newowner_event", ensAddr)).StructScan(&log) + Expect(err).ToNot(HaveOccurred()) + // We don't know vulcID, so compare individual fields instead of complete structures + Expect(log.HeaderID).To(Equal(headerID)) + Expect(log.Node).To(Equal("0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae")) + Expect(log.Label).To(Equal("0x95832c7a47ff8a7840e28b78ce695797aaf402b1c186bad9eca28842625b5047")) + Expect(log.Owner).To(Equal("0x6090A6e47849629b7245Dfa1Ca21D94cd15878Ef")) + }) + + It("Keeps track of contract-related hashes while transforming event data if they need to be used for later method polling", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) + t.SetMethods(constants.EnsContractAddress, []string{"owner"}) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + c, ok := t.Contracts[ensAddr] + Expect(ok).To(Equal(true)) + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + Expect(len(c.EmittedHashes)).To(Equal(2)) + Expect(len(c.EmittedAddrs)).To(Equal(0)) + + b, ok := c.EmittedHashes[common.HexToHash("0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + b, ok = c.EmittedHashes[common.HexToHash("0x95832c7a47ff8a7840e28b78ce695797aaf402b1c186bad9eca28842625b5047")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + // Doesn't keep track of address since it wouldn't be used in calling the 'owner' method + _, ok = c.EmittedAddrs[common.HexToAddress("0x6090A6e47849629b7245Dfa1Ca21D94cd15878Ef")] + Expect(ok).To(Equal(false)) + }) + + It("Polls given method using list of collected hashes", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) + t.SetMethods(constants.EnsContractAddress, []string{"owner"}) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + + res := test_helpers.Owner{} + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae' AND block = '6885696'", ensAddr)).StructScan(&res) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Address).To(Equal("0x6090A6e47849629b7245Dfa1Ca21D94cd15878Ef")) + Expect(res.TokenName).To(Equal("")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x95832c7a47ff8a7840e28b78ce695797aaf402b1c186bad9eca28842625b5047' AND block = '6885696'", ensAddr)).StructScan(&res) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Address).To(Equal("0x0000000000000000000000000000000000000000")) + Expect(res.TokenName).To(Equal("")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x95832c7a47ff8a7840e28b78ceMADEUPaaf4HASHc186badTHIS288IS625bFAKE' AND block = '6885696'", ensAddr)).StructScan(&res) + Expect(err).To(HaveOccurred()) + }) + + It("It does not persist events if they do not pass the emitted arg filter", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) + t.SetMethods(constants.EnsContractAddress, nil) + t.SetEventArgs(constants.EnsContractAddress, []string{"fake_filter_value"}) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + + log := test_helpers.LightNewOwnerLog{} + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.newowner_event", ensAddr)).StructScan(&log) + Expect(err).To(HaveOccurred()) + }) + + It("If a method arg filter is applied, only those arguments are used in polling", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) + t.SetMethods(constants.EnsContractAddress, []string{"owner"}) + t.SetMethodArgs(constants.EnsContractAddress, []string{"0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae"}) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + + res := test_helpers.Owner{} + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae' AND block = '6885696'", ensAddr)).StructScan(&res) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Address).To(Equal("0x6090A6e47849629b7245Dfa1Ca21D94cd15878Ef")) + Expect(res.TokenName).To(Equal("")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x95832c7a47ff8a7840e28b78ce695797aaf402b1c186bad9eca28842625b5047' AND block = '6885696'", ensAddr)).StructScan(&res) + Expect(err).To(HaveOccurred()) + }) + }) + + Describe("Execute- against both ENS and TrueUSD", func() { + BeforeEach(func() { + header1, err := blockChain.GetHeaderByNumber(6791668) + Expect(err).ToNot(HaveOccurred()) + header2, err := blockChain.GetHeaderByNumber(6791669) + Expect(err).ToNot(HaveOccurred()) + header3, err := blockChain.GetHeaderByNumber(6791670) + Expect(err).ToNot(HaveOccurred()) + header4, err := blockChain.GetHeaderByNumber(6885695) + Expect(err).ToNot(HaveOccurred()) + header5, err := blockChain.GetHeaderByNumber(6885696) + Expect(err).ToNot(HaveOccurred()) + header6, err := blockChain.GetHeaderByNumber(6885697) + Expect(err).ToNot(HaveOccurred()) + headerRepository.CreateOrUpdateHeader(header1) + headerID, err = headerRepository.CreateOrUpdateHeader(header2) + Expect(err).ToNot(HaveOccurred()) + headerRepository.CreateOrUpdateHeader(header3) + headerRepository.CreateOrUpdateHeader(header4) + headerID2, err = headerRepository.CreateOrUpdateHeader(header5) + Expect(err).ToNot(HaveOccurred()) + headerRepository.CreateOrUpdateHeader(header6) + }) + + It("Transforms watched contract data into custom repositories", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) + t.SetMethods(constants.EnsContractAddress, nil) + t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) + t.SetMethods(constants.TusdContractAddress, nil) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + + newOwnerLog := test_helpers.LightNewOwnerLog{} + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.newowner_event", ensAddr)).StructScan(&newOwnerLog) + Expect(err).ToNot(HaveOccurred()) + // We don't know vulcID, so compare individual fields instead of complete structures + Expect(newOwnerLog.HeaderID).To(Equal(headerID2)) + Expect(newOwnerLog.Node).To(Equal("0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae")) + Expect(newOwnerLog.Label).To(Equal("0x95832c7a47ff8a7840e28b78ce695797aaf402b1c186bad9eca28842625b5047")) + Expect(newOwnerLog.Owner).To(Equal("0x6090A6e47849629b7245Dfa1Ca21D94cd15878Ef")) + + transferLog := test_helpers.LightTransferLog{} + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.transfer_event", tusdAddr)).StructScan(&transferLog) + Expect(err).ToNot(HaveOccurred()) + // We don't know vulcID, so compare individual fields instead of complete structures + Expect(transferLog.HeaderID).To(Equal(headerID)) + Expect(transferLog.From).To(Equal("0x1062a747393198f70F71ec65A582423Dba7E5Ab3")) + Expect(transferLog.To).To(Equal("0x2930096dB16b4A44Ecd4084EA4bd26F7EeF1AEf0")) + Expect(transferLog.Value).To(Equal("9998940000000000000000")) + }) + + It("Keeps track of contract-related hashes and addresses while transforming event data if they need to be used for later method polling", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) + t.SetMethods(constants.EnsContractAddress, []string{"owner"}) + t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) + t.SetMethods(constants.TusdContractAddress, []string{"balanceOf"}) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + ens, ok := t.Contracts[ensAddr] + Expect(ok).To(Equal(true)) + tusd, ok := t.Contracts[tusdAddr] + Expect(ok).To(Equal(true)) + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + Expect(len(ens.EmittedHashes)).To(Equal(2)) + Expect(len(ens.EmittedAddrs)).To(Equal(0)) + Expect(len(tusd.EmittedAddrs)).To(Equal(4)) + Expect(len(tusd.EmittedHashes)).To(Equal(0)) + + b, ok := ens.EmittedHashes[common.HexToHash("0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + b, ok = ens.EmittedHashes[common.HexToHash("0x95832c7a47ff8a7840e28b78ce695797aaf402b1c186bad9eca28842625b5047")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + b, ok = tusd.EmittedAddrs[common.HexToAddress("0x1062a747393198f70F71ec65A582423Dba7E5Ab3")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + b, ok = tusd.EmittedAddrs[common.HexToAddress("0x2930096dB16b4A44Ecd4084EA4bd26F7EeF1AEf0")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + b, ok = tusd.EmittedAddrs[common.HexToAddress("0x571A326f5B15E16917dC17761c340c1ec5d06f6d")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + b, ok = tusd.EmittedAddrs[common.HexToAddress("0xFBb1b73C4f0BDa4f67dcA266ce6Ef42f520fBB98")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + }) + + It("Polls given methods for each contract, using list of collected values", func() { + t := transformer.NewTransformer("", blockChain, db) + t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) + t.SetMethods(constants.EnsContractAddress, []string{"owner"}) + t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) + t.SetMethods(constants.TusdContractAddress, []string{"balanceOf"}) + err = t.Init() + Expect(err).ToNot(HaveOccurred()) + err = t.Execute() + Expect(err).ToNot(HaveOccurred()) + + owner := test_helpers.Owner{} + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae' AND block = '6885696'", ensAddr)).StructScan(&owner) + Expect(err).ToNot(HaveOccurred()) + Expect(owner.Address).To(Equal("0x6090A6e47849629b7245Dfa1Ca21D94cd15878Ef")) + Expect(owner.TokenName).To(Equal("")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x95832c7a47ff8a7840e28b78ce695797aaf402b1c186bad9eca28842625b5047' AND block = '6885696'", ensAddr)).StructScan(&owner) + Expect(err).ToNot(HaveOccurred()) + Expect(owner.Address).To(Equal("0x0000000000000000000000000000000000000000")) + Expect(owner.TokenName).To(Equal("")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x95832c7a47ff8a7840e28b78ceMADEUPaaf4HASHc186badTHIS288IS625bFAKE' AND block = '6885696'", ensAddr)).StructScan(&owner) + Expect(err).To(HaveOccurred()) + + bal := test_helpers.BalanceOf{} + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method WHERE who_ = '0x1062a747393198f70F71ec65A582423Dba7E5Ab3' AND block = '6791669'", tusdAddr)).StructScan(&bal) + Expect(err).ToNot(HaveOccurred()) + Expect(bal.Balance).To(Equal("55849938025000000000000")) + Expect(bal.TokenName).To(Equal("TrueUSD")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method WHERE who_ = '0x09BbBBE21a5975cAc061D82f7b843b1234567890' AND block = '6791669'", tusdAddr)).StructScan(&bal) + Expect(err).To(HaveOccurred()) + }) + }) +}) diff --git a/pkg/omni/shared/constants/constants.go b/pkg/omni/shared/constants/constants.go new file mode 100644 index 00000000..594e0fce --- /dev/null +++ b/pkg/omni/shared/constants/constants.go @@ -0,0 +1,127 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package constants + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/filters" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers" +) + +// Event enums +type Event int + +const ( + TransferEvent Event = 0 + ApprovalEvent Event = 1 + BurnEvent Event = 2 + MintEvent Event = 3 + NewOwnerEvent Event = 4 +) + +func (e Event) String() string { + strings := [...]string{ + "Transfer", + "Approval", + "Burn", + "Mint", + "NewOwner", + } + + if e < TransferEvent || e > NewOwnerEvent { + return "Unknown" + } + + return strings[e] +} + +func (e Event) Signature() string { + strings := [...]string{ + helpers.GenerateSignature("Transfer(address,address,uint256)"), + helpers.GenerateSignature("Approval(address,address,uint256)"), + helpers.GenerateSignature("Burn(address,uint256)"), + helpers.GenerateSignature("Mint(address,uint256)"), + helpers.GenerateSignature("NewOwner(bytes32,bytes32,address)"), + } + + if e < TransferEvent || e > NewOwnerEvent { + return "Unknown" + } + + return strings[e] +} + +// Contract Addresses +var DaiContractAddress = "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359" +var TusdContractAddress = "0x8dd5fbCe2F6a956C3022bA3663759011Dd51e73E" +var EnsContractAddress = "0x314159265dD8dbb310642f98f50C066173C1259b" +var PublicResolverAddress = "0x1da022710dF5002339274AaDEe8D58218e9D6AB5" + +// Contract Owner +var DaiContractOwner = "0x0000000000000000000000000000000000000000" +var TusdContractOwner = "0x9978d2d229a69b3aef93420d132ab22b44e3578f" + +// Contract Abis +var DaiAbiString = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"mint","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"burn","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"name_","type":"bytes32"}],"name":"setName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"src","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"stopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"burn","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"mint","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"push","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"move","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"start","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"src","type":"address"},{"name":"guy","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"wad","type":"uint256"}],"name":"pull","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"symbol_","type":"bytes32"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"},{"anonymous":true,"inputs":[{"indexed":true,"name":"sig","type":"bytes4"},{"indexed":true,"name":"guy","type":"address"},{"indexed":true,"name":"foo","type":"bytes32"},{"indexed":true,"name":"bar","type":"bytes32"},{"indexed":false,"name":"wad","type":"uint256"},{"indexed":false,"name":"fax","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"}]` + +var TusdAbiString = `[{"constant":true,"inputs":[],"name":"burnMin","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"delegateAllowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"burnFeeFlat","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_canReceiveMintWhiteList","type":"address"},{"name":"_canBurnWhiteList","type":"address"},{"name":"_blackList","type":"address"},{"name":"_noFeesList","type":"address"}],"name":"setLists","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"token","type":"address"}],"name":"reclaimToken","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newContract","type":"address"}],"name":"delegateToNewContract","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_transferFeeNumerator","type":"uint80"},{"name":"_transferFeeDenominator","type":"uint80"},{"name":"_mintFeeNumerator","type":"uint80"},{"name":"_mintFeeDenominator","type":"uint80"},{"name":"_mintFeeFlat","type":"uint256"},{"name":"_burnFeeNumerator","type":"uint80"},{"name":"_burnFeeDenominator","type":"uint80"},{"name":"_burnFeeFlat","type":"uint256"}],"name":"changeStakingFees","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"canReceiveMintWhiteList","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"delegatedFrom","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"},{"name":"origSender","type":"address"}],"name":"delegateApprove","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"contractAddr","type":"address"}],"name":"reclaimContract","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"allowances","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"mint","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"uint256"}],"name":"burn","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"delegateBalanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"origSender","type":"address"}],"name":"delegateTransferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"claimOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"sheet","type":"address"}],"name":"setBalanceSheet","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"addedValue","type":"uint256"},{"name":"origSender","type":"address"}],"name":"delegateIncreaseApproval","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"burnFeeNumerator","outputs":[{"name":"","type":"uint80"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"canBurnWhiteList","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"burnMax","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"mintFeeDenominator","outputs":[{"name":"","type":"uint80"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"staker","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"setDelegatedFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"subtractedValue","type":"uint256"}],"name":"decreaseApproval","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"noFeesList","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newMin","type":"uint256"},{"name":"newMax","type":"uint256"}],"name":"changeBurnBounds","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"delegateTotalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"balances","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_symbol","type":"string"}],"name":"changeName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"mintFeeNumerator","outputs":[{"name":"","type":"uint80"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"transferFeeNumerator","outputs":[{"name":"","type":"uint80"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"subtractedValue","type":"uint256"},{"name":"origSender","type":"address"}],"name":"delegateDecreaseApproval","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"origSender","type":"address"}],"name":"delegateTransfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"reclaimEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newStaker","type":"address"}],"name":"changeStaker","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"account","type":"address"}],"name":"wipeBlacklistedAccount","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"from_","type":"address"},{"name":"value_","type":"uint256"},{"name":"data_","type":"bytes"}],"name":"tokenFallback","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"burnFeeDenominator","outputs":[{"name":"","type":"uint80"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"blackList","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"transferFeeDenominator","outputs":[{"name":"","type":"uint80"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"mintFeeFlat","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"addedValue","type":"uint256"}],"name":"increaseApproval","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"pendingOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"sheet","type":"address"}],"name":"setAllowanceSheet","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":false,"stateMutability":"nonpayable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newMin","type":"uint256"},{"indexed":false,"name":"newMax","type":"uint256"}],"name":"ChangeBurnBoundsEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"balance","type":"uint256"}],"name":"WipedAccount","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"newContract","type":"address"}],"name":"DelegatedTo","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"burner","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]` + +var ENSAbiString = `[{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"label","type":"bytes32"},{"name":"owner","type":"address"}],"name":"setSubnodeOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"ttl","type":"uint64"}],"name":"setTTL","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"ttl","outputs":[{"name":"","type":"uint64"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"resolver","type":"address"}],"name":"setResolver","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"owner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":true,"name":"label","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"NewOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"resolver","type":"address"}],"name":"NewResolver","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"ttl","type":"uint64"}],"name":"NewTTL","type":"event"}]` + +// Look-up table for ABI strings +var Abis = map[common.Address]string{ + common.HexToAddress("0x314159265dD8dbb310642f98f50C066173C1259b"): ENSAbiString, + common.HexToAddress("0x8dd5fbCe2F6a956C3022bA3663759011Dd51e73E"): TusdAbiString, + common.HexToAddress("0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359"): DaiAbiString, +} + +// Filters +// To add additional filter parameters, filter by other Topics e.g. for a Transfer event filter Topics[1] to filter for a specific 'from' address +var DaiERC20Filters = []filters.LogFilter{ + { + Name: TransferEvent.String(), + FromBlock: 4752008, + ToBlock: -1, + Address: DaiContractAddress, + Topics: core.Topics{TransferEvent.Signature()}, + }, + { + Name: ApprovalEvent.String(), + FromBlock: 4752008, + ToBlock: -1, + Address: DaiContractAddress, + Topics: core.Topics{ApprovalEvent.Signature()}, + }, +} + +var TusdGenericFilters = []filters.LogFilter{ + { + Name: BurnEvent.String(), + FromBlock: 5197514, + ToBlock: -1, + Address: TusdContractAddress, + Topics: core.Topics{BurnEvent.Signature()}, + }, + { + Name: MintEvent.String(), + FromBlock: 5197514, + ToBlock: -1, + Address: TusdContractAddress, + Topics: core.Topics{MintEvent.Signature()}, + }, +} diff --git a/pkg/omni/shared/constants/interface.go b/pkg/omni/shared/constants/interface.go new file mode 100644 index 00000000..a1bf56b0 --- /dev/null +++ b/pkg/omni/shared/constants/interface.go @@ -0,0 +1,127 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package constants + +import ( + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// Basic abi needed to check which interfaces are adhered to +var SupportsInterfaceABI = `[{"constant":true,"inputs":[{"name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}]` + +// Individual event interfaces for constructing ABI from +var SupportsInterace = `{"constant":true,"inputs":[{"name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}` +var AddrChangeInterface = `{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"a","type":"address"}],"name":"AddrChanged","type":"event"}` +var ContentChangeInterface = `{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"hash","type":"bytes32"}],"name":"ContentChanged","type":"event"}` +var NameChangeInterface = `{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"name","type":"string"}],"name":"NameChanged","type":"event"}` +var AbiChangeInterface = `{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":true,"name":"contentType","type":"uint256"}],"name":"ABIChanged","type":"event"}` +var PubkeyChangeInterface = `{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"x","type":"bytes32"},{"indexed":false,"name":"y","type":"bytes32"}],"name":"PubkeyChanged","type":"event"}` +var TextChangeInterface = `{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"indexedKey","type":"string"},{"indexed":false,"name":"key","type":"string"}],"name":"TextChanged","type":"event"}` +var MultihashChangeInterface = `{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"hash","type":"bytes"}],"name":"MultihashChanged","type":"event"}` +var ContenthashChangeInterface = `{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"hash","type":"bytes"}],"name":"ContenthashChanged","type":"event"}` + +var StartingBlock = int64(3648359) + +// Resolver interface signatures +type Interface int + +const ( + MetaSig Interface = iota + AddrChangeSig + ContentChangeSig + NameChangeSig + AbiChangeSig + PubkeyChangeSig + TextChangeSig + MultihashChangeSig + ContentHashChangeSig +) + +func (e Interface) Hex() string { + strings := [...]string{ + "0x01ffc9a7", + "0x3b3b57de", + "0xd8389dc5", + "0x691f3431", + "0x2203ab56", + "0xc8690233", + "0x59d1d43c", + "0xe89401a1", + "0xbc1c58d1", + } + + if e < MetaSig || e > ContentHashChangeSig { + return "Unknown" + } + + return strings[e] +} + +func (e Interface) Bytes() [4]uint8 { + if e < MetaSig || e > ContentHashChangeSig { + return [4]byte{} + } + + str := e.Hex() + by, _ := hexutil.Decode(str) + var byArray [4]uint8 + for i := 0; i < 4; i++ { + byArray[i] = by[i] + } + + return byArray +} + +func (e Interface) EventSig() string { + strings := [...]string{ + "", + "AddrChanged(bytes32,address)", + "ContentChanged(bytes32,bytes32)", + "NameChanged(bytes32,string)", + "ABIChanged(bytes32,uint256)", + "PubkeyChanged(bytes32,bytes32,bytes32)", + "TextChanged(bytes32,string,string)", + "MultihashChanged(bytes32,bytes)", + "ContenthashChanged(bytes32,bytes)", + } + + if e < MetaSig || e > ContentHashChangeSig { + return "Unknown" + } + + return strings[e] +} + +func (e Interface) MethodSig() string { + strings := [...]string{ + "supportsInterface(bytes4)", + "addr(bytes32)", + "content(bytes32)", + "name(bytes32)", + "ABI(bytes32,uint256)", + "pubkey(bytes32)", + "text(bytes32,string)", + "multihash(bytes32)", + "setContenthash(bytes32,bytes)", + } + + if e < MetaSig || e > ContentHashChangeSig { + return "Unknown" + } + + return strings[e] +} diff --git a/pkg/omni/shared/contract/contract.go b/pkg/omni/shared/contract/contract.go new file mode 100644 index 00000000..a916e734 --- /dev/null +++ b/pkg/omni/shared/contract/contract.go @@ -0,0 +1,176 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package contract + +import ( + "errors" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/filters" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" +) + +// Contract object to hold our contract data +type Contract struct { + Name string // Name of the contract + Address string // Address of the contract + Network string // Network on which the contract is deployed; default empty "" is Ethereum mainnet + StartingBlock int64 // Starting block of the contract + LastBlock int64 // Most recent block on the network + Abi string // Abi string + ParsedAbi abi.ABI // Parsed abi + Events map[string]types.Event // List of events to watch + Methods []types.Method // List of methods to poll + Filters map[string]filters.LogFilter // Map of event filters to their event names; used only for full sync watcher + FilterArgs map[string]bool // User-input list of values to filter event logs for + MethodArgs map[string]bool // User-input list of values to limit method polling to + EmittedAddrs map[interface{}]bool // List of all unique addresses collected from converted event logs + EmittedHashes map[interface{}]bool // List of all unique hashes collected from converted event logs + CreateAddrList bool // Whether or not to persist address list to postgres + CreateHashList bool // Whether or not to persist hash list to postgres + Piping bool // Whether or not to pipe method results forward as arguments to subsequent methods +} + +// If we will be calling methods that use addr, hash, or byte arrays +// as arguments then we initialize maps to hold these types of values +func (c Contract) Init() *Contract { + for _, method := range c.Methods { + for _, arg := range method.Args { + switch arg.Type.T { + case abi.AddressTy: + c.EmittedAddrs = map[interface{}]bool{} + case abi.HashTy, abi.BytesTy, abi.FixedBytesTy: + c.EmittedHashes = map[interface{}]bool{} + default: + } + } + } + + // If we are creating an address list in postgres + // we initialize the map despite what method call, if any + if c.CreateAddrList { + c.EmittedAddrs = map[interface{}]bool{} + } + + return &c +} + +// Use contract info to generate event filters - full sync omni watcher only +func (c *Contract) GenerateFilters() error { + c.Filters = map[string]filters.LogFilter{} + + for name, event := range c.Events { + c.Filters[name] = filters.LogFilter{ + Name: event.Name, + FromBlock: c.StartingBlock, + ToBlock: -1, + Address: common.HexToAddress(c.Address).Hex(), + Topics: core.Topics{event.Sig().Hex()}, + } + } + // If no filters were generated, throw an error (no point in continuing with this contract) + if len(c.Filters) == 0 { + return errors.New("error: no filters created") + } + + return nil +} + +// Returns true if address is in list of arguments to +// filter events for or if no filtering is specified +func (c *Contract) WantedEventArg(arg string) bool { + if c.FilterArgs == nil { + return false + } else if len(c.FilterArgs) == 0 { + return true + } else if a, ok := c.FilterArgs[arg]; ok { + return a + } + + return false +} + +// Returns true if address is in list of arguments to +// poll methods with or if no filtering is specified +func (c *Contract) WantedMethodArg(arg interface{}) bool { + if c.MethodArgs == nil { + return false + } else if len(c.MethodArgs) == 0 { + return true + } + + // resolve interface to one of the three types we handle as arguments + str := StringifyArg(arg) + + // See if it's hex string has been filtered for + if a, ok := c.MethodArgs[str]; ok { + return a + } + + return false +} + +// Returns true if any mapping value matches filtered for address or if no filter exists +// Used to check if an event log name-value mapping should be filtered or not +func (c *Contract) PassesEventFilter(args map[string]string) bool { + for _, arg := range args { + if c.WantedEventArg(arg) { + return true + } + } + + return false +} + +// Add event emitted address to our list if it passes filter and method polling is on +func (c *Contract) AddEmittedAddr(addresses ...interface{}) { + for _, addr := range addresses { + if c.WantedMethodArg(addr) && c.Methods != nil { + c.EmittedAddrs[addr] = true + } + } +} + +// Add event emitted hash to our list if it passes filter and method polling is on +func (c *Contract) AddEmittedHash(hashes ...interface{}) { + for _, hash := range hashes { + if c.WantedMethodArg(hash) && c.Methods != nil { + c.EmittedHashes[hash] = true + } + } +} + +func StringifyArg(arg interface{}) (str string) { + switch arg.(type) { + case string: + str = arg.(string) + case common.Address: + a := arg.(common.Address) + str = a.String() + case common.Hash: + a := arg.(common.Hash) + str = a.String() + case []byte: + a := arg.([]byte) + str = hexutil.Encode(a) + } + + return +} diff --git a/pkg/omni/shared/contract/contract_suite_test.go b/pkg/omni/shared/contract/contract_suite_test.go new file mode 100644 index 00000000..9ef74e40 --- /dev/null +++ b/pkg/omni/shared/contract/contract_suite_test.go @@ -0,0 +1,35 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package contract_test + +import ( + "io/ioutil" + "log" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestContract(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Contract Suite Test") +} + +var _ = BeforeSuite(func() { + log.SetOutput(ioutil.Discard) +}) diff --git a/pkg/omni/shared/contract/contract_test.go b/pkg/omni/shared/contract/contract_test.go new file mode 100644 index 00000000..a9164f94 --- /dev/null +++ b/pkg/omni/shared/contract/contract_test.go @@ -0,0 +1,236 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package contract_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" +) + +var _ = Describe("Contract", func() { + var err error + var info *contract.Contract + var wantedEvents = []string{"Transfer", "Approval"} + + Describe("GenerateFilters", func() { + + It("Generates filters from contract data", func() { + info = test_helpers.SetupTusdContract(wantedEvents, nil) + err = info.GenerateFilters() + Expect(err).ToNot(HaveOccurred()) + + val, ok := info.Filters["Transfer"] + Expect(ok).To(Equal(true)) + Expect(val).To(Equal(mocks.ExpectedTransferFilter)) + + val, ok = info.Filters["Approval"] + Expect(ok).To(Equal(true)) + Expect(val).To(Equal(mocks.ExpectedApprovalFilter)) + + val, ok = info.Filters["Mint"] + Expect(ok).To(Equal(false)) + + }) + + It("Fails with an empty contract", func() { + info = &contract.Contract{} + err = info.GenerateFilters() + Expect(err).To(HaveOccurred()) + }) + }) + + Describe("IsEventAddr", func() { + + BeforeEach(func() { + info = &contract.Contract{} + info.MethodArgs = map[string]bool{} + info.FilterArgs = map[string]bool{} + }) + + It("Returns true if address is in event address filter list", func() { + info.FilterArgs["testAddress1"] = true + info.FilterArgs["testAddress2"] = true + + is := info.WantedEventArg("testAddress1") + Expect(is).To(Equal(true)) + is = info.WantedEventArg("testAddress2") + Expect(is).To(Equal(true)) + + info.MethodArgs["testAddress3"] = true + is = info.WantedEventArg("testAddress3") + Expect(is).To(Equal(false)) + }) + + It("Returns true if event address filter is empty (no filter)", func() { + is := info.WantedEventArg("testAddress1") + Expect(is).To(Equal(true)) + is = info.WantedEventArg("testAddress2") + Expect(is).To(Equal(true)) + }) + + It("Returns false if address is not in event address filter list", func() { + info.FilterArgs["testAddress1"] = true + info.FilterArgs["testAddress2"] = true + + is := info.WantedEventArg("testAddress3") + Expect(is).To(Equal(false)) + }) + + It("Returns false if event address filter is nil (block all)", func() { + info.FilterArgs = nil + + is := info.WantedEventArg("testAddress1") + Expect(is).To(Equal(false)) + is = info.WantedEventArg("testAddress2") + Expect(is).To(Equal(false)) + }) + }) + + Describe("IsMethodAddr", func() { + BeforeEach(func() { + info = &contract.Contract{} + info.MethodArgs = map[string]bool{} + info.FilterArgs = map[string]bool{} + }) + + It("Returns true if address is in method address filter list", func() { + info.MethodArgs["testAddress1"] = true + info.MethodArgs["testAddress2"] = true + + is := info.WantedMethodArg("testAddress1") + Expect(is).To(Equal(true)) + is = info.WantedMethodArg("testAddress2") + Expect(is).To(Equal(true)) + + info.FilterArgs["testAddress3"] = true + is = info.WantedMethodArg("testAddress3") + Expect(is).To(Equal(false)) + }) + + It("Returns true if method address filter list is empty (no filter)", func() { + is := info.WantedMethodArg("testAddress1") + Expect(is).To(Equal(true)) + is = info.WantedMethodArg("testAddress2") + Expect(is).To(Equal(true)) + }) + + It("Returns false if address is not in method address filter list", func() { + info.MethodArgs["testAddress1"] = true + info.MethodArgs["testAddress2"] = true + + is := info.WantedMethodArg("testAddress3") + Expect(is).To(Equal(false)) + }) + + It("Returns false if method address filter list is nil (block all)", func() { + info.MethodArgs = nil + + is := info.WantedMethodArg("testAddress1") + Expect(is).To(Equal(false)) + is = info.WantedMethodArg("testAddress2") + Expect(is).To(Equal(false)) + }) + }) + + Describe("PassesEventFilter", func() { + var mapping map[string]string + BeforeEach(func() { + info = &contract.Contract{} + info.FilterArgs = map[string]bool{} + mapping = map[string]string{} + + }) + + It("Return true if event log name-value mapping has filtered for address as a value", func() { + info.FilterArgs["testAddress1"] = true + info.FilterArgs["testAddress2"] = true + + mapping["testInputName1"] = "testAddress1" + mapping["testInputName2"] = "testAddress2" + mapping["testInputName3"] = "testAddress3" + + pass := info.PassesEventFilter(mapping) + Expect(pass).To(Equal(true)) + }) + + It("Return true if event address filter list is empty (no filter)", func() { + mapping["testInputName1"] = "testAddress1" + mapping["testInputName2"] = "testAddress2" + mapping["testInputName3"] = "testAddress3" + + pass := info.PassesEventFilter(mapping) + Expect(pass).To(Equal(true)) + }) + + It("Return false if event log name-value mapping does not have filtered for address as a value", func() { + info.FilterArgs["testAddress1"] = true + info.FilterArgs["testAddress2"] = true + + mapping["testInputName3"] = "testAddress3" + + pass := info.PassesEventFilter(mapping) + Expect(pass).To(Equal(false)) + }) + + It("Return false if event address filter list is nil (block all)", func() { + info.FilterArgs = nil + + mapping["testInputName1"] = "testAddress1" + mapping["testInputName2"] = "testAddress2" + mapping["testInputName3"] = "testAddress3" + + pass := info.PassesEventFilter(mapping) + Expect(pass).To(Equal(false)) + }) + }) + + Describe("AddEmittedAddr", func() { + BeforeEach(func() { + info = &contract.Contract{} + info.FilterArgs = map[string]bool{} + info.MethodArgs = map[string]bool{} + info.Methods = []types.Method{} + info.EmittedAddrs = map[interface{}]bool{} + }) + + It("Adds address to list if it is on the method filter address list", func() { + info.MethodArgs["testAddress2"] = true + info.AddEmittedAddr("testAddress2") + b := info.EmittedAddrs["testAddress2"] + Expect(b).To(Equal(true)) + }) + + It("Adds address to list if method filter is empty", func() { + info.AddEmittedAddr("testAddress2") + b := info.EmittedAddrs["testAddress2"] + Expect(b).To(Equal(true)) + }) + + It("Does not add address to list if both filters are closed (nil)", func() { + info.FilterArgs = nil // close both + info.MethodArgs = nil + info.AddEmittedAddr("testAddress1") + b := info.EmittedAddrs["testAddress1"] + Expect(b).To(Equal(false)) + }) + }) +}) diff --git a/pkg/omni/shared/fetcher/fetcher.go b/pkg/omni/shared/fetcher/fetcher.go new file mode 100644 index 00000000..f481b6aa --- /dev/null +++ b/pkg/omni/shared/fetcher/fetcher.go @@ -0,0 +1,122 @@ +// Copyright 2018 Vulcanize +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fetcher + +import ( + "fmt" + "log" + "math/big" + + "github.com/ethereum/go-ethereum/common" + + "github.com/vulcanize/vulcanizedb/pkg/core" +) + +// Fetcher serves as the lower level data fetcher that calls the underlying +// blockchain's FetchConctractData method for a given return type + +// Interface definition for a Fetcher +type FetcherInterface interface { + FetchBigInt(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (big.Int, error) + FetchBool(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (bool, error) + FetchAddress(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (common.Address, error) + FetchString(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (string, error) + FetchHash(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (common.Hash, error) +} + +// Used to create a new Fetcher error for a given error and fetch method +func newFetcherError(err error, fetchMethod string) *fetcherError { + e := fetcherError{err.Error(), fetchMethod} + log.Println(e.Error()) + return &e +} + +// Fetcher struct +type Fetcher struct { + BlockChain core.BlockChain // Underyling Blockchain +} + +// Fetcher error +type fetcherError struct { + err string + fetchMethod string +} + +// Fetcher error method +func (fe *fetcherError) Error() string { + return fmt.Sprintf("Error fetching %s: %s", fe.fetchMethod, fe.err) +} + +// Generic Fetcher methods used by Getters to call contract methods + +// Method used to fetch big.Int value from contract +func (f Fetcher) FetchBigInt(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (big.Int, error) { + var result = new(big.Int) + err := f.BlockChain.FetchContractData(contractAbi, contractAddress, method, methodArgs, &result, blockNumber) + + if err != nil { + return *result, newFetcherError(err, method) + } + + return *result, nil +} + +// Method used to fetch bool value from contract +func (f Fetcher) FetchBool(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (bool, error) { + var result = new(bool) + err := f.BlockChain.FetchContractData(contractAbi, contractAddress, method, methodArgs, &result, blockNumber) + + if err != nil { + return *result, newFetcherError(err, method) + } + + return *result, nil +} + +// Method used to fetch address value from contract +func (f Fetcher) FetchAddress(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (common.Address, error) { + var result = new(common.Address) + err := f.BlockChain.FetchContractData(contractAbi, contractAddress, method, methodArgs, &result, blockNumber) + + if err != nil { + return *result, newFetcherError(err, method) + } + + return *result, nil +} + +// Method used to fetch string value from contract +func (f Fetcher) FetchString(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (string, error) { + var result = new(string) + err := f.BlockChain.FetchContractData(contractAbi, contractAddress, method, methodArgs, &result, blockNumber) + + if err != nil { + return *result, newFetcherError(err, method) + } + + return *result, nil +} + +// Method used to fetch hash value from contract +func (f Fetcher) FetchHash(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (common.Hash, error) { + var result = new(common.Hash) + err := f.BlockChain.FetchContractData(contractAbi, contractAddress, method, methodArgs, &result, blockNumber) + + if err != nil { + return *result, newFetcherError(err, method) + } + + return *result, nil +} diff --git a/pkg/omni/shared/getter/getter_suite_test.go b/pkg/omni/shared/getter/getter_suite_test.go new file mode 100644 index 00000000..78fdc7bf --- /dev/null +++ b/pkg/omni/shared/getter/getter_suite_test.go @@ -0,0 +1,35 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package getter_test + +import ( + "io/ioutil" + "log" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestRepository(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Getter Suite Test") +} + +var _ = BeforeSuite(func() { + log.SetOutput(ioutil.Discard) +}) diff --git a/pkg/omni/shared/getter/getter_test.go b/pkg/omni/shared/getter/getter_test.go new file mode 100644 index 00000000..1c440972 --- /dev/null +++ b/pkg/omni/shared/getter/getter_test.go @@ -0,0 +1,55 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package getter_test + +import ( + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/geth" + "github.com/vulcanize/vulcanizedb/pkg/geth/client" + rpc2 "github.com/vulcanize/vulcanizedb/pkg/geth/converters/rpc" + "github.com/vulcanize/vulcanizedb/pkg/geth/node" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/getter" +) + +var _ = Describe("Interface Getter", func() { + Describe("GetAbi", func() { + It("Constructs and returns a custom abi based on results from supportsInterface calls", func() { + expectedABI := `[` + constants.AddrChangeInterface + `,` + constants.NameChangeInterface + `,` + constants.ContentChangeInterface + `,` + constants.AbiChangeInterface + `,` + constants.PubkeyChangeInterface + `]` + + blockNumber := int64(6885696) + infuraIPC := "https://mainnet.infura.io/v3/b09888c1113640cc9ab42750ce750c05" + rawRpcClient, err := rpc.Dial(infuraIPC) + Expect(err).NotTo(HaveOccurred()) + rpcClient := client.NewRpcClient(rawRpcClient, infuraIPC) + ethClient := ethclient.NewClient(rawRpcClient) + blockChainClient := client.NewEthClient(ethClient) + node := node.MakeNode(rpcClient) + transactionConverter := rpc2.NewRpcTransactionConverter(ethClient) + blockChain := geth.NewBlockChain(blockChainClient, rpcClient, node, transactionConverter) + interfaceGetter := getter.NewInterfaceGetter(blockChain) + abi := interfaceGetter.GetABI(constants.PublicResolverAddress, blockNumber) + Expect(abi).To(Equal(expectedABI)) + _, err = geth.ParseAbi(abi) + Expect(err).ToNot(HaveOccurred()) + }) + }) +}) diff --git a/pkg/omni/shared/getter/interface_getter.go b/pkg/omni/shared/getter/interface_getter.go new file mode 100644 index 00000000..a49ac669 --- /dev/null +++ b/pkg/omni/shared/getter/interface_getter.go @@ -0,0 +1,105 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package getter + +import ( + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/fetcher" +) + +type InterfaceGetter interface { + GetABI(resolverAddr string, blockNumber int64) string + GetBlockChain() core.BlockChain +} + +type interfaceGetter struct { + fetcher.Fetcher +} + +func NewInterfaceGetter(blockChain core.BlockChain) *interfaceGetter { + return &interfaceGetter{ + Fetcher: fetcher.Fetcher{ + BlockChain: blockChain, + }, + } +} + +// Used to construct a custom ABI based on the results from calling supportsInterface +func (g *interfaceGetter) GetABI(resolverAddr string, blockNumber int64) string { + a := constants.SupportsInterfaceABI + args := make([]interface{}, 1) + args[0] = constants.MetaSig.Bytes() + supports, err := g.getSupportsInterface(a, resolverAddr, blockNumber, args) + if err != nil || !supports { + return "" + } + abiStr := `[` + args[0] = constants.AddrChangeSig.Bytes() + supports, err = g.getSupportsInterface(a, resolverAddr, blockNumber, args) + if err == nil && supports { + abiStr += constants.AddrChangeInterface + "," + } + args[0] = constants.NameChangeSig.Bytes() + supports, err = g.getSupportsInterface(a, resolverAddr, blockNumber, args) + if err == nil && supports { + abiStr += constants.NameChangeInterface + "," + } + args[0] = constants.ContentChangeSig.Bytes() + supports, err = g.getSupportsInterface(a, resolverAddr, blockNumber, args) + if err == nil && supports { + abiStr += constants.ContentChangeInterface + "," + } + args[0] = constants.AbiChangeSig.Bytes() + supports, err = g.getSupportsInterface(a, resolverAddr, blockNumber, args) + if err == nil && supports { + abiStr += constants.AbiChangeInterface + "," + } + args[0] = constants.PubkeyChangeSig.Bytes() + supports, err = g.getSupportsInterface(a, resolverAddr, blockNumber, args) + if err == nil && supports { + abiStr += constants.PubkeyChangeInterface + "," + } + args[0] = constants.ContentHashChangeSig.Bytes() + supports, err = g.getSupportsInterface(a, resolverAddr, blockNumber, args) + if err == nil && supports { + abiStr += constants.ContenthashChangeInterface + "," + } + args[0] = constants.MultihashChangeSig.Bytes() + supports, err = g.getSupportsInterface(a, resolverAddr, blockNumber, args) + if err == nil && supports { + abiStr += constants.MultihashChangeInterface + "," + } + args[0] = constants.TextChangeSig.Bytes() + supports, err = g.getSupportsInterface(a, resolverAddr, blockNumber, args) + if err == nil && supports { + abiStr += constants.TextChangeInterface + "," + } + abiStr = abiStr[:len(abiStr)-1] + `]` + + return abiStr +} + +// Use this method to check whether or not a contract supports a given method/event interface +func (g *interfaceGetter) getSupportsInterface(contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (bool, error) { + return g.Fetcher.FetchBool("supportsInterface", contractAbi, contractAddress, blockNumber, methodArgs) +} + +// Method to retrieve the Getter's blockchain +func (g *interfaceGetter) GetBlockChain() core.BlockChain { + return g.Fetcher.BlockChain +} diff --git a/pkg/omni/shared/helpers/helpers.go b/pkg/omni/shared/helpers/helpers.go new file mode 100644 index 00000000..3c448989 --- /dev/null +++ b/pkg/omni/shared/helpers/helpers.go @@ -0,0 +1,69 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package helpers + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/vulcanize/vulcanizedb/pkg/core" +) + +func ConvertToLog(watchedEvent core.WatchedEvent) types.Log { + allTopics := []string{watchedEvent.Topic0, watchedEvent.Topic1, watchedEvent.Topic2, watchedEvent.Topic3} + var nonNilTopics []string + for _, topic := range allTopics { + if topic != "" { + nonNilTopics = append(nonNilTopics, topic) + } + } + return types.Log{ + Address: common.HexToAddress(watchedEvent.Address), + Topics: createTopics(nonNilTopics...), + Data: hexutil.MustDecode(watchedEvent.Data), + BlockNumber: uint64(watchedEvent.BlockNumber), + TxHash: common.HexToHash(watchedEvent.TxHash), + TxIndex: 0, + BlockHash: common.HexToHash("0x0"), + Index: uint(watchedEvent.Index), + Removed: false, + } +} + +func createTopics(topics ...string) []common.Hash { + var topicsArray []common.Hash + for _, topic := range topics { + topicsArray = append(topicsArray, common.HexToHash(topic)) + } + return topicsArray +} + +func BigFromString(n string) *big.Int { + b := new(big.Int) + b.SetString(n, 10) + return b +} + +func GenerateSignature(s string) string { + eventSignature := []byte(s) + hash := crypto.Keccak256Hash(eventSignature) + return hash.Hex() +} diff --git a/pkg/omni/shared/helpers/test_helpers/database.go b/pkg/omni/shared/helpers/test_helpers/database.go new file mode 100644 index 00000000..3bea427b --- /dev/null +++ b/pkg/omni/shared/helpers/test_helpers/database.go @@ -0,0 +1,287 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package test_helpers + +import ( + "math/rand" + + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/config" + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories" + "github.com/vulcanize/vulcanizedb/pkg/geth" + "github.com/vulcanize/vulcanizedb/pkg/geth/client" + rpc2 "github.com/vulcanize/vulcanizedb/pkg/geth/converters/rpc" + "github.com/vulcanize/vulcanizedb/pkg/geth/node" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks" +) + +type TransferLog struct { + Id int64 `db:"id"` + VulvanizeLogId int64 `db:"vulcanize_log_id"` + TokenName string `db:"token_name"` + Block int64 `db:"block"` + Tx string `db:"tx"` + From string `db:"from_"` + To string `db:"to_"` + Value string `db:"value_"` +} + +type NewOwnerLog struct { + Id int64 `db:"id"` + VulvanizeLogId int64 `db:"vulcanize_log_id"` + TokenName string `db:"token_name"` + Block int64 `db:"block"` + Tx string `db:"tx"` + Node string `db:"node_"` + Label string `db:"label_"` + Owner string `db:"owner_"` +} + +type LightTransferLog struct { + Id int64 `db:"id"` + HeaderID int64 `db:"header_id"` + TokenName string `db:"token_name"` + LogIndex int64 `db:"log_idx"` + TxIndex int64 `db:"tx_idx"` + From string `db:"from_"` + To string `db:"to_"` + Value string `db:"value_"` + RawLog []byte `db:"raw_log"` +} + +type LightNewOwnerLog struct { + Id int64 `db:"id"` + HeaderID int64 `db:"header_id"` + TokenName string `db:"token_name"` + LogIndex int64 `db:"log_idx"` + TxIndex int64 `db:"tx_idx"` + Node string `db:"node_"` + Label string `db:"label_"` + Owner string `db:"owner_"` + RawLog []byte `db:"raw_log"` +} + +type BalanceOf struct { + Id int64 `db:"id"` + TokenName string `db:"token_name"` + Block int64 `db:"block"` + Address string `db:"who_"` + Balance string `db:"returned"` +} + +type Resolver struct { + Id int64 `db:"id"` + TokenName string `db:"token_name"` + Block int64 `db:"block"` + Node string `db:"node_"` + Address string `db:"returned"` +} + +type Owner struct { + Id int64 `db:"id"` + TokenName string `db:"token_name"` + Block int64 `db:"block"` + Node string `db:"node_"` + Address string `db:"returned"` +} + +func SetupBC() core.BlockChain { + infuraIPC := "https://mainnet.infura.io/v3/b09888c1113640cc9ab42750ce750c05" + rawRpcClient, err := rpc.Dial(infuraIPC) + Expect(err).NotTo(HaveOccurred()) + rpcClient := client.NewRpcClient(rawRpcClient, infuraIPC) + ethClient := ethclient.NewClient(rawRpcClient) + blockChainClient := client.NewEthClient(ethClient) + node := node.MakeNode(rpcClient) + transactionConverter := rpc2.NewRpcTransactionConverter(ethClient) + blockChain := geth.NewBlockChain(blockChainClient, rpcClient, node, transactionConverter) + + return blockChain +} + +func SetupDBandBC() (*postgres.DB, core.BlockChain) { + infuraIPC := "https://mainnet.infura.io/v3/b09888c1113640cc9ab42750ce750c05" + rawRpcClient, err := rpc.Dial(infuraIPC) + Expect(err).NotTo(HaveOccurred()) + rpcClient := client.NewRpcClient(rawRpcClient, infuraIPC) + ethClient := ethclient.NewClient(rawRpcClient) + blockChainClient := client.NewEthClient(ethClient) + node := node.MakeNode(rpcClient) + transactionConverter := rpc2.NewRpcTransactionConverter(ethClient) + blockChain := geth.NewBlockChain(blockChainClient, rpcClient, node, transactionConverter) + + db, err := postgres.NewDB(config.Database{ + Hostname: "localhost", + Name: "vulcanize_private", + Port: 5432, + }, blockChain.Node()) + Expect(err).NotTo(HaveOccurred()) + + return db, blockChain +} + +func SetupTusdRepo(vulcanizeLogId *int64, wantedEvents, wantedMethods []string) (*postgres.DB, *contract.Contract) { + db, err := postgres.NewDB(config.Database{ + Hostname: "localhost", + Name: "vulcanize_private", + Port: 5432, + }, core.Node{}) + Expect(err).NotTo(HaveOccurred()) + + receiptRepository := repositories.ReceiptRepository{DB: db} + logRepository := repositories.LogRepository{DB: db} + blockRepository := *repositories.NewBlockRepository(db) + + blockNumber := rand.Int63() + blockId := CreateBlock(blockNumber, blockRepository) + + receipts := []core.Receipt{{Logs: []core.Log{{}}}} + + err = receiptRepository.CreateReceiptsAndLogs(blockId, receipts) + Expect(err).ToNot(HaveOccurred()) + + err = logRepository.Get(vulcanizeLogId, `SELECT id FROM logs`) + Expect(err).ToNot(HaveOccurred()) + + info := SetupTusdContract(wantedEvents, wantedMethods) + + return db, info +} + +func SetupTusdContract(wantedEvents, wantedMethods []string) *contract.Contract { + p := mocks.NewParser(constants.TusdAbiString) + err := p.Parse() + Expect(err).ToNot(HaveOccurred()) + + return contract.Contract{ + Name: "TrueUSD", + Address: constants.TusdContractAddress, + Abi: p.Abi(), + ParsedAbi: p.ParsedAbi(), + StartingBlock: 6194634, + LastBlock: 6507323, + Events: p.GetEvents(wantedEvents), + Methods: p.GetSelectMethods(wantedMethods), + MethodArgs: map[string]bool{}, + FilterArgs: map[string]bool{}, + }.Init() +} + +func SetupENSRepo(vulcanizeLogId *int64, wantedEvents, wantedMethods []string) (*postgres.DB, *contract.Contract) { + db, err := postgres.NewDB(config.Database{ + Hostname: "localhost", + Name: "vulcanize_private", + Port: 5432, + }, core.Node{}) + Expect(err).NotTo(HaveOccurred()) + + receiptRepository := repositories.ReceiptRepository{DB: db} + logRepository := repositories.LogRepository{DB: db} + blockRepository := *repositories.NewBlockRepository(db) + + blockNumber := rand.Int63() + blockId := CreateBlock(blockNumber, blockRepository) + + receipts := []core.Receipt{{Logs: []core.Log{{}}}} + + err = receiptRepository.CreateReceiptsAndLogs(blockId, receipts) + Expect(err).ToNot(HaveOccurred()) + + err = logRepository.Get(vulcanizeLogId, `SELECT id FROM logs`) + Expect(err).ToNot(HaveOccurred()) + + info := SetupENSContract(wantedEvents, wantedMethods) + + return db, info +} + +func SetupENSContract(wantedEvents, wantedMethods []string) *contract.Contract { + p := mocks.NewParser(constants.ENSAbiString) + err := p.Parse() + Expect(err).ToNot(HaveOccurred()) + + return contract.Contract{ + Name: "ENS-Registry", + Address: constants.EnsContractAddress, + Abi: p.Abi(), + ParsedAbi: p.ParsedAbi(), + StartingBlock: 6194634, + LastBlock: 6507323, + Events: p.GetEvents(wantedEvents), + Methods: p.GetSelectMethods(wantedMethods), + MethodArgs: map[string]bool{}, + FilterArgs: map[string]bool{}, + }.Init() +} + +func TearDown(db *postgres.DB) { + tx, err := db.Begin() + Expect(err).NotTo(HaveOccurred()) + + _, err = tx.Exec(`DELETE FROM blocks`) + Expect(err).NotTo(HaveOccurred()) + + _, err = tx.Exec(`DELETE FROM headers`) + Expect(err).NotTo(HaveOccurred()) + + _, err = tx.Exec(`DELETE FROM logs`) + Expect(err).NotTo(HaveOccurred()) + + _, err = tx.Exec(`DELETE FROM log_filters`) + Expect(err).NotTo(HaveOccurred()) + + _, err = tx.Exec(`DELETE FROM transactions`) + Expect(err).NotTo(HaveOccurred()) + + _, err = tx.Exec(`DELETE FROM receipts`) + Expect(err).NotTo(HaveOccurred()) + + _, err = tx.Exec(`DROP TABLE checked_headers`) + Expect(err).NotTo(HaveOccurred()) + + _, err = tx.Exec(`CREATE TABLE checked_headers (id SERIAL PRIMARY KEY, header_id INTEGER UNIQUE NOT NULL REFERENCES headers (id) ON DELETE CASCADE);`) + Expect(err).NotTo(HaveOccurred()) + + _, err = tx.Exec(`DROP SCHEMA IF EXISTS full_0x8dd5fbce2f6a956c3022ba3663759011dd51e73e CASCADE`) + Expect(err).NotTo(HaveOccurred()) + + _, err = tx.Exec(`DROP SCHEMA IF EXISTS light_0x8dd5fbce2f6a956c3022ba3663759011dd51e73e CASCADE`) + Expect(err).NotTo(HaveOccurred()) + + _, err = tx.Exec(`DROP SCHEMA IF EXISTS full_0x314159265dd8dbb310642f98f50c066173c1259b CASCADE`) + Expect(err).NotTo(HaveOccurred()) + + _, err = tx.Exec(`DROP SCHEMA IF EXISTS light_0x314159265dd8dbb310642f98f50c066173c1259b CASCADE`) + Expect(err).NotTo(HaveOccurred()) + + err = tx.Commit() + Expect(err).NotTo(HaveOccurred()) +} + +func CreateBlock(blockNumber int64, repository repositories.BlockRepository) (blockId int64) { + blockId, err := repository.CreateOrUpdateBlock(core.Block{Number: blockNumber}) + Expect(err).NotTo(HaveOccurred()) + + return blockId +} diff --git a/pkg/omni/shared/helpers/test_helpers/mocks/entities.go b/pkg/omni/shared/helpers/test_helpers/mocks/entities.go new file mode 100644 index 00000000..4d865382 --- /dev/null +++ b/pkg/omni/shared/helpers/test_helpers/mocks/entities.go @@ -0,0 +1,251 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package mocks + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/filters" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants" +) + +var TransferBlock1 = core.Block{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad123ert", + Number: 6194633, + Transactions: []core.Transaction{{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654aaa", + Receipt: core.Receipt{ + TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654aaa", + ContractAddress: "", + Logs: []core.Log{{ + BlockNumber: 6194633, + TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654aaa", + Address: constants.TusdContractAddress, + Topics: core.Topics{ + constants.TransferEvent.Signature(), + "0x000000000000000000000000000000000000000000000000000000000000af21", + "0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391", + "", + }, + Index: 1, + Data: "0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe", + }}, + }, + }}, +} + +var TransferBlock2 = core.Block{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad123ooo", + Number: 6194634, + Transactions: []core.Transaction{{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654eee", + Receipt: core.Receipt{ + TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654eee", + ContractAddress: "", + Logs: []core.Log{{ + BlockNumber: 6194634, + TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654eee", + Address: constants.TusdContractAddress, + Topics: core.Topics{ + constants.TransferEvent.Signature(), + "0x000000000000000000000000000000000000000000000000000000000000af21", + "0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391", + "", + }, + Index: 1, + Data: "0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe", + }}, + }, + }}, +} + +var NewOwnerBlock1 = core.Block{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad123ppp", + Number: 6194635, + Transactions: []core.Transaction{{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654bbb", + Receipt: core.Receipt{ + TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654bbb", + ContractAddress: "", + Logs: []core.Log{{ + BlockNumber: 6194635, + TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654bbb", + Address: constants.EnsContractAddress, + Topics: core.Topics{ + constants.NewOwnerEvent.Signature(), + "0x0000000000000000000000000000000000000000000000000000c02aaa39b223", + "0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391", + "", + }, + Index: 1, + Data: "0x000000000000000000000000000000000000000000000000000000000000af21", + }}, + }, + }}, +} + +var NewOwnerBlock2 = core.Block{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad123ggg", + Number: 6194636, + Transactions: []core.Transaction{{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654lll", + Receipt: core.Receipt{ + TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654lll", + ContractAddress: "", + Logs: []core.Log{{ + BlockNumber: 6194636, + TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654lll", + Address: constants.EnsContractAddress, + Topics: core.Topics{ + constants.NewOwnerEvent.Signature(), + "0x0000000000000000000000000000000000000000000000000000c02aaa39b223", + "0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba400", + "", + }, + Index: 1, + Data: "0x000000000000000000000000000000000000000000000000000000000000af21", + }}, + }, + }}, +} + +var ExpectedTransferFilter = filters.LogFilter{ + Name: "Transfer", + Address: constants.TusdContractAddress, + ToBlock: -1, + FromBlock: 6194634, + Topics: core.Topics{constants.TransferEvent.Signature()}, +} + +var ExpectedApprovalFilter = filters.LogFilter{ + Name: "Approval", + Address: constants.TusdContractAddress, + ToBlock: -1, + FromBlock: 6194634, + Topics: core.Topics{constants.ApprovalEvent.Signature()}, +} + +var MockTranferEvent = core.WatchedEvent{ + LogID: 1, + Name: constants.TransferEvent.String(), + BlockNumber: 5488076, + Address: constants.TusdContractAddress, + TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae", + Index: 110, + Topic0: constants.TransferEvent.Signature(), + Topic1: "0x000000000000000000000000000000000000000000000000000000000000af21", + Topic2: "0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391", + Topic3: "", + Data: "0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe", +} + +var rawFakeHeader, _ = json.Marshal(core.Header{}) + +var MockHeader1 = core.Header{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad123ert", + BlockNumber: 6194632, + Raw: rawFakeHeader, + Timestamp: "50000000", +} + +var MockHeader2 = core.Header{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad456yui", + BlockNumber: 6194633, + Raw: rawFakeHeader, + Timestamp: "50000015", +} + +var MockHeader3 = core.Header{ + Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad234hfs", + BlockNumber: 6194634, + Raw: rawFakeHeader, + Timestamp: "50000030", +} + +var MockTransferLog1 = types.Log{ + Index: 1, + Address: common.HexToAddress(constants.TusdContractAddress), + BlockNumber: 5488076, + TxIndex: 110, + TxHash: common.HexToHash("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae"), + Topics: []common.Hash{ + common.HexToHash(constants.TransferEvent.Signature()), + common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000af21"), + common.HexToHash("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391"), + }, + Data: hexutil.MustDecode("0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe"), +} + +var MockTransferLog2 = types.Log{ + Index: 3, + Address: common.HexToAddress(constants.TusdContractAddress), + BlockNumber: 5488077, + TxIndex: 2, + TxHash: common.HexToHash("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546df"), + Topics: []common.Hash{ + common.HexToHash(constants.TransferEvent.Signature()), + common.HexToHash("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391"), + common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000af21"), + }, + Data: hexutil.MustDecode("0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe"), +} + +var MockMintLog = types.Log{ + Index: 10, + Address: common.HexToAddress(constants.TusdContractAddress), + BlockNumber: 5488080, + TxIndex: 50, + TxHash: common.HexToHash("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6minty"), + Topics: []common.Hash{ + common.HexToHash(constants.MintEvent.Signature()), + common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000af21"), + }, + Data: hexutil.MustDecode("0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe"), +} + +var MockNewOwnerLog1 = types.Log{ + Index: 1, + Address: common.HexToAddress(constants.EnsContractAddress), + BlockNumber: 5488076, + TxIndex: 110, + TxHash: common.HexToHash("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae"), + Topics: []common.Hash{ + common.HexToHash(constants.NewOwnerEvent.Signature()), + common.HexToHash("0x000000000000000000000000c02aaa39b223helloa0e5c4f27ead9083c752553"), + common.HexToHash("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391"), + }, + Data: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000af21"), +} + +var MockNewOwnerLog2 = types.Log{ + Index: 3, + Address: common.HexToAddress(constants.EnsContractAddress), + BlockNumber: 5488077, + TxIndex: 2, + TxHash: common.HexToHash("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546df"), + Topics: []common.Hash{ + common.HexToHash(constants.NewOwnerEvent.Signature()), + common.HexToHash("0x000000000000000000000000c02aaa39b223helloa0e5c4f27ead9083c752553"), + common.HexToHash("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba400"), + }, + Data: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000af21"), +} diff --git a/pkg/omni/shared/helpers/test_helpers/mocks/parser.go b/pkg/omni/shared/helpers/test_helpers/mocks/parser.go new file mode 100644 index 00000000..5fd8f5fc --- /dev/null +++ b/pkg/omni/shared/helpers/test_helpers/mocks/parser.go @@ -0,0 +1,162 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package mocks + +import ( + "github.com/ethereum/go-ethereum/accounts/abi" + + "github.com/vulcanize/vulcanizedb/pkg/geth" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" +) + +// Mock parser +// Is given ABI string instead of address +// Performs all other functions of the real parser +type parser struct { + abi string + parsedAbi abi.ABI +} + +func NewParser(abi string) *parser { + + return &parser{ + abi: abi, + } +} + +func (p *parser) Abi() string { + return p.abi +} + +func (p *parser) ParsedAbi() abi.ABI { + return p.parsedAbi +} + +// Retrieves and parses the abi string +// for the given contract address +func (p *parser) Parse() error { + var err error + p.parsedAbi, err = geth.ParseAbi(p.abi) + + return err +} + +// Returns only specified methods, if they meet the criteria +// Returns as array with methods in same order they were specified +// Nil wanted array => no events are returned +func (p *parser) GetSelectMethods(wanted []string) []types.Method { + wLen := len(wanted) + if wLen == 0 { + return nil + } + methods := make([]types.Method, wLen) + for _, m := range p.parsedAbi.Methods { + for i, name := range wanted { + if name == m.Name && okTypes(m, wanted) { + methods[i] = types.NewMethod(m) + } + } + } + + return methods +} + +// Returns wanted methods +// Empty wanted array => all methods are returned +// Nil wanted array => no methods are returned +func (p *parser) GetMethods(wanted []string) []types.Method { + if wanted == nil { + return nil + } + methods := make([]types.Method, 0) + length := len(wanted) + for _, m := range p.parsedAbi.Methods { + if length == 0 || stringInSlice(wanted, m.Name) { + methods = append(methods, types.NewMethod(m)) + } + } + + return methods +} + +// Returns wanted events as map of types.Events +// If no events are specified, all events are returned +func (p *parser) GetEvents(wanted []string) map[string]types.Event { + events := map[string]types.Event{} + + for _, e := range p.parsedAbi.Events { + if len(wanted) == 0 || stringInSlice(wanted, e.Name) { + event := types.NewEvent(e) + events[e.Name] = event + } + } + + return events +} + +func stringInSlice(list []string, s string) bool { + for _, b := range list { + if b == s { + return true + } + } + + return false +} + +func okTypes(m abi.Method, wanted []string) bool { + // Only return method if it has less than 3 arguments, a single output value, and it is a method we want or we want all methods (empty 'wanted' slice) + if len(m.Inputs) < 3 && len(m.Outputs) == 1 && (len(wanted) == 0 || stringInSlice(wanted, m.Name)) { + // Only return methods if inputs are all of accepted types and output is of the accepted types + if !okReturnType(m.Outputs[0]) { + return false + } + for _, input := range m.Inputs { + switch input.Type.T { + case abi.AddressTy, abi.HashTy, abi.BytesTy, abi.FixedBytesTy: + default: + return false + } + } + + return true + } + + return false +} + +func okReturnType(arg abi.Argument) bool { + wantedTypes := []byte{ + abi.UintTy, + abi.IntTy, + abi.BoolTy, + abi.StringTy, + abi.AddressTy, + abi.HashTy, + abi.BytesTy, + abi.FixedBytesTy, + abi.FixedPointTy, + } + + for _, ty := range wantedTypes { + if arg.Type.T == ty { + return true + } + } + + return false +} diff --git a/pkg/omni/shared/parser/parser.go b/pkg/omni/shared/parser/parser.go new file mode 100644 index 00000000..14860f43 --- /dev/null +++ b/pkg/omni/shared/parser/parser.go @@ -0,0 +1,218 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package parser + +import ( + "errors" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + "github.com/vulcanize/vulcanizedb/pkg/geth" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" +) + +// Parser is used to fetch and parse contract ABIs +// It is dependent on etherscan's api +type Parser interface { + Parse(contractAddr string) error + ParseAbiStr(abiStr string) error + Abi() string + ParsedAbi() abi.ABI + GetMethods(wanted []string) []types.Method + GetSelectMethods(wanted []string) []types.Method + GetEvents(wanted []string) map[string]types.Event +} + +type parser struct { + client *geth.EtherScanAPI + abi string + parsedAbi abi.ABI +} + +func NewParser(network string) *parser { + url := geth.GenURL(network) + + return &parser{ + client: geth.NewEtherScanClient(url), + } +} + +func (p *parser) Abi() string { + return p.abi +} + +func (p *parser) ParsedAbi() abi.ABI { + return p.parsedAbi +} + +// Retrieves and parses the abi string +// for the given contract address +func (p *parser) Parse(contractAddr string) error { + // If the abi is one our locally stored abis, fetch + // TODO: Allow users to pass abis through config + knownAbi, err := p.lookUp(contractAddr) + if err == nil { + p.abi = knownAbi + p.parsedAbi, err = geth.ParseAbi(knownAbi) + return err + } + // Try getting abi from etherscan + abiStr, err := p.client.GetAbi(contractAddr) + if err != nil { + return err + } + //TODO: Implement other ways to fetch abi + p.abi = abiStr + p.parsedAbi, err = geth.ParseAbi(abiStr) + + return err +} + +// Loads and parses an abi from a given abi string +func (p *parser) ParseAbiStr(abiStr string) error { + var err error + p.abi = abiStr + p.parsedAbi, err = geth.ParseAbi(abiStr) + + return err +} + +func (p *parser) lookUp(contractAddr string) (string, error) { + if v, ok := constants.Abis[common.HexToAddress(contractAddr)]; ok { + return v, nil + } + + return "", errors.New("ABI not present in lookup tabe") +} + +// Returns only specified methods, if they meet the criteria +// Returns as array with methods in same order they were specified +// Nil or empty wanted array => no events are returned +func (p *parser) GetSelectMethods(wanted []string) []types.Method { + wLen := len(wanted) + if wLen == 0 { + return nil + } + methods := make([]types.Method, wLen) + for _, m := range p.parsedAbi.Methods { + for i, name := range wanted { + if name == m.Name && okTypes(m, wanted) { + methods[i] = types.NewMethod(m) + } + } + } + + return methods +} + +// Returns wanted methods +// Empty wanted array => all methods are returned +// Nil wanted array => no methods are returned +func (p *parser) GetMethods(wanted []string) []types.Method { + if wanted == nil { + return nil + } + methods := make([]types.Method, 0) + length := len(wanted) + for _, m := range p.parsedAbi.Methods { + if length == 0 || stringInSlice(wanted, m.Name) { + methods = append(methods, types.NewMethod(m)) + } + } + + return methods +} + +// Returns wanted events as map of types.Events +// Empty wanted array => all events are returned +// Nil wanted array => no events are returned +func (p *parser) GetEvents(wanted []string) map[string]types.Event { + events := map[string]types.Event{} + if wanted == nil { + return events + } + + length := len(wanted) + for _, e := range p.parsedAbi.Events { + if length == 0 || stringInSlice(wanted, e.Name) { + events[e.Name] = types.NewEvent(e) + } + } + + return events +} + +func okReturnType(arg abi.Argument) bool { + wantedTypes := []byte{ + abi.UintTy, + abi.IntTy, + abi.BoolTy, + abi.StringTy, + abi.AddressTy, + abi.HashTy, + abi.BytesTy, + abi.FixedBytesTy, + abi.FixedPointTy, + } + + for _, ty := range wantedTypes { + if arg.Type.T == ty { + return true + } + } + + return false +} + +func okTypes(m abi.Method, wanted []string) bool { + // Only return method if it has less than 3 arguments, a single output value, and it is a method we want or we want all methods (empty 'wanted' slice) + if len(m.Inputs) < 3 && len(m.Outputs) == 1 && (len(wanted) == 0 || stringInSlice(wanted, m.Name)) { + // Only return methods if inputs are all of accepted types and output is of the accepted types + if !okReturnType(m.Outputs[0]) { + return false + } + for _, input := range m.Inputs { + switch input.Type.T { + // Addresses are properly labeled and caught + // But hashes tend to not be explicitly labeled and caught + // Instead bytes32 are assumed to be hashes + case abi.AddressTy, abi.HashTy: + case abi.FixedBytesTy: + if input.Type.Size != 32 { + return false + } + default: + return false + } + } + return true + } + + return false +} + +func stringInSlice(list []string, s string) bool { + for _, b := range list { + if b == s { + return true + } + } + + return false +} diff --git a/pkg/omni/shared/parser/parser_suite_test.go b/pkg/omni/shared/parser/parser_suite_test.go new file mode 100644 index 00000000..ccc11fb2 --- /dev/null +++ b/pkg/omni/shared/parser/parser_suite_test.go @@ -0,0 +1,35 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package parser_test + +import ( + "io/ioutil" + "log" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestParser(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Parser Suite Test") +} + +var _ = BeforeSuite(func() { + log.SetOutput(ioutil.Discard) +}) diff --git a/pkg/omni/shared/parser/parser_test.go b/pkg/omni/shared/parser/parser_test.go new file mode 100644 index 00000000..0b7f3aa3 --- /dev/null +++ b/pkg/omni/shared/parser/parser_test.go @@ -0,0 +1,226 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package parser_test + +import ( + "github.com/ethereum/go-ethereum/accounts/abi" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/geth" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/parser" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" +) + +var _ = Describe("Parser", func() { + + var p parser.Parser + var err error + + BeforeEach(func() { + p = parser.NewParser("") + }) + + Describe("Mock Parse", func() { + It("Uses parses given abi string", func() { + mp := mocks.NewParser(constants.DaiAbiString) + err = mp.Parse() + Expect(err).ToNot(HaveOccurred()) + + parsedAbi := mp.ParsedAbi() + expectedAbi, err := geth.ParseAbi(constants.DaiAbiString) + Expect(err).ToNot(HaveOccurred()) + Expect(parsedAbi).To(Equal(expectedAbi)) + + methods := mp.GetSelectMethods([]string{"balanceOf"}) + Expect(len(methods)).To(Equal(1)) + balOf := methods[0] + Expect(balOf.Name).To(Equal("balanceOf")) + Expect(len(balOf.Args)).To(Equal(1)) + Expect(len(balOf.Return)).To(Equal(1)) + + events := mp.GetEvents([]string{"Transfer"}) + _, ok := events["Mint"] + Expect(ok).To(Equal(false)) + e, ok := events["Transfer"] + Expect(ok).To(Equal(true)) + Expect(len(e.Fields)).To(Equal(3)) + }) + }) + + Describe("Parse", func() { + It("Fetches and parses abi from etherscan using contract address", func() { + contractAddr := "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359" // dai contract address + err = p.Parse(contractAddr) + Expect(err).ToNot(HaveOccurred()) + + expectedAbi := constants.DaiAbiString + Expect(p.Abi()).To(Equal(expectedAbi)) + + expectedParsedAbi, err := geth.ParseAbi(expectedAbi) + Expect(err).ToNot(HaveOccurred()) + Expect(p.ParsedAbi()).To(Equal(expectedParsedAbi)) + }) + + It("Fails with a normal, non-contract, account address", func() { + addr := "0xAb2A8F7cB56D9EC65573BA1bE0f92Fa2Ff7dd165" + err = p.Parse(addr) + Expect(err).To(HaveOccurred()) + }) + }) + + Describe("GetEvents", func() { + It("Returns parsed events", func() { + contractAddr := "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359" + err = p.Parse(contractAddr) + Expect(err).ToNot(HaveOccurred()) + + events := p.GetEvents([]string{"Transfer"}) + + e, ok := events["Transfer"] + Expect(ok).To(Equal(true)) + + abiTy := e.Fields[0].Type.T + Expect(abiTy).To(Equal(abi.AddressTy)) + + pgTy := e.Fields[0].PgType + Expect(pgTy).To(Equal("CHARACTER VARYING(66)")) + + abiTy = e.Fields[1].Type.T + Expect(abiTy).To(Equal(abi.AddressTy)) + + pgTy = e.Fields[1].PgType + Expect(pgTy).To(Equal("CHARACTER VARYING(66)")) + + abiTy = e.Fields[2].Type.T + Expect(abiTy).To(Equal(abi.UintTy)) + + pgTy = e.Fields[2].PgType + Expect(pgTy).To(Equal("DECIMAL")) + + _, ok = events["Approval"] + Expect(ok).To(Equal(false)) + }) + }) + + Describe("GetSelectMethods", func() { + It("Parses and returns only methods specified in passed array", func() { + contractAddr := "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359" + err = p.Parse(contractAddr) + Expect(err).ToNot(HaveOccurred()) + + methods := p.GetSelectMethods([]string{"balanceOf"}) + Expect(len(methods)).To(Equal(1)) + + balOf := methods[0] + Expect(balOf.Name).To(Equal("balanceOf")) + Expect(len(balOf.Args)).To(Equal(1)) + Expect(len(balOf.Return)).To(Equal(1)) + + abiTy := balOf.Args[0].Type.T + Expect(abiTy).To(Equal(abi.AddressTy)) + + pgTy := balOf.Args[0].PgType + Expect(pgTy).To(Equal("CHARACTER VARYING(66)")) + + abiTy = balOf.Return[0].Type.T + Expect(abiTy).To(Equal(abi.UintTy)) + + pgTy = balOf.Return[0].PgType + Expect(pgTy).To(Equal("DECIMAL")) + + }) + + It("Parses and returns methods in the order they were specified", func() { + contractAddr := "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359" + err = p.Parse(contractAddr) + Expect(err).ToNot(HaveOccurred()) + + selectMethods := p.GetSelectMethods([]string{"balanceOf", "allowance"}) + Expect(len(selectMethods)).To(Equal(2)) + + balOf := selectMethods[0] + allow := selectMethods[1] + + Expect(balOf.Name).To(Equal("balanceOf")) + Expect(allow.Name).To(Equal("allowance")) + }) + + It("Returns nil if given a nil or empty array", func() { + contractAddr := "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359" + err = p.Parse(contractAddr) + Expect(err).ToNot(HaveOccurred()) + + var nilArr []types.Method + selectMethods := p.GetSelectMethods([]string{}) + Expect(selectMethods).To(Equal(nilArr)) + selectMethods = p.GetMethods(nil) + Expect(selectMethods).To(Equal(nilArr)) + }) + + }) + + Describe("GetMethods", func() { + It("Parses and returns only methods specified in passed array", func() { + contractAddr := "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359" + err = p.Parse(contractAddr) + Expect(err).ToNot(HaveOccurred()) + + methods := p.GetMethods([]string{"balanceOf"}) + Expect(len(methods)).To(Equal(1)) + + balOf := methods[0] + Expect(balOf.Name).To(Equal("balanceOf")) + Expect(len(balOf.Args)).To(Equal(1)) + Expect(len(balOf.Return)).To(Equal(1)) + + abiTy := balOf.Args[0].Type.T + Expect(abiTy).To(Equal(abi.AddressTy)) + + pgTy := balOf.Args[0].PgType + Expect(pgTy).To(Equal("CHARACTER VARYING(66)")) + + abiTy = balOf.Return[0].Type.T + Expect(abiTy).To(Equal(abi.UintTy)) + + pgTy = balOf.Return[0].PgType + Expect(pgTy).To(Equal("DECIMAL")) + + }) + + It("Returns nil if given a nil array", func() { + contractAddr := "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359" + err = p.Parse(contractAddr) + Expect(err).ToNot(HaveOccurred()) + + var nilArr []types.Method + selectMethods := p.GetMethods(nil) + Expect(selectMethods).To(Equal(nilArr)) + }) + + It("Returns every method if given an empty array", func() { + contractAddr := "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359" + err = p.Parse(contractAddr) + Expect(err).ToNot(HaveOccurred()) + + selectMethods := p.GetMethods([]string{}) + Expect(len(selectMethods)).To(Equal(22)) + }) + }) +}) diff --git a/pkg/omni/shared/poller/poller.go b/pkg/omni/shared/poller/poller.go new file mode 100644 index 00000000..c1b30a93 --- /dev/null +++ b/pkg/omni/shared/poller/poller.go @@ -0,0 +1,292 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package poller + +import ( + "errors" + "fmt" + "math/big" + "strconv" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/repository" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" +) + +type Poller interface { + PollContract(con contract.Contract) error + PollContractAt(con contract.Contract, blockNumber int64) error + FetchContractData(contractAbi, contractAddress, method string, methodArgs []interface{}, result interface{}, blockNumber int64) error +} + +type poller struct { + repository.MethodRepository + bc core.BlockChain + contract contract.Contract +} + +func NewPoller(blockChain core.BlockChain, db *postgres.DB, mode types.Mode) *poller { + return &poller{ + MethodRepository: repository.NewMethodRepository(db, mode), + bc: blockChain, + } +} + +func (p *poller) PollContract(con contract.Contract) error { + for i := con.StartingBlock; i <= con.LastBlock; i++ { + p.PollContractAt(con, i) + } + + return nil +} + +func (p *poller) PollContractAt(con contract.Contract, blockNumber int64) error { + p.contract = con + for _, m := range con.Methods { + switch len(m.Args) { + case 0: + if err := p.pollNoArgAt(m, blockNumber); err != nil { + return err + } + case 1: + if err := p.pollSingleArgAt(m, blockNumber); err != nil { + return err + } + case 2: + if err := p.pollDoubleArgAt(m, blockNumber); err != nil { + return err + } + default: + return errors.New("poller error: too many arguments to handle") + + } + } + + return nil +} + +func (p *poller) pollNoArgAt(m types.Method, bn int64) error { + result := types.Result{ + Block: bn, + Method: m, + Inputs: nil, + PgType: m.Return[0].PgType, + } + + var out interface{} + err := p.bc.FetchContractData(p.contract.Abi, p.contract.Address, m.Name, nil, &out, bn) + if err != nil { + return errors.New(fmt.Sprintf("poller error calling 0 argument method\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err)) + } + + strOut, err := stringify(out) + if err != nil { + return err + } + + // Cache returned value if piping is turned on + p.cache(out) + result.Output = strOut + + // Persist result immediately + err = p.PersistResults([]types.Result{result}, m, p.contract.Address, p.contract.Name) + if err != nil { + return errors.New(fmt.Sprintf("poller error persisting 0 argument method result\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err)) + } + + return nil +} + +// Use token holder address to poll methods that take 1 address argument (e.g. balanceOf) +func (p *poller) pollSingleArgAt(m types.Method, bn int64) error { + result := types.Result{ + Block: bn, + Method: m, + Inputs: make([]interface{}, 1), + PgType: m.Return[0].PgType, + } + + // Depending on the type of the arg choose + // the correct argument set to iterate over + var args map[interface{}]bool + switch m.Args[0].Type.T { + case abi.HashTy, abi.FixedBytesTy: + args = p.contract.EmittedHashes + case abi.AddressTy: + args = p.contract.EmittedAddrs + } + if len(args) == 0 { // If we haven't collected any args by now we can't call the method + return nil + } + results := make([]types.Result, 0, len(args)) + + for arg := range args { + in := []interface{}{arg} + strIn := []interface{}{contract.StringifyArg(arg)} + + var out interface{} + err := p.bc.FetchContractData(p.contract.Abi, p.contract.Address, m.Name, in, &out, bn) + if err != nil { + return errors.New(fmt.Sprintf("poller error calling 1 argument method\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err)) + } + strOut, err := stringify(out) + if err != nil { + return err + } + p.cache(out) + + // Write inputs and outputs to result and append result to growing set + result.Inputs = strIn + result.Output = strOut + results = append(results, result) + } + + // Persist result set as batch + err := p.PersistResults(results, m, p.contract.Address, p.contract.Name) + if err != nil { + return errors.New(fmt.Sprintf("poller error persisting 1 argument method result\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err)) + } + + return nil +} + +// Use token holder address to poll methods that take 2 address arguments (e.g. allowance) +func (p *poller) pollDoubleArgAt(m types.Method, bn int64) error { + result := types.Result{ + Block: bn, + Method: m, + Inputs: make([]interface{}, 2), + PgType: m.Return[0].PgType, + } + + // Depending on the type of the args choose + // the correct argument sets to iterate over + var firstArgs map[interface{}]bool + switch m.Args[0].Type.T { + case abi.HashTy, abi.FixedBytesTy: + firstArgs = p.contract.EmittedHashes + case abi.AddressTy: + firstArgs = p.contract.EmittedAddrs + } + if len(firstArgs) == 0 { + return nil + } + + var secondArgs map[interface{}]bool + switch m.Args[1].Type.T { + case abi.HashTy, abi.FixedBytesTy: + secondArgs = p.contract.EmittedHashes + case abi.AddressTy: + secondArgs = p.contract.EmittedAddrs + } + if len(secondArgs) == 0 { + return nil + } + + results := make([]types.Result, 0, len(firstArgs)*len(secondArgs)) + + for arg1 := range firstArgs { + for arg2 := range secondArgs { + in := []interface{}{arg1, arg2} + strIn := []interface{}{contract.StringifyArg(arg1), contract.StringifyArg(arg2)} + + var out interface{} + err := p.bc.FetchContractData(p.contract.Abi, p.contract.Address, m.Name, in, &out, bn) + if err != nil { + return errors.New(fmt.Sprintf("poller error calling 2 argument method\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err)) + } + + strOut, err := stringify(out) + if err != nil { + return err + } + + p.cache(out) + + result.Output = strOut + result.Inputs = strIn + results = append(results, result) + + } + } + + err := p.PersistResults(results, m, p.contract.Address, p.contract.Name) + if err != nil { + return errors.New(fmt.Sprintf("poller error persisting 2 argument method result\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err)) + } + + return nil +} + +// This is just a wrapper around the poller blockchain's FetchContractData method +func (p *poller) FetchContractData(contractAbi, contractAddress, method string, methodArgs []interface{}, result interface{}, blockNumber int64) error { + return p.bc.FetchContractData(contractAbi, contractAddress, method, methodArgs, result, blockNumber) +} + +// This is used to cache an method return value if method piping is turned on +func (p *poller) cache(out interface{}) { + // Cache returned value if piping is turned on + if p.contract.Piping { + switch out.(type) { + case common.Hash: + if p.contract.EmittedHashes != nil { + p.contract.AddEmittedHash(out.(common.Hash)) + } + case []byte: + if p.contract.EmittedHashes != nil && len(out.([]byte)) == 32 { + p.contract.AddEmittedHash(common.BytesToHash(out.([]byte))) + } + case common.Address: + if p.contract.EmittedAddrs != nil { + p.contract.AddEmittedAddr(out.(common.Address)) + } + default: + } + } +} + +func stringify(input interface{}) (string, error) { + switch input.(type) { + case *big.Int: + b := input.(*big.Int) + return b.String(), nil + case common.Address: + a := input.(common.Address) + return a.String(), nil + case common.Hash: + h := input.(common.Hash) + return h.String(), nil + case string: + return input.(string), nil + case []byte: + b := hexutil.Encode(input.([]byte)) + return b, nil + case byte: + b := input.(byte) + return string(b), nil + case bool: + return strconv.FormatBool(input.(bool)), nil + default: + return "", errors.New("error: unhandled return type") + } +} diff --git a/pkg/omni/shared/poller/poller_suite_test.go b/pkg/omni/shared/poller/poller_suite_test.go new file mode 100644 index 00000000..9a2ea847 --- /dev/null +++ b/pkg/omni/shared/poller/poller_suite_test.go @@ -0,0 +1,35 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package poller_test + +import ( + "io/ioutil" + "log" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestPoller(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Poller Suite Test") +} + +var _ = BeforeSuite(func() { + log.SetOutput(ioutil.Discard) +}) diff --git a/pkg/omni/shared/poller/poller_test.go b/pkg/omni/shared/poller/poller_test.go new file mode 100644 index 00000000..b36cecde --- /dev/null +++ b/pkg/omni/shared/poller/poller_test.go @@ -0,0 +1,257 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package poller_test + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/poller" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" +) + +var _ = Describe("Poller", func() { + + var p poller.Poller + var con *contract.Contract + var db *postgres.DB + var bc core.BlockChain + + AfterEach(func() { + test_helpers.TearDown(db) + }) + + Describe("Full sync mode", func() { + BeforeEach(func() { + db, bc = test_helpers.SetupDBandBC() + p = poller.NewPoller(bc, db, types.FullSync) + }) + + Describe("PollContract", func() { + It("Polls specified contract methods using contract's argument list", func() { + con = test_helpers.SetupTusdContract(nil, []string{"balanceOf"}) + Expect(con.Abi).To(Equal(constants.TusdAbiString)) + con.StartingBlock = 6707322 + con.LastBlock = 6707323 + con.AddEmittedAddr(common.HexToAddress("0xfE9e8709d3215310075d67E3ed32A380CCf451C8"), common.HexToAddress("0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE")) + + err := p.PollContract(*con) + Expect(err).ToNot(HaveOccurred()) + + scanStruct := test_helpers.BalanceOf{} + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0xfE9e8709d3215310075d67E3ed32A380CCf451C8' AND block = '6707322'", constants.TusdContractAddress)).StructScan(&scanStruct) + Expect(err).ToNot(HaveOccurred()) + Expect(scanStruct.Balance).To(Equal("66386309548896882859581786")) + Expect(scanStruct.TokenName).To(Equal("TrueUSD")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0xfE9e8709d3215310075d67E3ed32A380CCf451C8' AND block = '6707323'", constants.TusdContractAddress)).StructScan(&scanStruct) + Expect(err).ToNot(HaveOccurred()) + Expect(scanStruct.Balance).To(Equal("66386309548896882859581786")) + Expect(scanStruct.TokenName).To(Equal("TrueUSD")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE' AND block = '6707322'", constants.TusdContractAddress)).StructScan(&scanStruct) + Expect(err).ToNot(HaveOccurred()) + Expect(scanStruct.Balance).To(Equal("17982350181394112023885864")) + Expect(scanStruct.TokenName).To(Equal("TrueUSD")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE' AND block = '6707323'", constants.TusdContractAddress)).StructScan(&scanStruct) + Expect(err).ToNot(HaveOccurred()) + Expect(scanStruct.Balance).To(Equal("17982350181394112023885864")) + Expect(scanStruct.TokenName).To(Equal("TrueUSD")) + }) + + It("Polls specified contract methods using contract's hash list", func() { + con = test_helpers.SetupENSContract(nil, []string{"owner"}) + Expect(con.Abi).To(Equal(constants.ENSAbiString)) + Expect(len(con.Methods)).To(Equal(1)) + con.AddEmittedHash(common.HexToHash("0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae"), common.HexToHash("0x7e74a86b6e146964fb965db04dc2590516da77f720bb6759337bf5632415fd86")) + + err := p.PollContractAt(*con, 6885877) + Expect(err).ToNot(HaveOccurred()) + + scanStruct := test_helpers.Owner{} + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.owner_method WHERE node_ = '0x7e74a86b6e146964fb965db04dc2590516da77f720bb6759337bf5632415fd86' AND block = '6885877'", constants.EnsContractAddress)).StructScan(&scanStruct) + Expect(err).ToNot(HaveOccurred()) + Expect(scanStruct.Address).To(Equal("0x546aA2EaE2514494EeaDb7bbb35243348983C59d")) + Expect(scanStruct.TokenName).To(Equal("ENS-Registry")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.owner_method WHERE node_ = '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae' AND block = '6885877'", constants.EnsContractAddress)).StructScan(&scanStruct) + Expect(err).ToNot(HaveOccurred()) + Expect(scanStruct.Address).To(Equal("0x6090A6e47849629b7245Dfa1Ca21D94cd15878Ef")) + Expect(scanStruct.TokenName).To(Equal("ENS-Registry")) + }) + + It("Does not poll and persist any methods if none are specified", func() { + con = test_helpers.SetupTusdContract(nil, nil) + Expect(con.Abi).To(Equal(constants.TusdAbiString)) + con.StartingBlock = 6707322 + con.LastBlock = 6707323 + con.AddEmittedAddr(common.HexToAddress("0xfE9e8709d3215310075d67E3ed32A380CCf451C8"), common.HexToAddress("0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE")) + + err := p.PollContract(*con) + Expect(err).ToNot(HaveOccurred()) + + scanStruct := test_helpers.BalanceOf{} + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0xfE9e8709d3215310075d67E3ed32A380CCf451C8' AND block = '6707322'", constants.TusdContractAddress)).StructScan(&scanStruct) + Expect(err).To(HaveOccurred()) + }) + }) + + Describe("FetchContractData", func() { + It("Calls a single contract method", func() { + var name = new(string) + err := p.FetchContractData(constants.TusdAbiString, constants.TusdContractAddress, "name", nil, &name, 6197514) + Expect(err).ToNot(HaveOccurred()) + Expect(*name).To(Equal("TrueUSD")) + }) + }) + }) + + Describe("Light sync mode", func() { + BeforeEach(func() { + db, bc = test_helpers.SetupDBandBC() + p = poller.NewPoller(bc, db, types.LightSync) + }) + + Describe("PollContract", func() { + It("Polls specified contract methods using contract's token holder address list", func() { + con = test_helpers.SetupTusdContract(nil, []string{"balanceOf"}) + Expect(con.Abi).To(Equal(constants.TusdAbiString)) + con.StartingBlock = 6707322 + con.LastBlock = 6707323 + con.AddEmittedAddr(common.HexToAddress("0xfE9e8709d3215310075d67E3ed32A380CCf451C8"), common.HexToAddress("0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE")) + + err := p.PollContract(*con) + Expect(err).ToNot(HaveOccurred()) + + scanStruct := test_helpers.BalanceOf{} + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method WHERE who_ = '0xfE9e8709d3215310075d67E3ed32A380CCf451C8' AND block = '6707322'", constants.TusdContractAddress)).StructScan(&scanStruct) + Expect(err).ToNot(HaveOccurred()) + Expect(scanStruct.Balance).To(Equal("66386309548896882859581786")) + Expect(scanStruct.TokenName).To(Equal("TrueUSD")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method WHERE who_ = '0xfE9e8709d3215310075d67E3ed32A380CCf451C8' AND block = '6707323'", constants.TusdContractAddress)).StructScan(&scanStruct) + Expect(err).ToNot(HaveOccurred()) + Expect(scanStruct.Balance).To(Equal("66386309548896882859581786")) + Expect(scanStruct.TokenName).To(Equal("TrueUSD")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method WHERE who_ = '0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE' AND block = '6707322'", constants.TusdContractAddress)).StructScan(&scanStruct) + Expect(err).ToNot(HaveOccurred()) + Expect(scanStruct.Balance).To(Equal("17982350181394112023885864")) + Expect(scanStruct.TokenName).To(Equal("TrueUSD")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method WHERE who_ = '0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE' AND block = '6707323'", constants.TusdContractAddress)).StructScan(&scanStruct) + Expect(err).ToNot(HaveOccurred()) + Expect(scanStruct.Balance).To(Equal("17982350181394112023885864")) + Expect(scanStruct.TokenName).To(Equal("TrueUSD")) + }) + + It("Polls specified contract methods using contract's hash list", func() { + con = test_helpers.SetupENSContract(nil, []string{"owner"}) + Expect(con.Abi).To(Equal(constants.ENSAbiString)) + Expect(len(con.Methods)).To(Equal(1)) + con.AddEmittedHash(common.HexToHash("0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae"), common.HexToHash("0x7e74a86b6e146964fb965db04dc2590516da77f720bb6759337bf5632415fd86")) + + err := p.PollContractAt(*con, 6885877) + Expect(err).ToNot(HaveOccurred()) + + scanStruct := test_helpers.Owner{} + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x7e74a86b6e146964fb965db04dc2590516da77f720bb6759337bf5632415fd86' AND block = '6885877'", constants.EnsContractAddress)).StructScan(&scanStruct) + Expect(err).ToNot(HaveOccurred()) + Expect(scanStruct.Address).To(Equal("0x546aA2EaE2514494EeaDb7bbb35243348983C59d")) + Expect(scanStruct.TokenName).To(Equal("ENS-Registry")) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae' AND block = '6885877'", constants.EnsContractAddress)).StructScan(&scanStruct) + Expect(err).ToNot(HaveOccurred()) + Expect(scanStruct.Address).To(Equal("0x6090A6e47849629b7245Dfa1Ca21D94cd15878Ef")) + Expect(scanStruct.TokenName).To(Equal("ENS-Registry")) + }) + + It("Does not poll and persist any methods if none are specified", func() { + con = test_helpers.SetupTusdContract(nil, nil) + Expect(con.Abi).To(Equal(constants.TusdAbiString)) + con.StartingBlock = 6707322 + con.LastBlock = 6707323 + con.AddEmittedAddr(common.HexToAddress("0xfE9e8709d3215310075d67E3ed32A380CCf451C8"), common.HexToAddress("0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE")) + + err := p.PollContract(*con) + Expect(err).ToNot(HaveOccurred()) + + scanStruct := test_helpers.BalanceOf{} + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method WHERE who_ = '0xfE9e8709d3215310075d67E3ed32A380CCf451C8' AND block = '6707322'", constants.TusdContractAddress)).StructScan(&scanStruct) + Expect(err).To(HaveOccurred()) + }) + + It("Caches returned values of the appropriate types for downstream method polling if method piping is turned on", func() { + con = test_helpers.SetupENSContract(nil, []string{"resolver"}) + Expect(con.Abi).To(Equal(constants.ENSAbiString)) + con.StartingBlock = 6921967 + con.LastBlock = 6921968 + con.EmittedAddrs = map[interface{}]bool{} + con.Piping = false + con.AddEmittedHash(common.HexToHash("0x495b6e6efdedb750aa519919b5cf282bdaa86067b82a2293a3ff5723527141e8")) + err := p.PollContract(*con) + Expect(err).ToNot(HaveOccurred()) + + scanStruct := test_helpers.Resolver{} + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.resolver_method WHERE node_ = '0x495b6e6efdedb750aa519919b5cf282bdaa86067b82a2293a3ff5723527141e8' AND block = '6921967'", constants.EnsContractAddress)).StructScan(&scanStruct) + Expect(err).ToNot(HaveOccurred()) + Expect(scanStruct.Address).To(Equal("0x5FfC014343cd971B7eb70732021E26C35B744cc4")) + Expect(scanStruct.TokenName).To(Equal("ENS-Registry")) + Expect(len(con.EmittedAddrs)).To(Equal(0)) // With piping off the address is not saved + + test_helpers.TearDown(db) + db, bc = test_helpers.SetupDBandBC() + p = poller.NewPoller(bc, db, types.LightSync) + + con.Piping = true + err = p.PollContract(*con) + Expect(err).ToNot(HaveOccurred()) + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.resolver_method WHERE node_ = '0x495b6e6efdedb750aa519919b5cf282bdaa86067b82a2293a3ff5723527141e8' AND block = '6921967'", constants.EnsContractAddress)).StructScan(&scanStruct) + Expect(err).ToNot(HaveOccurred()) + Expect(scanStruct.Address).To(Equal("0x5FfC014343cd971B7eb70732021E26C35B744cc4")) + Expect(scanStruct.TokenName).To(Equal("ENS-Registry")) + Expect(len(con.EmittedAddrs)).To(Equal(1)) // With piping on it is saved + Expect(con.EmittedAddrs[common.HexToAddress("0x5FfC014343cd971B7eb70732021E26C35B744cc4")]).To(Equal(true)) + }) + }) + + Describe("FetchContractData", func() { + It("Calls a single contract method", func() { + var name = new(string) + err := p.FetchContractData(constants.TusdAbiString, constants.TusdContractAddress, "name", nil, &name, 6197514) + Expect(err).ToNot(HaveOccurred()) + Expect(*name).To(Equal("TrueUSD")) + }) + }) + }) +}) diff --git a/pkg/omni/shared/repository/event_repository.go b/pkg/omni/shared/repository/event_repository.go new file mode 100644 index 00000000..4b599447 --- /dev/null +++ b/pkg/omni/shared/repository/event_repository.go @@ -0,0 +1,313 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package repository + +import ( + "errors" + "fmt" + "strings" + + "github.com/hashicorp/golang-lru" + + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/omni/light/repository" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" +) + +const ( + // Number of contract address and method ids to keep in cache + contractCacheSize = 100 + eventChacheSize = 1000 +) + +// Event repository is used to persist event data into custom tables +type EventRepository interface { + PersistLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error + CreateEventTable(contractAddr string, event types.Event) (bool, error) + CreateContractSchema(contractName string) (bool, error) + CheckSchemaCache(key string) (interface{}, bool) + CheckTableCache(key string) (interface{}, bool) +} + +type eventRepository struct { + db *postgres.DB + mode types.Mode + schemas *lru.Cache // Cache names of recently used schemas to minimize db connections + tables *lru.Cache // Cache names of recently used tables to minimize db connections +} + +func NewEventRepository(db *postgres.DB, mode types.Mode) *eventRepository { + ccs, _ := lru.New(contractCacheSize) + ecs, _ := lru.New(eventChacheSize) + return &eventRepository{ + db: db, + mode: mode, + schemas: ccs, + tables: ecs, + } +} + +// Creates a schema for the contract if needed +// Creates table for the watched contract event if needed +// Persists converted event log data into this custom table +func (r *eventRepository) PersistLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error { + if len(logs) == 0 { + return errors.New("event repository error: passed empty logs slice") + } + _, err := r.CreateContractSchema(contractAddr) + if err != nil { + return err + } + + _, err = r.CreateEventTable(contractAddr, eventInfo) + if err != nil { + return err + } + + return r.persistLogs(logs, eventInfo, contractAddr, contractName) +} + +func (r *eventRepository) persistLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error { + var err error + switch r.mode { + case types.LightSync: + err = r.persistLightSyncLogs(logs, eventInfo, contractAddr, contractName) + case types.FullSync: + err = r.persistFullSyncLogs(logs, eventInfo, contractAddr, contractName) + default: + return errors.New("event repository error: unhandled mode") + } + + return err +} + +// Creates a custom postgres command to persist logs for the given event (compatible with light synced vDB) +func (r *eventRepository) persistLightSyncLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error { + tx, err := r.db.Begin() + if err != nil { + return err + } + + for _, event := range logs { + // Begin pg query string + pgStr := fmt.Sprintf("INSERT INTO %s_%s.%s_event ", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(eventInfo.Name)) + pgStr = pgStr + "(header_id, token_name, raw_log, log_idx, tx_idx" + el := len(event.Values) + + // Preallocate slice of needed capacity and proceed to pack variables into it in same order they appear in string + data := make([]interface{}, 0, 5+el) + data = append(data, + event.Id, + contractName, + event.Raw, + event.LogIndex, + event.TransactionIndex) + + // Iterate over inputs and append name to query string and value to input data + for inputName, input := range event.Values { + pgStr = pgStr + fmt.Sprintf(", %s_", strings.ToLower(inputName)) // Add underscore after to avoid any collisions with reserved pg words + data = append(data, input) + } + + // For each input entry we created we add its postgres command variable to the string + pgStr = pgStr + ") VALUES ($1, $2, $3, $4, $5" + for i := 0; i < el; i++ { + pgStr = pgStr + fmt.Sprintf(", $%d", i+6) + } + pgStr = pgStr + ")" + + // Add this query to the transaction + _, err = tx.Exec(pgStr, data...) + if err != nil { + tx.Rollback() + return err + } + } + + // Mark header as checked for this eventId + eventId := strings.ToLower(eventInfo.Name + "_" + contractAddr) + err = repository.MarkHeaderCheckedInTransaction(logs[0].Id, tx, eventId) // This assumes all logs are from same block + if err != nil { + tx.Rollback() + return err + } + + return tx.Commit() +} + +// Creates a custom postgres command to persist logs for the given event (compatible with fully synced vDB) +func (r *eventRepository) persistFullSyncLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error { + tx, err := r.db.Begin() + if err != nil { + return err + } + + for _, event := range logs { + pgStr := fmt.Sprintf("INSERT INTO %s_%s.%s_event ", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(eventInfo.Name)) + pgStr = pgStr + "(vulcanize_log_id, token_name, block, tx" + el := len(event.Values) + + data := make([]interface{}, 0, 4+el) + data = append(data, + event.Id, + contractName, + event.Block, + event.Tx) + + for inputName, input := range event.Values { + pgStr = pgStr + fmt.Sprintf(", %s_", strings.ToLower(inputName)) + data = append(data, input) + } + + pgStr = pgStr + ") VALUES ($1, $2, $3, $4" + for i := 0; i < el; i++ { + pgStr = pgStr + fmt.Sprintf(", $%d", i+5) + } + pgStr = pgStr + ") ON CONFLICT (vulcanize_log_id) DO NOTHING" + + _, err = tx.Exec(pgStr, data...) + if err != nil { + tx.Rollback() + return err + } + } + + return tx.Commit() +} + +// Checks for event table and creates it if it does not already exist +// Returns true if it created a new table; returns false if table already existed +func (r *eventRepository) CreateEventTable(contractAddr string, event types.Event) (bool, error) { + tableID := fmt.Sprintf("%s_%s.%s_event", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(event.Name)) + // Check cache before querying pq to see if table exists + _, ok := r.tables.Get(tableID) + if ok { + return false, nil + } + tableExists, err := r.checkForTable(contractAddr, event.Name) + if err != nil { + return false, err + } + + if !tableExists { + err = r.newEventTable(tableID, event) + if err != nil { + return false, err + } + } + + // Add table id to cache + r.tables.Add(tableID, true) + + return !tableExists, nil +} + +// Creates a table for the given contract and event +func (r *eventRepository) newEventTable(tableID string, event types.Event) error { + // Begin pg string + var pgStr = fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s ", tableID) + var err error + + // Handle different modes + switch r.mode { + case types.FullSync: + pgStr = pgStr + "(id SERIAL, vulcanize_log_id INTEGER NOT NULL UNIQUE, token_name CHARACTER VARYING(66) NOT NULL, block INTEGER NOT NULL, tx CHARACTER VARYING(66) NOT NULL," + + // Iterate over event fields, using their name and pgType to grow the string + for _, field := range event.Fields { + pgStr = pgStr + fmt.Sprintf(" %s_ %s NOT NULL,", strings.ToLower(field.Name), field.PgType) + } + pgStr = pgStr + " CONSTRAINT log_index_fk FOREIGN KEY (vulcanize_log_id) REFERENCES logs (id) ON DELETE CASCADE)" + case types.LightSync: + pgStr = pgStr + "(id SERIAL, header_id INTEGER NOT NULL REFERENCES headers (id) ON DELETE CASCADE, token_name CHARACTER VARYING(66) NOT NULL, raw_log JSONB, log_idx INTEGER NOT NULL, tx_idx INTEGER NOT NULL," + + for _, field := range event.Fields { + pgStr = pgStr + fmt.Sprintf(" %s_ %s NOT NULL,", strings.ToLower(field.Name), field.PgType) + } + pgStr = pgStr + " UNIQUE (header_id, tx_idx, log_idx))" + default: + return errors.New("unhandled repository mode") + } + + _, err = r.db.Exec(pgStr) + + return err +} + +// Checks if a table already exists for the given contract and event +func (r *eventRepository) checkForTable(contractAddr string, eventName string) (bool, error) { + pgStr := fmt.Sprintf("SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = '%s_%s' AND table_name = '%s_event')", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(eventName)) + + var exists bool + err := r.db.Get(&exists, pgStr) + + return exists, err +} + +// Checks for contract schema and creates it if it does not already exist +// Returns true if it created a new schema; returns false if schema already existed +func (r *eventRepository) CreateContractSchema(contractAddr string) (bool, error) { + if contractAddr == "" { + return false, errors.New("error: no contract address specified") + } + + // Check cache before querying pq to see if schema exists + _, ok := r.schemas.Get(contractAddr) + if ok { + return false, nil + } + schemaExists, err := r.checkForSchema(contractAddr) + if err != nil { + return false, err + } + if !schemaExists { + err = r.newContractSchema(contractAddr) + if err != nil { + return false, err + } + } + + // Add schema name to cache + r.schemas.Add(contractAddr, true) + + return !schemaExists, nil +} + +// Creates a schema for the given contract +func (r *eventRepository) newContractSchema(contractAddr string) error { + _, err := r.db.Exec("CREATE SCHEMA IF NOT EXISTS " + r.mode.String() + "_" + strings.ToLower(contractAddr)) + + return err +} + +// Checks if a schema already exists for the given contract +func (r *eventRepository) checkForSchema(contractAddr string) (bool, error) { + pgStr := fmt.Sprintf("SELECT EXISTS (SELECT schema_name FROM information_schema.schemata WHERE schema_name = '%s_%s')", r.mode.String(), strings.ToLower(contractAddr)) + + var exists bool + err := r.db.QueryRow(pgStr).Scan(&exists) + + return exists, err +} + +func (r *eventRepository) CheckSchemaCache(key string) (interface{}, bool) { + return r.schemas.Get(key) +} + +func (r *eventRepository) CheckTableCache(key string) (interface{}, bool) { + return r.tables.Get(key) +} diff --git a/pkg/omni/shared/repository/event_repository_test.go b/pkg/omni/shared/repository/event_repository_test.go new file mode 100644 index 00000000..06cac7d7 --- /dev/null +++ b/pkg/omni/shared/repository/event_repository_test.go @@ -0,0 +1,351 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package repository_test + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/common" + geth "github.com/ethereum/go-ethereum/core/types" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories" + fc "github.com/vulcanize/vulcanizedb/pkg/omni/full/converter" + lc "github.com/vulcanize/vulcanizedb/pkg/omni/light/converter" + lr "github.com/vulcanize/vulcanizedb/pkg/omni/light/repository" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks" + sr "github.com/vulcanize/vulcanizedb/pkg/omni/shared/repository" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" +) + +var _ = Describe("Repository", func() { + var db *postgres.DB + var dataStore sr.EventRepository + var err error + var log *types.Log + var logs []types.Log + var con *contract.Contract + var vulcanizeLogId int64 + var wantedEvents = []string{"Transfer"} + var wantedMethods = []string{"balanceOf"} + var event types.Event + var headerID int64 + var mockEvent = mocks.MockTranferEvent + var mockLog1 = mocks.MockTransferLog1 + var mockLog2 = mocks.MockTransferLog2 + + BeforeEach(func() { + db, con = test_helpers.SetupTusdRepo(&vulcanizeLogId, wantedEvents, wantedMethods) + mockEvent.LogID = vulcanizeLogId + + event = con.Events["Transfer"] + err = con.GenerateFilters() + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + test_helpers.TearDown(db) + }) + + Describe("Full sync mode", func() { + BeforeEach(func() { + dataStore = sr.NewEventRepository(db, types.FullSync) + }) + + Describe("CreateContractSchema", func() { + It("Creates schema if it doesn't exist", func() { + created, err := dataStore.CreateContractSchema(con.Address) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + created, err = dataStore.CreateContractSchema(con.Address) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(false)) + }) + + It("Caches schema it creates so that it does not need to repeatedly query the database to check for it's existence", func() { + _, ok := dataStore.CheckSchemaCache(con.Address) + Expect(ok).To(Equal(false)) + + created, err := dataStore.CreateContractSchema(con.Address) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + v, ok := dataStore.CheckSchemaCache(con.Address) + Expect(ok).To(Equal(true)) + Expect(v).To(Equal(true)) + }) + }) + + Describe("CreateEventTable", func() { + It("Creates table if it doesn't exist", func() { + created, err := dataStore.CreateContractSchema(con.Address) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + created, err = dataStore.CreateEventTable(con.Address, event) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + created, err = dataStore.CreateEventTable(con.Address, event) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(false)) + }) + + It("Caches table it creates so that it does not need to repeatedly query the database to check for it's existence", func() { + created, err := dataStore.CreateContractSchema(con.Address) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + tableID := fmt.Sprintf("%s_%s.%s_event", types.FullSync, strings.ToLower(con.Address), strings.ToLower(event.Name)) + _, ok := dataStore.CheckTableCache(tableID) + Expect(ok).To(Equal(false)) + + created, err = dataStore.CreateEventTable(con.Address, event) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + v, ok := dataStore.CheckTableCache(tableID) + Expect(ok).To(Equal(true)) + Expect(v).To(Equal(true)) + }) + }) + + Describe("PersistLogs", func() { + BeforeEach(func() { + c := fc.NewConverter(con) + log, err = c.Convert(mockEvent, event) + Expect(err).ToNot(HaveOccurred()) + }) + + It("Persists contract event log values into custom tables", func() { + err = dataStore.PersistLogs([]types.Log{*log}, event, con.Address, con.Name) + Expect(err).ToNot(HaveOccurred()) + + b, ok := con.EmittedAddrs[common.HexToAddress("0x000000000000000000000000000000000000Af21")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + b, ok = con.EmittedAddrs[common.HexToAddress("0x09BbBBE21a5975cAc061D82f7b843bCE061BA391")] + Expect(ok).To(Equal(true)) + Expect(b).To(Equal(true)) + + scanLog := test_helpers.TransferLog{} + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.transfer_event", constants.TusdContractAddress)).StructScan(&scanLog) + Expect(err).ToNot(HaveOccurred()) + expectedLog := test_helpers.TransferLog{ + Id: 1, + VulvanizeLogId: vulcanizeLogId, + TokenName: "TrueUSD", + Block: 5488076, + Tx: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae", + From: "0x000000000000000000000000000000000000Af21", + To: "0x09BbBBE21a5975cAc061D82f7b843bCE061BA391", + Value: "1097077688018008265106216665536940668749033598146", + } + Expect(scanLog).To(Equal(expectedLog)) + }) + + It("Doesn't persist duplicate event logs", func() { + // Try to persist the same log twice in a single call + err = dataStore.PersistLogs([]types.Log{*log, *log}, event, con.Address, con.Name) + Expect(err).ToNot(HaveOccurred()) + + scanLog := test_helpers.TransferLog{} + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.transfer_event", constants.TusdContractAddress)).StructScan(&scanLog) + Expect(err).ToNot(HaveOccurred()) + expectedLog := test_helpers.TransferLog{ + Id: 1, + VulvanizeLogId: vulcanizeLogId, + TokenName: "TrueUSD", + Block: 5488076, + Tx: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae", + From: "0x000000000000000000000000000000000000Af21", + To: "0x09BbBBE21a5975cAc061D82f7b843bCE061BA391", + Value: "1097077688018008265106216665536940668749033598146", + } + Expect(scanLog).To(Equal(expectedLog)) + + // Attempt to persist the same log again in seperate call + err = dataStore.PersistLogs([]types.Log{*log}, event, con.Address, con.Name) + Expect(err).ToNot(HaveOccurred()) + + // Show that no new logs were entered + var count int + err = db.Get(&count, fmt.Sprintf("SELECT COUNT(*) FROM full_%s.transfer_event", constants.TusdContractAddress)) + Expect(err).ToNot(HaveOccurred()) + Expect(count).To(Equal(1)) + }) + + It("Fails with empty log", func() { + err = dataStore.PersistLogs([]types.Log{}, event, con.Address, con.Name) + Expect(err).To(HaveOccurred()) + }) + }) + }) + + Describe("Light sync mode", func() { + BeforeEach(func() { + dataStore = sr.NewEventRepository(db, types.LightSync) + }) + + Describe("CreateContractSchema", func() { + It("Creates schema if it doesn't exist", func() { + created, err := dataStore.CreateContractSchema(con.Address) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + created, err = dataStore.CreateContractSchema(con.Address) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(false)) + }) + + It("Caches schema it creates so that it does not need to repeatedly query the database to check for it's existence", func() { + _, ok := dataStore.CheckSchemaCache(con.Address) + Expect(ok).To(Equal(false)) + + created, err := dataStore.CreateContractSchema(con.Address) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + v, ok := dataStore.CheckSchemaCache(con.Address) + Expect(ok).To(Equal(true)) + Expect(v).To(Equal(true)) + }) + + It("Caches table it creates so that it does not need to repeatedly query the database to check for it's existence", func() { + created, err := dataStore.CreateContractSchema(con.Address) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + tableID := fmt.Sprintf("%s_%s.%s_event", types.LightSync, strings.ToLower(con.Address), strings.ToLower(event.Name)) + _, ok := dataStore.CheckTableCache(tableID) + Expect(ok).To(Equal(false)) + + created, err = dataStore.CreateEventTable(con.Address, event) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + v, ok := dataStore.CheckTableCache(tableID) + Expect(ok).To(Equal(true)) + Expect(v).To(Equal(true)) + }) + }) + + Describe("CreateEventTable", func() { + It("Creates table if it doesn't exist", func() { + created, err := dataStore.CreateContractSchema(con.Address) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + created, err = dataStore.CreateEventTable(con.Address, event) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + created, err = dataStore.CreateEventTable(con.Address, event) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(false)) + }) + }) + + Describe("PersistLogs", func() { + BeforeEach(func() { + headerRepository := repositories.NewHeaderRepository(db) + headerID, err = headerRepository.CreateOrUpdateHeader(mocks.MockHeader1) + Expect(err).ToNot(HaveOccurred()) + c := lc.NewConverter(con) + logs, err = c.Convert([]geth.Log{mockLog1, mockLog2}, event, headerID) + Expect(err).ToNot(HaveOccurred()) + }) + + It("Persists contract event log values into custom tables", func() { + hr := lr.NewHeaderRepository(db) + err = hr.AddCheckColumn(event.Name + "_" + con.Address) + Expect(err).ToNot(HaveOccurred()) + + err = dataStore.PersistLogs(logs, event, con.Address, con.Name) + Expect(err).ToNot(HaveOccurred()) + + var count int + err = db.Get(&count, fmt.Sprintf("SELECT COUNT(*) FROM light_%s.transfer_event", constants.TusdContractAddress)) + Expect(err).ToNot(HaveOccurred()) + Expect(count).To(Equal(2)) + + scanLog := test_helpers.LightTransferLog{} + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.transfer_event LIMIT 1", constants.TusdContractAddress)).StructScan(&scanLog) + Expect(err).ToNot(HaveOccurred()) + Expect(scanLog.HeaderID).To(Equal(headerID)) + Expect(scanLog.TokenName).To(Equal("TrueUSD")) + Expect(scanLog.TxIndex).To(Equal(int64(110))) + Expect(scanLog.LogIndex).To(Equal(int64(1))) + Expect(scanLog.From).To(Equal("0x000000000000000000000000000000000000Af21")) + Expect(scanLog.To).To(Equal("0x09BbBBE21a5975cAc061D82f7b843bCE061BA391")) + Expect(scanLog.Value).To(Equal("1097077688018008265106216665536940668749033598146")) + + var expectedRawLog, rawLog geth.Log + err = json.Unmarshal(logs[0].Raw, &expectedRawLog) + Expect(err).ToNot(HaveOccurred()) + err = json.Unmarshal(scanLog.RawLog, &rawLog) + Expect(err).ToNot(HaveOccurred()) + Expect(rawLog).To(Equal(expectedRawLog)) + }) + + It("Doesn't persist duplicate event logs", func() { + hr := lr.NewHeaderRepository(db) + err = hr.AddCheckColumn(event.Name + "_" + con.Address) + Expect(err).ToNot(HaveOccurred()) + + // Try and fail to persist the same log twice in a single call + err = dataStore.PersistLogs([]types.Log{logs[0], logs[0]}, event, con.Address, con.Name) + Expect(err).To(HaveOccurred()) + + // Successfuly persist the two unique logs + err = dataStore.PersistLogs(logs, event, con.Address, con.Name) + Expect(err).ToNot(HaveOccurred()) + + // Try and fail to persist the same logs again in separate call + err = dataStore.PersistLogs([]types.Log{*log}, event, con.Address, con.Name) + Expect(err).To(HaveOccurred()) + + // Show that no new logs were entered + var count int + err = db.Get(&count, fmt.Sprintf("SELECT COUNT(*) FROM light_%s.transfer_event", constants.TusdContractAddress)) + Expect(err).ToNot(HaveOccurred()) + Expect(count).To(Equal(2)) + }) + + It("Fails if the persisted event does not have a corresponding eventID column in the checked_headers table", func() { + err = dataStore.PersistLogs(logs, event, con.Address, con.Name) + Expect(err).To(HaveOccurred()) + }) + + It("Fails with empty log", func() { + err = dataStore.PersistLogs([]types.Log{}, event, con.Address, con.Name) + Expect(err).To(HaveOccurred()) + }) + }) + }) +}) diff --git a/pkg/omni/shared/repository/method_repository.go b/pkg/omni/shared/repository/method_repository.go new file mode 100644 index 00000000..4231404b --- /dev/null +++ b/pkg/omni/shared/repository/method_repository.go @@ -0,0 +1,227 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package repository + +import ( + "errors" + "fmt" + "strings" + + "github.com/hashicorp/golang-lru" + + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" +) + +const methodCacheSize = 1000 + +type MethodRepository interface { + PersistResults(results []types.Result, methodInfo types.Method, contractAddr, contractName string) error + CreateMethodTable(contractAddr string, method types.Method) (bool, error) + CreateContractSchema(contractAddr string) (bool, error) + CheckSchemaCache(key string) (interface{}, bool) + CheckTableCache(key string) (interface{}, bool) +} + +type methodRepository struct { + *postgres.DB + mode types.Mode + schemas *lru.Cache // Cache names of recently used schemas to minimize db connections + tables *lru.Cache // Cache names of recently used tables to minimize db connections +} + +func NewMethodRepository(db *postgres.DB, mode types.Mode) *methodRepository { + ccs, _ := lru.New(contractCacheSize) + mcs, _ := lru.New(methodCacheSize) + return &methodRepository{ + DB: db, + mode: mode, + schemas: ccs, + tables: mcs, + } +} + +// Creates a schema for the contract if needed +// Creates table for the contract method if needed +// Persists method polling data into this custom table +func (r *methodRepository) PersistResults(results []types.Result, methodInfo types.Method, contractAddr, contractName string) error { + if len(results) == 0 { + return errors.New("method repository error: passed empty results slice") + } + _, err := r.CreateContractSchema(contractAddr) + if err != nil { + return err + } + + _, err = r.CreateMethodTable(contractAddr, methodInfo) + if err != nil { + return err + } + + return r.persistResults(results, methodInfo, contractAddr, contractName) +} + +// Creates a custom postgres command to persist logs for the given event +func (r *methodRepository) persistResults(results []types.Result, methodInfo types.Method, contractAddr, contractName string) error { + tx, err := r.DB.Begin() + if err != nil { + return err + } + + for _, result := range results { + // Begin postgres string + pgStr := fmt.Sprintf("INSERT INTO %s_%s.%s_method ", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(result.Name)) + pgStr = pgStr + "(token_name, block" + ml := len(result.Args) + + // Preallocate slice of needed capacity and proceed to pack variables into it in same order they appear in string + data := make([]interface{}, 0, 3+ml) + data = append(data, + contractName, + result.Block) + + // Iterate over method args and return value, adding names + // to the string and pushing values to the slice + for i, arg := range result.Args { + pgStr = pgStr + fmt.Sprintf(", %s_", strings.ToLower(arg.Name)) // Add underscore after to avoid any collisions with reserved pg words + data = append(data, result.Inputs[i]) + } + pgStr = pgStr + ", returned) VALUES ($1, $2" + data = append(data, result.Output) + + // For each input entry we created we add its postgres command variable to the string + for i := 0; i <= ml; i++ { + pgStr = pgStr + fmt.Sprintf(", $%d", i+3) + } + pgStr = pgStr + ")" + + // Add this query to the transaction + _, err = tx.Exec(pgStr, data...) + if err != nil { + tx.Rollback() + return err + } + } + + return tx.Commit() +} + +// Checks for event table and creates it if it does not already exist +func (r *methodRepository) CreateMethodTable(contractAddr string, method types.Method) (bool, error) { + tableID := fmt.Sprintf("%s_%s.%s_method", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(method.Name)) + + // Check cache before querying pq to see if table exists + _, ok := r.tables.Get(tableID) + if ok { + return false, nil + } + tableExists, err := r.checkForTable(contractAddr, method.Name) + if err != nil { + return false, err + } + if !tableExists { + err = r.newMethodTable(tableID, method) + if err != nil { + return false, err + } + } + + // Add schema name to cache + r.tables.Add(tableID, true) + + return !tableExists, nil +} + +// Creates a table for the given contract and event +func (r *methodRepository) newMethodTable(tableID string, method types.Method) error { + // Begin pg string + pgStr := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s ", tableID) + pgStr = pgStr + "(id SERIAL, token_name CHARACTER VARYING(66) NOT NULL, block INTEGER NOT NULL," + + // Iterate over method inputs and outputs, using their name and pgType to grow the string + for _, arg := range method.Args { + pgStr = pgStr + fmt.Sprintf(" %s_ %s NOT NULL,", strings.ToLower(arg.Name), arg.PgType) + } + + pgStr = pgStr + fmt.Sprintf(" returned %s NOT NULL)", method.Return[0].PgType) + + _, err := r.DB.Exec(pgStr) + + return err +} + +// Checks if a table already exists for the given contract and event +func (r *methodRepository) checkForTable(contractAddr string, methodName string) (bool, error) { + pgStr := fmt.Sprintf("SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = '%s_%s' AND table_name = '%s_method')", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(methodName)) + var exists bool + err := r.DB.Get(&exists, pgStr) + + return exists, err +} + +// Checks for contract schema and creates it if it does not already exist +func (r *methodRepository) CreateContractSchema(contractAddr string) (bool, error) { + if contractAddr == "" { + return false, errors.New("error: no contract address specified") + } + + // Check cache before querying pq to see if schema exists + _, ok := r.schemas.Get(contractAddr) + if ok { + return false, nil + } + schemaExists, err := r.checkForSchema(contractAddr) + if err != nil { + return false, err + } + if !schemaExists { + err = r.newContractSchema(contractAddr) + if err != nil { + return false, err + } + } + + // Add schema name to cache + r.schemas.Add(contractAddr, true) + + return !schemaExists, nil +} + +// Creates a schema for the given contract +func (r *methodRepository) newContractSchema(contractAddr string) error { + _, err := r.DB.Exec("CREATE SCHEMA IF NOT EXISTS " + r.mode.String() + "_" + strings.ToLower(contractAddr)) + + return err +} + +// Checks if a schema already exists for the given contract +func (r *methodRepository) checkForSchema(contractAddr string) (bool, error) { + pgStr := fmt.Sprintf("SELECT EXISTS (SELECT schema_name FROM information_schema.schemata WHERE schema_name = '%s_%s')", r.mode.String(), strings.ToLower(contractAddr)) + + var exists bool + err := r.DB.Get(&exists, pgStr) + + return exists, err +} + +func (r *methodRepository) CheckSchemaCache(key string) (interface{}, bool) { + return r.schemas.Get(key) +} + +func (r *methodRepository) CheckTableCache(key string) (interface{}, bool) { + return r.tables.Get(key) +} diff --git a/pkg/omni/shared/repository/method_repository_test.go b/pkg/omni/shared/repository/method_repository_test.go new file mode 100644 index 00000000..c6936e8a --- /dev/null +++ b/pkg/omni/shared/repository/method_repository_test.go @@ -0,0 +1,240 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package repository_test + +import ( + "fmt" + "strings" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/repository" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" +) + +var _ = Describe("Repository", func() { + var db *postgres.DB + var dataStore repository.MethodRepository + var con *contract.Contract + var err error + var mockResult types.Result + var method types.Method + + BeforeEach(func() { + con = test_helpers.SetupTusdContract([]string{}, []string{"balanceOf"}) + Expect(len(con.Methods)).To(Equal(1)) + method = con.Methods[0] + mockResult = types.Result{ + Method: method, + PgType: method.Return[0].PgType, + Inputs: make([]interface{}, 1), + Output: new(interface{}), + Block: 6707323, + } + mockResult.Inputs[0] = "0xfE9e8709d3215310075d67E3ed32A380CCf451C8" + mockResult.Output = "66386309548896882859581786" + db, _ = test_helpers.SetupDBandBC() + dataStore = repository.NewMethodRepository(db, types.FullSync) + }) + + AfterEach(func() { + test_helpers.TearDown(db) + }) + + Describe("Full Sync Mode", func() { + BeforeEach(func() { + dataStore = repository.NewMethodRepository(db, types.FullSync) + }) + + Describe("CreateContractSchema", func() { + It("Creates schema if it doesn't exist", func() { + created, err := dataStore.CreateContractSchema(constants.TusdContractAddress) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + created, err = dataStore.CreateContractSchema(constants.TusdContractAddress) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(false)) + }) + + It("Caches schema it creates so that it does not need to repeatedly query the database to check for it's existence", func() { + _, ok := dataStore.CheckSchemaCache(con.Address) + Expect(ok).To(Equal(false)) + + created, err := dataStore.CreateContractSchema(con.Address) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + v, ok := dataStore.CheckSchemaCache(con.Address) + Expect(ok).To(Equal(true)) + Expect(v).To(Equal(true)) + }) + }) + + Describe("CreateMethodTable", func() { + It("Creates table if it doesn't exist", func() { + created, err := dataStore.CreateContractSchema(constants.TusdContractAddress) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, method) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, method) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(false)) + }) + + It("Caches table it creates so that it does not need to repeatedly query the database to check for it's existence", func() { + created, err := dataStore.CreateContractSchema(con.Address) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + tableID := fmt.Sprintf("%s_%s.%s_method", types.FullSync, strings.ToLower(con.Address), strings.ToLower(method.Name)) + _, ok := dataStore.CheckTableCache(tableID) + Expect(ok).To(Equal(false)) + + created, err = dataStore.CreateMethodTable(con.Address, method) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + v, ok := dataStore.CheckTableCache(tableID) + Expect(ok).To(Equal(true)) + Expect(v).To(Equal(true)) + }) + }) + + Describe("PersistResult", func() { + It("Persists result from method polling in custom pg table", func() { + err = dataStore.PersistResults([]types.Result{mockResult}, method, con.Address, con.Name) + Expect(err).ToNot(HaveOccurred()) + + scanStruct := test_helpers.BalanceOf{} + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method", constants.TusdContractAddress)).StructScan(&scanStruct) + expectedLog := test_helpers.BalanceOf{ + Id: 1, + TokenName: "TrueUSD", + Block: 6707323, + Address: "0xfE9e8709d3215310075d67E3ed32A380CCf451C8", + Balance: "66386309548896882859581786", + } + Expect(scanStruct).To(Equal(expectedLog)) + }) + + It("Fails with empty result", func() { + err = dataStore.PersistResults([]types.Result{}, method, con.Address, con.Name) + Expect(err).To(HaveOccurred()) + }) + }) + }) + + Describe("Light Sync Mode", func() { + BeforeEach(func() { + dataStore = repository.NewMethodRepository(db, types.LightSync) + }) + + Describe("CreateContractSchema", func() { + It("Creates schema if it doesn't exist", func() { + created, err := dataStore.CreateContractSchema(constants.TusdContractAddress) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + created, err = dataStore.CreateContractSchema(constants.TusdContractAddress) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(false)) + }) + + It("Caches schema it creates so that it does not need to repeatedly query the database to check for it's existence", func() { + _, ok := dataStore.CheckSchemaCache(con.Address) + Expect(ok).To(Equal(false)) + + created, err := dataStore.CreateContractSchema(con.Address) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + v, ok := dataStore.CheckSchemaCache(con.Address) + Expect(ok).To(Equal(true)) + Expect(v).To(Equal(true)) + }) + }) + + Describe("CreateMethodTable", func() { + It("Creates table if it doesn't exist", func() { + created, err := dataStore.CreateContractSchema(constants.TusdContractAddress) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, method) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, method) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(false)) + }) + + It("Caches table it creates so that it does not need to repeatedly query the database to check for it's existence", func() { + created, err := dataStore.CreateContractSchema(con.Address) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + tableID := fmt.Sprintf("%s_%s.%s_method", types.LightSync, strings.ToLower(con.Address), strings.ToLower(method.Name)) + _, ok := dataStore.CheckTableCache(tableID) + Expect(ok).To(Equal(false)) + + created, err = dataStore.CreateMethodTable(con.Address, method) + Expect(err).ToNot(HaveOccurred()) + Expect(created).To(Equal(true)) + + v, ok := dataStore.CheckTableCache(tableID) + Expect(ok).To(Equal(true)) + Expect(v).To(Equal(true)) + }) + }) + + Describe("PersistResult", func() { + It("Persists result from method polling in custom pg table for light sync mode vDB", func() { + err = dataStore.PersistResults([]types.Result{mockResult}, method, con.Address, con.Name) + Expect(err).ToNot(HaveOccurred()) + + scanStruct := test_helpers.BalanceOf{} + + err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method", constants.TusdContractAddress)).StructScan(&scanStruct) + expectedLog := test_helpers.BalanceOf{ + Id: 1, + TokenName: "TrueUSD", + Block: 6707323, + Address: "0xfE9e8709d3215310075d67E3ed32A380CCf451C8", + Balance: "66386309548896882859581786", + } + Expect(scanStruct).To(Equal(expectedLog)) + }) + + It("Fails with empty result", func() { + err = dataStore.PersistResults([]types.Result{}, method, con.Address, con.Name) + Expect(err).To(HaveOccurred()) + }) + }) + }) +}) diff --git a/pkg/omni/shared/repository/repository_suite_test.go b/pkg/omni/shared/repository/repository_suite_test.go new file mode 100644 index 00000000..a1bfc548 --- /dev/null +++ b/pkg/omni/shared/repository/repository_suite_test.go @@ -0,0 +1,35 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package repository_test + +import ( + "io/ioutil" + "log" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestRepository(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Shared Repository Suite Test") +} + +var _ = BeforeSuite(func() { + log.SetOutput(ioutil.Discard) +}) diff --git a/pkg/omni/shared/retriever/address_retriever.go b/pkg/omni/shared/retriever/address_retriever.go new file mode 100644 index 00000000..f56d448f --- /dev/null +++ b/pkg/omni/shared/retriever/address_retriever.go @@ -0,0 +1,120 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package retriever + +import ( + "fmt" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" +) + +// Address retriever is used to retrieve the addresses associated with a contract +type AddressRetriever interface { + RetrieveTokenHolderAddresses(info contract.Contract) (map[common.Address]bool, error) +} + +type addressRetriever struct { + db *postgres.DB + mode types.Mode +} + +func NewAddressRetriever(db *postgres.DB, mode types.Mode) (r *addressRetriever) { + return &addressRetriever{ + db: db, + mode: mode, + } +} + +// Method to retrieve list of token-holding/contract-related addresses by iterating over available events +// This generic method should work whether or not the argument/input names of the events meet the expected standard +// This could be generalized to iterate over ALL events and pull out any address arguments +func (r *addressRetriever) RetrieveTokenHolderAddresses(info contract.Contract) (map[common.Address]bool, error) { + addrList := make([]string, 0) + + _, ok := info.Filters["Transfer"] + if ok { + addrs, err := r.retrieveTransferAddresses(info) + if err != nil { + return nil, err + } + addrList = append(addrList, addrs...) + } + + _, ok = info.Filters["Mint"] + if ok { + addrs, err := r.retrieveTokenMintees(info) + if err != nil { + return nil, err + } + addrList = append(addrList, addrs...) + } + + contractAddresses := make(map[common.Address]bool) + for _, addr := range addrList { + contractAddresses[common.HexToAddress(addr)] = true + } + + return contractAddresses, nil +} + +func (r *addressRetriever) retrieveTransferAddresses(con contract.Contract) ([]string, error) { + transferAddrs := make([]string, 0) + event := con.Events["Transfer"] + + for _, field := range event.Fields { // Iterate over event fields, finding the ones with address type + + if field.Type.T == abi.AddressTy { // If they have address type, retrieve those addresses + addrs := make([]string, 0) + pgStr := fmt.Sprintf("SELECT %s_ FROM %s_%s.%s_event", strings.ToLower(field.Name), r.mode.String(), strings.ToLower(con.Address), strings.ToLower(event.Name)) + err := r.db.Select(&addrs, pgStr) + if err != nil { + return []string{}, err + } + + transferAddrs = append(transferAddrs, addrs...) // And append them to the growing list + } + } + + return transferAddrs, nil +} + +func (r *addressRetriever) retrieveTokenMintees(con contract.Contract) ([]string, error) { + mintAddrs := make([]string, 0) + event := con.Events["Mint"] + + for _, field := range event.Fields { // Iterate over event fields, finding the ones with address type + + if field.Type.T == abi.AddressTy { // If they have address type, retrieve those addresses + addrs := make([]string, 0) + pgStr := fmt.Sprintf("SELECT %s_ FROM %s_%s.%s_event", strings.ToLower(field.Name), r.mode.String(), strings.ToLower(con.Address), strings.ToLower(event.Name)) + err := r.db.Select(&addrs, pgStr) + if err != nil { + return []string{}, err + } + + mintAddrs = append(mintAddrs, addrs...) // And append them to the growing list + } + } + + return mintAddrs, nil +} diff --git a/pkg/omni/shared/retriever/address_retriever_test.go b/pkg/omni/shared/retriever/address_retriever_test.go new file mode 100644 index 00000000..48f45531 --- /dev/null +++ b/pkg/omni/shared/retriever/address_retriever_test.go @@ -0,0 +1,107 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package retriever_test + +import ( + "github.com/ethereum/go-ethereum/common" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/omni/full/converter" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/repository" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/retriever" + "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" +) + +var mockEvent = core.WatchedEvent{ + Name: constants.TransferEvent.String(), + BlockNumber: 5488076, + Address: constants.TusdContractAddress, + TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae", + Index: 110, + Topic0: constants.TransferEvent.Signature(), + Topic1: "0x000000000000000000000000000000000000000000000000000000000000af21", + Topic2: "0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391", + Topic3: "", + Data: "0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe", +} + +var _ = Describe("Address Retriever Test", func() { + var db *postgres.DB + var dataStore repository.EventRepository + var err error + var info *contract.Contract + var vulcanizeLogId int64 + var log *types.Log + var r retriever.AddressRetriever + var addresses map[common.Address]bool + var wantedEvents = []string{"Transfer"} + + BeforeEach(func() { + db, info = test_helpers.SetupTusdRepo(&vulcanizeLogId, wantedEvents, []string{}) + mockEvent.LogID = vulcanizeLogId + + event := info.Events["Transfer"] + err = info.GenerateFilters() + Expect(err).ToNot(HaveOccurred()) + + c := converter.NewConverter(info) + log, err = c.Convert(mockEvent, event) + Expect(err).ToNot(HaveOccurred()) + + dataStore = repository.NewEventRepository(db, types.FullSync) + dataStore.PersistLogs([]types.Log{*log}, event, info.Address, info.Name) + Expect(err).ToNot(HaveOccurred()) + + r = retriever.NewAddressRetriever(db, types.FullSync) + }) + + AfterEach(func() { + test_helpers.TearDown(db) + }) + + Describe("RetrieveTokenHolderAddresses", func() { + It("Retrieves a list of token holder addresses from persisted event logs", func() { + addresses, err = r.RetrieveTokenHolderAddresses(*info) + Expect(err).ToNot(HaveOccurred()) + + _, ok := addresses[common.HexToAddress("0x000000000000000000000000000000000000000000000000000000000000af21")] + Expect(ok).To(Equal(true)) + + _, ok = addresses[common.HexToAddress("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391")] + Expect(ok).To(Equal(true)) + + _, ok = addresses[common.HexToAddress("0x")] + Expect(ok).To(Equal(false)) + + _, ok = addresses[common.HexToAddress(constants.TusdContractAddress)] + Expect(ok).To(Equal(false)) + + }) + + It("Returns empty list when empty contract info is used", func() { + addresses, err = r.RetrieveTokenHolderAddresses(contract.Contract{}) + Expect(err).ToNot(HaveOccurred()) + Expect(len(addresses)).To(Equal(0)) + }) + }) +}) diff --git a/pkg/omni/shared/retriever/retriever_suite_test.go b/pkg/omni/shared/retriever/retriever_suite_test.go new file mode 100644 index 00000000..b46c31fd --- /dev/null +++ b/pkg/omni/shared/retriever/retriever_suite_test.go @@ -0,0 +1,35 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package retriever_test + +import ( + "io/ioutil" + "log" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestRetriever(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Address Retriever Suite Test") +} + +var _ = BeforeSuite(func() { + log.SetOutput(ioutil.Discard) +}) diff --git a/pkg/omni/shared/transformer/interface.go b/pkg/omni/shared/transformer/interface.go new file mode 100644 index 00000000..42a241df --- /dev/null +++ b/pkg/omni/shared/transformer/interface.go @@ -0,0 +1,32 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package transformer + +// Used to extract any/all events and a subset of method (state variable) +// data for any contract and persists it to custom postgres tables in vDB +type Transformer interface { + SetEvents(contractAddr string, filterSet []string) + SetEventArgs(contractAddr string, filterSet []string) + SetMethods(contractAddr string, filterSet []string) + SetMethodArgs(contractAddr string, filterSet []string) + SetStartingBlock(contractAddr string, start int64) + SetCreateAddrList(contractAddr string, on bool) + SetCreateHashList(contractAddr string, on bool) + SetPiping(contractAddr string, on bool) + Init() error + Execute() error +} diff --git a/pkg/omni/shared/types/event.go b/pkg/omni/shared/types/event.go new file mode 100644 index 00000000..8d801d15 --- /dev/null +++ b/pkg/omni/shared/types/event.go @@ -0,0 +1,96 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package types + +import ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +type Event struct { + Name string + Anonymous bool + Fields []Field +} + +type Field struct { + abi.Argument // Name, Type, Indexed + PgType string // Holds type used when committing data held in this field to postgres +} + +// Struct to hold instance of an event log data +type Log struct { + Id int64 // VulcanizeIdLog for full sync and header ID for light sync omni watcher + Values map[string]string // Map of event input names to their values + + // Used for full sync only + Block int64 + Tx string + + // Used for lightSync only + LogIndex uint + TransactionIndex uint + Raw []byte // json.Unmarshalled byte array of geth/core/types.Log{} +} + +// Unpack abi.Event into our custom Event struct +func NewEvent(e abi.Event) Event { + fields := make([]Field, len(e.Inputs)) + for i, input := range e.Inputs { + fields[i] = Field{} + fields[i].Name = input.Name + fields[i].Type = input.Type + fields[i].Indexed = input.Indexed + // Fill in pg type based on abi type + switch fields[i].Type.T { + case abi.HashTy, abi.AddressTy: + fields[i].PgType = "CHARACTER VARYING(66)" + case abi.IntTy, abi.UintTy: + fields[i].PgType = "DECIMAL" + case abi.BoolTy: + fields[i].PgType = "BOOLEAN" + case abi.BytesTy, abi.FixedBytesTy: + fields[i].PgType = "BYTEA" + case abi.ArrayTy: + fields[i].PgType = "TEXT[]" + case abi.FixedPointTy: + fields[i].PgType = "MONEY" // use shopspring/decimal for fixed point numbers in go and money type in postgres? + default: + fields[i].PgType = "TEXT" + } + } + + return Event{ + Name: e.Name, + Anonymous: e.Anonymous, + Fields: fields, + } +} + +func (e Event) Sig() common.Hash { + types := make([]string, len(e.Fields)) + + for i, input := range e.Fields { + types[i] = input.Type.String() + } + + return common.BytesToHash(crypto.Keccak256([]byte(fmt.Sprintf("%v(%v)", e.Name, strings.Join(types, ","))))) +} diff --git a/pkg/omni/shared/types/method.go b/pkg/omni/shared/types/method.go new file mode 100644 index 00000000..fbd3e25a --- /dev/null +++ b/pkg/omni/shared/types/method.go @@ -0,0 +1,111 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package types + +import ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +type Method struct { + Name string + Const bool + Args []Field + Return []Field +} + +// Struct to hold instance of result from method call with given inputs and block +type Result struct { + Method + Inputs []interface{} // Will only use addresses + Output interface{} + PgType string // Holds output pg type + Block int64 +} + +// Unpack abi.Method into our custom Method struct +func NewMethod(m abi.Method) Method { + inputs := make([]Field, len(m.Inputs)) + for i, input := range m.Inputs { + inputs[i] = Field{} + inputs[i].Name = input.Name + inputs[i].Type = input.Type + inputs[i].Indexed = input.Indexed + switch inputs[i].Type.T { + case abi.HashTy, abi.AddressTy: + inputs[i].PgType = "CHARACTER VARYING(66)" + case abi.IntTy, abi.UintTy: + inputs[i].PgType = "DECIMAL" + case abi.BoolTy: + inputs[i].PgType = "BOOLEAN" + case abi.BytesTy, abi.FixedBytesTy: + inputs[i].PgType = "BYTEA" + case abi.ArrayTy: + inputs[i].PgType = "TEXT[]" + case abi.FixedPointTy: + inputs[i].PgType = "MONEY" // use shopspring/decimal for fixed point numbers in go and money type in postgres? + default: + inputs[i].PgType = "TEXT" + } + } + + outputs := make([]Field, len(m.Outputs)) + for i, output := range m.Outputs { + outputs[i] = Field{} + outputs[i].Name = output.Name + outputs[i].Type = output.Type + outputs[i].Indexed = output.Indexed + switch outputs[i].Type.T { + case abi.HashTy, abi.AddressTy: + outputs[i].PgType = "CHARACTER VARYING(66)" + case abi.IntTy, abi.UintTy: + outputs[i].PgType = "DECIMAL" + case abi.BoolTy: + outputs[i].PgType = "BOOLEAN" + case abi.BytesTy, abi.FixedBytesTy: + outputs[i].PgType = "BYTEA" + case abi.ArrayTy: + outputs[i].PgType = "TEXT[]" + case abi.FixedPointTy: + outputs[i].PgType = "MONEY" // use shopspring/decimal for fixed point numbers in go and money type in postgres? + default: + outputs[i].PgType = "TEXT" + } + } + + return Method{ + Name: m.Name, + Const: m.Const, + Args: inputs, + Return: outputs, + } +} + +func (m Method) Sig() common.Hash { + types := make([]string, len(m.Args)) + i := 0 + for _, arg := range m.Args { + types[i] = arg.Type.String() + i++ + } + + return common.BytesToHash(crypto.Keccak256([]byte(fmt.Sprintf("%v(%v)", m.Name, strings.Join(types, ","))))) +} diff --git a/pkg/omni/shared/types/mode.go b/pkg/omni/shared/types/mode.go new file mode 100644 index 00000000..71e5c531 --- /dev/null +++ b/pkg/omni/shared/types/mode.go @@ -0,0 +1,64 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package types + +import "fmt" + +type Mode int + +const ( + LightSync Mode = iota + FullSync +) + +func (mode Mode) IsValid() bool { + return mode >= LightSync && mode <= FullSync +} + +func (mode Mode) String() string { + switch mode { + case LightSync: + return "light" + case FullSync: + return "full" + default: + return "unknown" + } +} + +func (mode Mode) MarshalText() ([]byte, error) { + switch mode { + case LightSync: + return []byte("light"), nil + case FullSync: + return []byte("full"), nil + default: + return nil, fmt.Errorf("omni watcher: unknown mode %d, want LightSync or FullSync", mode) + } +} + +func (mode *Mode) UnmarshalText(text []byte) error { + switch string(text) { + case "light": + *mode = LightSync + case "full": + *mode = FullSync + default: + return fmt.Errorf(`omni watcher: unknown mode %q, want "light" or "full"`, text) + } + return nil +} diff --git a/test_config/test_config.go b/test_config/test_config.go index 2d31916b..a775a164 100644 --- a/test_config/test_config.go +++ b/test_config/test_config.go @@ -1,12 +1,29 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package test_config import ( - log "github.com/sirupsen/logrus" "os" . "github.com/onsi/gomega" + log "github.com/sirupsen/logrus" "github.com/spf13/viper" + "github.com/vulcanize/vulcanizedb/pkg/config" "github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" diff --git a/utils/utils.go b/utils/utils.go index 30d034f9..596d8ad3 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,13 +1,27 @@ +// VulcanizeDB +// Copyright © 2018 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program 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 Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package utils import ( - log "github.com/sirupsen/logrus" - + "math/big" + "os" "path/filepath" - "math/big" - - "os" + log "github.com/sirupsen/logrus" "github.com/vulcanize/vulcanizedb/pkg/config" "github.com/vulcanize/vulcanizedb/pkg/core" diff --git a/vendor/github.com/ethereum/go-ethereum/accounts/abi/abi.go b/vendor/github.com/ethereum/go-ethereum/accounts/abi/abi.go index 08d5db97..3b65dfa6 100644 --- a/vendor/github.com/ethereum/go-ethereum/accounts/abi/abi.go +++ b/vendor/github.com/ethereum/go-ethereum/accounts/abi/abi.go @@ -89,6 +89,24 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) { return fmt.Errorf("abi: could not locate named method or event") } +// Unpack output into a map according to the abi specification +func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, output []byte) (err error) { + if len(output) == 0 { + return fmt.Errorf("abi: unmarshalling empty output") + } + // since there can't be naming collisions with contracts and events, + // we need to decide whether we're calling a method or an event + if method, ok := abi.Methods[name]; ok { + if len(output)%32 != 0 { + return fmt.Errorf("abi: improperly formatted output") + } + return method.Outputs.UnpackIntoMap(v, output) + } else if event, ok := abi.Events[name]; ok { + return event.Inputs.UnpackIntoMap(v, output) + } + return fmt.Errorf("abi: could not locate named method or event") +} + // UnmarshalJSON implements json.Unmarshaler interface func (abi *ABI) UnmarshalJSON(data []byte) error { var fields []struct { diff --git a/vendor/github.com/ethereum/go-ethereum/accounts/abi/abi_test.go b/vendor/github.com/ethereum/go-ethereum/accounts/abi/abi_test.go index b9444f9f..6c2b7c2f 100644 --- a/vendor/github.com/ethereum/go-ethereum/accounts/abi/abi_test.go +++ b/vendor/github.com/ethereum/go-ethereum/accounts/abi/abi_test.go @@ -694,6 +694,62 @@ func TestUnpackEvent(t *testing.T) { } } +func TestUnpackIntoMapEvent(t *testing.T) { + const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]` + abi, err := JSON(strings.NewReader(abiJSON)) + if err != nil { + t.Fatal(err) + } + + const hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158` + data, err := hex.DecodeString(hexdata) + if err != nil { + t.Fatal(err) + } + if len(data)%32 == 0 { + t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) + } + + receivedMap := map[string]interface{}{} + expectedReceivedMap := map[string]interface{}{ + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []uint8{88}, + } + + err = abi.UnpackIntoMap(receivedMap, "received", data) + if err != nil { + t.Error(err) + } + + if receivedMap["sender"] != expectedReceivedMap["sender"] { + t.Errorf("unpacked map does not match expected map") + } + + if receivedMap["amount"].(*big.Int).String() != expectedReceivedMap["amount"].(*big.Int).String() { + t.Errorf("unpacked map does not match expected map") + } + + u8 := receivedMap["memo"].([]uint8) + expectedU8 := expectedReceivedMap["memo"].([]uint8) + for i, v := range expectedU8 { + if u8[i] != v { + t.Errorf("unpacked map does not match expected map") + } + } + + receivedAddrMap := map[string]interface{}{} + + err = abi.UnpackIntoMap(receivedAddrMap, "receivedAddr", data) + if err != nil { + t.Error(err) + } + + if receivedAddrMap["sender"] != expectedReceivedMap["sender"] { + t.Errorf("unpacked map does not match expected map") + } +} + func TestABI_MethodById(t *testing.T) { const abiJSON = `[ {"type":"function","name":"receive","constant":false,"inputs":[{"name":"memo","type":"bytes"}],"outputs":[],"payable":true,"stateMutability":"payable"}, diff --git a/vendor/github.com/ethereum/go-ethereum/accounts/abi/argument.go b/vendor/github.com/ethereum/go-ethereum/accounts/abi/argument.go index d0a6b035..49cbf139 100644 --- a/vendor/github.com/ethereum/go-ethereum/accounts/abi/argument.go +++ b/vendor/github.com/ethereum/go-ethereum/accounts/abi/argument.go @@ -102,6 +102,16 @@ func (arguments Arguments) Unpack(v interface{}, data []byte) error { return arguments.unpackAtomic(v, marshalledValues[0]) } +// Unpack performs the operation hexdata -> mapping of argument name to argument value +func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error { + marshalledValues, err := arguments.UnpackValues(data) + if err != nil { + return err + } + + return arguments.unpackIntoMap(v, marshalledValues) +} + // unpack sets the unmarshalled value to go format. // Note the dst here must be settable. func unpack(t *Type, dst interface{}, src interface{}) error { @@ -160,6 +170,19 @@ func unpack(t *Type, dst interface{}, src interface{}) error { return nil } +// Unpack arguments into map +func (arguments Arguments) unpackIntoMap(v map[string]interface{}, marshalledValues []interface{}) error { + // Make sure map is not nil + if v == nil { + return fmt.Errorf("abi: cannot unpack into a nil map") + } + + for i, arg := range arguments.NonIndexed() { + v[arg.Name] = marshalledValues[i] + } + return nil +} + // unpackAtomic unpacks ( hexdata -> go ) a single value func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interface{}) error { if arguments.LengthNonIndexed() == 0 { diff --git a/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/base.go b/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/base.go index c37bdf11..f70f911d 100644 --- a/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/base.go +++ b/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/base.go @@ -340,6 +340,22 @@ func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) return parseTopics(out, indexed, log.Topics[1:]) } +// UnpackLogIntoMap unpacks a retrieved log into the provided map. +func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event string, log types.Log) error { + if len(log.Data) > 0 { + if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil { + return err + } + } + var indexed abi.Arguments + for _, arg := range c.abi.Events[event].Inputs { + if arg.Indexed { + indexed = append(indexed, arg) + } + } + return parseTopicsIntoMap(out, indexed, log.Topics[1:]) +} + // ensureContext is a helper method to ensure a context is not nil, even if the // user specified it as such. func ensureContext(ctx context.Context) context.Context { diff --git a/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/topics.go b/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/topics.go index 600dfcda..cd84fb16 100644 --- a/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/topics.go +++ b/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/topics.go @@ -187,3 +187,42 @@ func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) er } return nil } + +// parseTopics converts the indexed topic field-value pairs into map key-value pairs +func parseTopicsIntoMap(out map[string]interface{}, fields abi.Arguments, topics []common.Hash) error { + // Sanity check that the fields and topics match up + if len(fields) != len(topics) { + return errors.New("topic/field count mismatch") + } + // Iterate over all the fields and reconstruct them from topics + for _, arg := range fields { + if !arg.Indexed { + return errors.New("non-indexed field in topic reconstruction") + } + + switch arg.Type.T { + case abi.BoolTy: + if topics[0][common.HashLength-1] == 1 { + out[arg.Name] = true + } else { + out[arg.Name] = false + } + case abi.IntTy, abi.UintTy: + num := new(big.Int).SetBytes(topics[0][:]) + out[arg.Name] = num + case abi.AddressTy: + var addr common.Address + copy(addr[:], topics[0][common.HashLength-common.AddressLength:]) + out[arg.Name] = addr + case abi.HashTy: + out[arg.Name] = topics[0] + case abi.BytesTy, abi.FixedBytesTy: + out[arg.Name] = topics[0][:] + default: + } + + topics = topics[1:] + } + + return nil +} diff --git a/vendor/github.com/ethereum/go-ethereum/accounts/abi/reflect.go b/vendor/github.com/ethereum/go-ethereum/accounts/abi/reflect.go index 1b0bb004..ccc6a659 100644 --- a/vendor/github.com/ethereum/go-ethereum/accounts/abi/reflect.go +++ b/vendor/github.com/ethereum/go-ethereum/accounts/abi/reflect.go @@ -74,7 +74,7 @@ func mustArrayToByteSlice(value reflect.Value) reflect.Value { func set(dst, src reflect.Value) error { dstType, srcType := dst.Type(), src.Type() switch { - case dstType.Kind() == reflect.Interface: + case dstType.Kind() == reflect.Interface && dst.Elem().IsValid(): return set(dst.Elem(), src) case dstType.Kind() == reflect.Ptr && dstType.Elem() != derefbigT: return set(dst.Elem(), src) diff --git a/vendor/github.com/hashicorp/golang-lru/.gitignore b/vendor/github.com/hashicorp/golang-lru/.gitignore new file mode 100644 index 00000000..83656241 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/.gitignore @@ -0,0 +1,23 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test diff --git a/vendor/github.com/hashicorp/golang-lru/2q.go b/vendor/github.com/hashicorp/golang-lru/2q.go new file mode 100644 index 00000000..e474cd07 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/2q.go @@ -0,0 +1,223 @@ +package lru + +import ( + "fmt" + "sync" + + "github.com/hashicorp/golang-lru/simplelru" +) + +const ( + // Default2QRecentRatio is the ratio of the 2Q cache dedicated + // to recently added entries that have only been accessed once. + Default2QRecentRatio = 0.25 + + // Default2QGhostEntries is the default ratio of ghost + // entries kept to track entries recently evicted + Default2QGhostEntries = 0.50 +) + +// TwoQueueCache is a thread-safe fixed size 2Q cache. +// 2Q is an enhancement over the standard LRU cache +// in that it tracks both frequently and recently used +// entries separately. This avoids a burst in access to new +// entries from evicting frequently used entries. It adds some +// additional tracking overhead to the standard LRU cache, and is +// computationally about 2x the cost, and adds some metadata over +// head. The ARCCache is similar, but does not require setting any +// parameters. +type TwoQueueCache struct { + size int + recentSize int + + recent simplelru.LRUCache + frequent simplelru.LRUCache + recentEvict simplelru.LRUCache + lock sync.RWMutex +} + +// New2Q creates a new TwoQueueCache using the default +// values for the parameters. +func New2Q(size int) (*TwoQueueCache, error) { + return New2QParams(size, Default2QRecentRatio, Default2QGhostEntries) +} + +// New2QParams creates a new TwoQueueCache using the provided +// parameter values. +func New2QParams(size int, recentRatio float64, ghostRatio float64) (*TwoQueueCache, error) { + if size <= 0 { + return nil, fmt.Errorf("invalid size") + } + if recentRatio < 0.0 || recentRatio > 1.0 { + return nil, fmt.Errorf("invalid recent ratio") + } + if ghostRatio < 0.0 || ghostRatio > 1.0 { + return nil, fmt.Errorf("invalid ghost ratio") + } + + // Determine the sub-sizes + recentSize := int(float64(size) * recentRatio) + evictSize := int(float64(size) * ghostRatio) + + // Allocate the LRUs + recent, err := simplelru.NewLRU(size, nil) + if err != nil { + return nil, err + } + frequent, err := simplelru.NewLRU(size, nil) + if err != nil { + return nil, err + } + recentEvict, err := simplelru.NewLRU(evictSize, nil) + if err != nil { + return nil, err + } + + // Initialize the cache + c := &TwoQueueCache{ + size: size, + recentSize: recentSize, + recent: recent, + frequent: frequent, + recentEvict: recentEvict, + } + return c, nil +} + +// Get looks up a key's value from the cache. +func (c *TwoQueueCache) Get(key interface{}) (value interface{}, ok bool) { + c.lock.Lock() + defer c.lock.Unlock() + + // Check if this is a frequent value + if val, ok := c.frequent.Get(key); ok { + return val, ok + } + + // If the value is contained in recent, then we + // promote it to frequent + if val, ok := c.recent.Peek(key); ok { + c.recent.Remove(key) + c.frequent.Add(key, val) + return val, ok + } + + // No hit + return nil, false +} + +// Add adds a value to the cache. +func (c *TwoQueueCache) Add(key, value interface{}) { + c.lock.Lock() + defer c.lock.Unlock() + + // Check if the value is frequently used already, + // and just update the value + if c.frequent.Contains(key) { + c.frequent.Add(key, value) + return + } + + // Check if the value is recently used, and promote + // the value into the frequent list + if c.recent.Contains(key) { + c.recent.Remove(key) + c.frequent.Add(key, value) + return + } + + // If the value was recently evicted, add it to the + // frequently used list + if c.recentEvict.Contains(key) { + c.ensureSpace(true) + c.recentEvict.Remove(key) + c.frequent.Add(key, value) + return + } + + // Add to the recently seen list + c.ensureSpace(false) + c.recent.Add(key, value) + return +} + +// ensureSpace is used to ensure we have space in the cache +func (c *TwoQueueCache) ensureSpace(recentEvict bool) { + // If we have space, nothing to do + recentLen := c.recent.Len() + freqLen := c.frequent.Len() + if recentLen+freqLen < c.size { + return + } + + // If the recent buffer is larger than + // the target, evict from there + if recentLen > 0 && (recentLen > c.recentSize || (recentLen == c.recentSize && !recentEvict)) { + k, _, _ := c.recent.RemoveOldest() + c.recentEvict.Add(k, nil) + return + } + + // Remove from the frequent list otherwise + c.frequent.RemoveOldest() +} + +// Len returns the number of items in the cache. +func (c *TwoQueueCache) Len() int { + c.lock.RLock() + defer c.lock.RUnlock() + return c.recent.Len() + c.frequent.Len() +} + +// Keys returns a slice of the keys in the cache. +// The frequently used keys are first in the returned slice. +func (c *TwoQueueCache) Keys() []interface{} { + c.lock.RLock() + defer c.lock.RUnlock() + k1 := c.frequent.Keys() + k2 := c.recent.Keys() + return append(k1, k2...) +} + +// Remove removes the provided key from the cache. +func (c *TwoQueueCache) Remove(key interface{}) { + c.lock.Lock() + defer c.lock.Unlock() + if c.frequent.Remove(key) { + return + } + if c.recent.Remove(key) { + return + } + if c.recentEvict.Remove(key) { + return + } +} + +// Purge is used to completely clear the cache. +func (c *TwoQueueCache) Purge() { + c.lock.Lock() + defer c.lock.Unlock() + c.recent.Purge() + c.frequent.Purge() + c.recentEvict.Purge() +} + +// Contains is used to check if the cache contains a key +// without updating recency or frequency. +func (c *TwoQueueCache) Contains(key interface{}) bool { + c.lock.RLock() + defer c.lock.RUnlock() + return c.frequent.Contains(key) || c.recent.Contains(key) +} + +// Peek is used to inspect the cache value of a key +// without updating recency or frequency. +func (c *TwoQueueCache) Peek(key interface{}) (value interface{}, ok bool) { + c.lock.RLock() + defer c.lock.RUnlock() + if val, ok := c.frequent.Peek(key); ok { + return val, ok + } + return c.recent.Peek(key) +} diff --git a/vendor/github.com/hashicorp/golang-lru/2q_test.go b/vendor/github.com/hashicorp/golang-lru/2q_test.go new file mode 100644 index 00000000..1b0f3518 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/2q_test.go @@ -0,0 +1,306 @@ +package lru + +import ( + "math/rand" + "testing" +) + +func Benchmark2Q_Rand(b *testing.B) { + l, err := New2Q(8192) + if err != nil { + b.Fatalf("err: %v", err) + } + + trace := make([]int64, b.N*2) + for i := 0; i < b.N*2; i++ { + trace[i] = rand.Int63() % 32768 + } + + b.ResetTimer() + + var hit, miss int + for i := 0; i < 2*b.N; i++ { + if i%2 == 0 { + l.Add(trace[i], trace[i]) + } else { + _, ok := l.Get(trace[i]) + if ok { + hit++ + } else { + miss++ + } + } + } + b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss)) +} + +func Benchmark2Q_Freq(b *testing.B) { + l, err := New2Q(8192) + if err != nil { + b.Fatalf("err: %v", err) + } + + trace := make([]int64, b.N*2) + for i := 0; i < b.N*2; i++ { + if i%2 == 0 { + trace[i] = rand.Int63() % 16384 + } else { + trace[i] = rand.Int63() % 32768 + } + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + l.Add(trace[i], trace[i]) + } + var hit, miss int + for i := 0; i < b.N; i++ { + _, ok := l.Get(trace[i]) + if ok { + hit++ + } else { + miss++ + } + } + b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss)) +} + +func Test2Q_RandomOps(t *testing.T) { + size := 128 + l, err := New2Q(128) + if err != nil { + t.Fatalf("err: %v", err) + } + + n := 200000 + for i := 0; i < n; i++ { + key := rand.Int63() % 512 + r := rand.Int63() + switch r % 3 { + case 0: + l.Add(key, key) + case 1: + l.Get(key) + case 2: + l.Remove(key) + } + + if l.recent.Len()+l.frequent.Len() > size { + t.Fatalf("bad: recent: %d freq: %d", + l.recent.Len(), l.frequent.Len()) + } + } +} + +func Test2Q_Get_RecentToFrequent(t *testing.T) { + l, err := New2Q(128) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Touch all the entries, should be in t1 + for i := 0; i < 128; i++ { + l.Add(i, i) + } + if n := l.recent.Len(); n != 128 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + + // Get should upgrade to t2 + for i := 0; i < 128; i++ { + _, ok := l.Get(i) + if !ok { + t.Fatalf("missing: %d", i) + } + } + if n := l.recent.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 128 { + t.Fatalf("bad: %d", n) + } + + // Get be from t2 + for i := 0; i < 128; i++ { + _, ok := l.Get(i) + if !ok { + t.Fatalf("missing: %d", i) + } + } + if n := l.recent.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 128 { + t.Fatalf("bad: %d", n) + } +} + +func Test2Q_Add_RecentToFrequent(t *testing.T) { + l, err := New2Q(128) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Add initially to recent + l.Add(1, 1) + if n := l.recent.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + + // Add should upgrade to frequent + l.Add(1, 1) + if n := l.recent.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + + // Add should remain in frequent + l.Add(1, 1) + if n := l.recent.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } +} + +func Test2Q_Add_RecentEvict(t *testing.T) { + l, err := New2Q(4) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Add 1,2,3,4,5 -> Evict 1 + l.Add(1, 1) + l.Add(2, 2) + l.Add(3, 3) + l.Add(4, 4) + l.Add(5, 5) + if n := l.recent.Len(); n != 4 { + t.Fatalf("bad: %d", n) + } + if n := l.recentEvict.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + + // Pull in the recently evicted + l.Add(1, 1) + if n := l.recent.Len(); n != 3 { + t.Fatalf("bad: %d", n) + } + if n := l.recentEvict.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + + // Add 6, should cause another recent evict + l.Add(6, 6) + if n := l.recent.Len(); n != 3 { + t.Fatalf("bad: %d", n) + } + if n := l.recentEvict.Len(); n != 2 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } +} + +func Test2Q(t *testing.T) { + l, err := New2Q(128) + if err != nil { + t.Fatalf("err: %v", err) + } + + for i := 0; i < 256; i++ { + l.Add(i, i) + } + if l.Len() != 128 { + t.Fatalf("bad len: %v", l.Len()) + } + + for i, k := range l.Keys() { + if v, ok := l.Get(k); !ok || v != k || v != i+128 { + t.Fatalf("bad key: %v", k) + } + } + for i := 0; i < 128; i++ { + _, ok := l.Get(i) + if ok { + t.Fatalf("should be evicted") + } + } + for i := 128; i < 256; i++ { + _, ok := l.Get(i) + if !ok { + t.Fatalf("should not be evicted") + } + } + for i := 128; i < 192; i++ { + l.Remove(i) + _, ok := l.Get(i) + if ok { + t.Fatalf("should be deleted") + } + } + + l.Purge() + if l.Len() != 0 { + t.Fatalf("bad len: %v", l.Len()) + } + if _, ok := l.Get(200); ok { + t.Fatalf("should contain nothing") + } +} + +// Test that Contains doesn't update recent-ness +func Test2Q_Contains(t *testing.T) { + l, err := New2Q(2) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + if !l.Contains(1) { + t.Errorf("1 should be contained") + } + + l.Add(3, 3) + if l.Contains(1) { + t.Errorf("Contains should not have updated recent-ness of 1") + } +} + +// Test that Peek doesn't update recent-ness +func Test2Q_Peek(t *testing.T) { + l, err := New2Q(2) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + if v, ok := l.Peek(1); !ok || v != 1 { + t.Errorf("1 should be set to 1: %v, %v", v, ok) + } + + l.Add(3, 3) + if l.Contains(1) { + t.Errorf("should not have updated recent-ness of 1") + } +} diff --git a/vendor/github.com/hashicorp/golang-lru/LICENSE b/vendor/github.com/hashicorp/golang-lru/LICENSE new file mode 100644 index 00000000..be2cc4df --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/LICENSE @@ -0,0 +1,362 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. "Contributor" + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. "Contributor Version" + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms of + a Secondary License. + +1.6. "Executable Form" + + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. + +1.8. "License" + + means this document. + +1.9. "Licensable" + + means having the right to grant, to the maximum extent possible, whether + at the time of the initial grant or subsequently, any and all of the + rights conveyed by this License. + +1.10. "Modifications" + + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor + + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" + + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, + judicial order, or regulation then You must: (a) comply with the terms of + this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing + basis, if such Contributor fails to notify You of the non-compliance by + some reasonable means prior to 60 days after You have come back into + compliance. Moreover, Your grants from a particular Contributor are + reinstated on an ongoing basis if such Contributor notifies You of the + non-compliance by some reasonable means, this is the first time You have + received notice of non-compliance with this License from such + Contributor, and You become compliant prior to 30 days after Your receipt + of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an "as is" basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is free + of defects, merchantable, fit for a particular purpose or non-infringing. + The entire risk as to the quality and performance of the Covered Software + is with You. Should any Covered Software prove defective in any respect, + You (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an essential + part of this License. No use of any Covered Software is authorized under + this License except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from + such party's negligence to the extent applicable law prohibits such + limitation. Some jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and limitation may + not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party's ability to bring cross-claims or + counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall not + be used to construe this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this License must be + attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + + This Source Code Form is "Incompatible + With Secondary Licenses", as defined by + the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/hashicorp/golang-lru/README.md b/vendor/github.com/hashicorp/golang-lru/README.md new file mode 100644 index 00000000..33e58cfa --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/README.md @@ -0,0 +1,25 @@ +golang-lru +========== + +This provides the `lru` package which implements a fixed-size +thread safe LRU cache. It is based on the cache in Groupcache. + +Documentation +============= + +Full docs are available on [Godoc](http://godoc.org/github.com/hashicorp/golang-lru) + +Example +======= + +Using the LRU is very simple: + +```go +l, _ := New(128) +for i := 0; i < 256; i++ { + l.Add(i, nil) +} +if l.Len() != 128 { + panic(fmt.Sprintf("bad len: %v", l.Len())) +} +``` diff --git a/vendor/github.com/hashicorp/golang-lru/arc.go b/vendor/github.com/hashicorp/golang-lru/arc.go new file mode 100644 index 00000000..555225a2 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/arc.go @@ -0,0 +1,257 @@ +package lru + +import ( + "sync" + + "github.com/hashicorp/golang-lru/simplelru" +) + +// ARCCache is a thread-safe fixed size Adaptive Replacement Cache (ARC). +// ARC is an enhancement over the standard LRU cache in that tracks both +// frequency and recency of use. This avoids a burst in access to new +// entries from evicting the frequently used older entries. It adds some +// additional tracking overhead to a standard LRU cache, computationally +// it is roughly 2x the cost, and the extra memory overhead is linear +// with the size of the cache. ARC has been patented by IBM, but is +// similar to the TwoQueueCache (2Q) which requires setting parameters. +type ARCCache struct { + size int // Size is the total capacity of the cache + p int // P is the dynamic preference towards T1 or T2 + + t1 simplelru.LRUCache // T1 is the LRU for recently accessed items + b1 simplelru.LRUCache // B1 is the LRU for evictions from t1 + + t2 simplelru.LRUCache // T2 is the LRU for frequently accessed items + b2 simplelru.LRUCache // B2 is the LRU for evictions from t2 + + lock sync.RWMutex +} + +// NewARC creates an ARC of the given size +func NewARC(size int) (*ARCCache, error) { + // Create the sub LRUs + b1, err := simplelru.NewLRU(size, nil) + if err != nil { + return nil, err + } + b2, err := simplelru.NewLRU(size, nil) + if err != nil { + return nil, err + } + t1, err := simplelru.NewLRU(size, nil) + if err != nil { + return nil, err + } + t2, err := simplelru.NewLRU(size, nil) + if err != nil { + return nil, err + } + + // Initialize the ARC + c := &ARCCache{ + size: size, + p: 0, + t1: t1, + b1: b1, + t2: t2, + b2: b2, + } + return c, nil +} + +// Get looks up a key's value from the cache. +func (c *ARCCache) Get(key interface{}) (value interface{}, ok bool) { + c.lock.Lock() + defer c.lock.Unlock() + + // If the value is contained in T1 (recent), then + // promote it to T2 (frequent) + if val, ok := c.t1.Peek(key); ok { + c.t1.Remove(key) + c.t2.Add(key, val) + return val, ok + } + + // Check if the value is contained in T2 (frequent) + if val, ok := c.t2.Get(key); ok { + return val, ok + } + + // No hit + return nil, false +} + +// Add adds a value to the cache. +func (c *ARCCache) Add(key, value interface{}) { + c.lock.Lock() + defer c.lock.Unlock() + + // Check if the value is contained in T1 (recent), and potentially + // promote it to frequent T2 + if c.t1.Contains(key) { + c.t1.Remove(key) + c.t2.Add(key, value) + return + } + + // Check if the value is already in T2 (frequent) and update it + if c.t2.Contains(key) { + c.t2.Add(key, value) + return + } + + // Check if this value was recently evicted as part of the + // recently used list + if c.b1.Contains(key) { + // T1 set is too small, increase P appropriately + delta := 1 + b1Len := c.b1.Len() + b2Len := c.b2.Len() + if b2Len > b1Len { + delta = b2Len / b1Len + } + if c.p+delta >= c.size { + c.p = c.size + } else { + c.p += delta + } + + // Potentially need to make room in the cache + if c.t1.Len()+c.t2.Len() >= c.size { + c.replace(false) + } + + // Remove from B1 + c.b1.Remove(key) + + // Add the key to the frequently used list + c.t2.Add(key, value) + return + } + + // Check if this value was recently evicted as part of the + // frequently used list + if c.b2.Contains(key) { + // T2 set is too small, decrease P appropriately + delta := 1 + b1Len := c.b1.Len() + b2Len := c.b2.Len() + if b1Len > b2Len { + delta = b1Len / b2Len + } + if delta >= c.p { + c.p = 0 + } else { + c.p -= delta + } + + // Potentially need to make room in the cache + if c.t1.Len()+c.t2.Len() >= c.size { + c.replace(true) + } + + // Remove from B2 + c.b2.Remove(key) + + // Add the key to the frequently used list + c.t2.Add(key, value) + return + } + + // Potentially need to make room in the cache + if c.t1.Len()+c.t2.Len() >= c.size { + c.replace(false) + } + + // Keep the size of the ghost buffers trim + if c.b1.Len() > c.size-c.p { + c.b1.RemoveOldest() + } + if c.b2.Len() > c.p { + c.b2.RemoveOldest() + } + + // Add to the recently seen list + c.t1.Add(key, value) + return +} + +// replace is used to adaptively evict from either T1 or T2 +// based on the current learned value of P +func (c *ARCCache) replace(b2ContainsKey bool) { + t1Len := c.t1.Len() + if t1Len > 0 && (t1Len > c.p || (t1Len == c.p && b2ContainsKey)) { + k, _, ok := c.t1.RemoveOldest() + if ok { + c.b1.Add(k, nil) + } + } else { + k, _, ok := c.t2.RemoveOldest() + if ok { + c.b2.Add(k, nil) + } + } +} + +// Len returns the number of cached entries +func (c *ARCCache) Len() int { + c.lock.RLock() + defer c.lock.RUnlock() + return c.t1.Len() + c.t2.Len() +} + +// Keys returns all the cached keys +func (c *ARCCache) Keys() []interface{} { + c.lock.RLock() + defer c.lock.RUnlock() + k1 := c.t1.Keys() + k2 := c.t2.Keys() + return append(k1, k2...) +} + +// Remove is used to purge a key from the cache +func (c *ARCCache) Remove(key interface{}) { + c.lock.Lock() + defer c.lock.Unlock() + if c.t1.Remove(key) { + return + } + if c.t2.Remove(key) { + return + } + if c.b1.Remove(key) { + return + } + if c.b2.Remove(key) { + return + } +} + +// Purge is used to clear the cache +func (c *ARCCache) Purge() { + c.lock.Lock() + defer c.lock.Unlock() + c.t1.Purge() + c.t2.Purge() + c.b1.Purge() + c.b2.Purge() +} + +// Contains is used to check if the cache contains a key +// without updating recency or frequency. +func (c *ARCCache) Contains(key interface{}) bool { + c.lock.RLock() + defer c.lock.RUnlock() + return c.t1.Contains(key) || c.t2.Contains(key) +} + +// Peek is used to inspect the cache value of a key +// without updating recency or frequency. +func (c *ARCCache) Peek(key interface{}) (value interface{}, ok bool) { + c.lock.RLock() + defer c.lock.RUnlock() + if val, ok := c.t1.Peek(key); ok { + return val, ok + } + return c.t2.Peek(key) +} diff --git a/vendor/github.com/hashicorp/golang-lru/arc_test.go b/vendor/github.com/hashicorp/golang-lru/arc_test.go new file mode 100644 index 00000000..e2d9b68c --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/arc_test.go @@ -0,0 +1,377 @@ +package lru + +import ( + "math/rand" + "testing" + "time" +) + +func init() { + rand.Seed(time.Now().Unix()) +} + +func BenchmarkARC_Rand(b *testing.B) { + l, err := NewARC(8192) + if err != nil { + b.Fatalf("err: %v", err) + } + + trace := make([]int64, b.N*2) + for i := 0; i < b.N*2; i++ { + trace[i] = rand.Int63() % 32768 + } + + b.ResetTimer() + + var hit, miss int + for i := 0; i < 2*b.N; i++ { + if i%2 == 0 { + l.Add(trace[i], trace[i]) + } else { + _, ok := l.Get(trace[i]) + if ok { + hit++ + } else { + miss++ + } + } + } + b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss)) +} + +func BenchmarkARC_Freq(b *testing.B) { + l, err := NewARC(8192) + if err != nil { + b.Fatalf("err: %v", err) + } + + trace := make([]int64, b.N*2) + for i := 0; i < b.N*2; i++ { + if i%2 == 0 { + trace[i] = rand.Int63() % 16384 + } else { + trace[i] = rand.Int63() % 32768 + } + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + l.Add(trace[i], trace[i]) + } + var hit, miss int + for i := 0; i < b.N; i++ { + _, ok := l.Get(trace[i]) + if ok { + hit++ + } else { + miss++ + } + } + b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss)) +} + +func TestARC_RandomOps(t *testing.T) { + size := 128 + l, err := NewARC(128) + if err != nil { + t.Fatalf("err: %v", err) + } + + n := 200000 + for i := 0; i < n; i++ { + key := rand.Int63() % 512 + r := rand.Int63() + switch r % 3 { + case 0: + l.Add(key, key) + case 1: + l.Get(key) + case 2: + l.Remove(key) + } + + if l.t1.Len()+l.t2.Len() > size { + t.Fatalf("bad: t1: %d t2: %d b1: %d b2: %d p: %d", + l.t1.Len(), l.t2.Len(), l.b1.Len(), l.b2.Len(), l.p) + } + if l.b1.Len()+l.b2.Len() > size { + t.Fatalf("bad: t1: %d t2: %d b1: %d b2: %d p: %d", + l.t1.Len(), l.t2.Len(), l.b1.Len(), l.b2.Len(), l.p) + } + } +} + +func TestARC_Get_RecentToFrequent(t *testing.T) { + l, err := NewARC(128) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Touch all the entries, should be in t1 + for i := 0; i < 128; i++ { + l.Add(i, i) + } + if n := l.t1.Len(); n != 128 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + + // Get should upgrade to t2 + for i := 0; i < 128; i++ { + _, ok := l.Get(i) + if !ok { + t.Fatalf("missing: %d", i) + } + } + if n := l.t1.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 128 { + t.Fatalf("bad: %d", n) + } + + // Get be from t2 + for i := 0; i < 128; i++ { + _, ok := l.Get(i) + if !ok { + t.Fatalf("missing: %d", i) + } + } + if n := l.t1.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 128 { + t.Fatalf("bad: %d", n) + } +} + +func TestARC_Add_RecentToFrequent(t *testing.T) { + l, err := NewARC(128) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Add initially to t1 + l.Add(1, 1) + if n := l.t1.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + + // Add should upgrade to t2 + l.Add(1, 1) + if n := l.t1.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + + // Add should remain in t2 + l.Add(1, 1) + if n := l.t1.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } +} + +func TestARC_Adaptive(t *testing.T) { + l, err := NewARC(4) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Fill t1 + for i := 0; i < 4; i++ { + l.Add(i, i) + } + if n := l.t1.Len(); n != 4 { + t.Fatalf("bad: %d", n) + } + + // Move to t2 + l.Get(0) + l.Get(1) + if n := l.t2.Len(); n != 2 { + t.Fatalf("bad: %d", n) + } + + // Evict from t1 + l.Add(4, 4) + if n := l.b1.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + + // Current state + // t1 : (MRU) [4, 3] (LRU) + // t2 : (MRU) [1, 0] (LRU) + // b1 : (MRU) [2] (LRU) + // b2 : (MRU) [] (LRU) + + // Add 2, should cause hit on b1 + l.Add(2, 2) + if n := l.b1.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + if l.p != 1 { + t.Fatalf("bad: %d", l.p) + } + if n := l.t2.Len(); n != 3 { + t.Fatalf("bad: %d", n) + } + + // Current state + // t1 : (MRU) [4] (LRU) + // t2 : (MRU) [2, 1, 0] (LRU) + // b1 : (MRU) [3] (LRU) + // b2 : (MRU) [] (LRU) + + // Add 4, should migrate to t2 + l.Add(4, 4) + if n := l.t1.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 4 { + t.Fatalf("bad: %d", n) + } + + // Current state + // t1 : (MRU) [] (LRU) + // t2 : (MRU) [4, 2, 1, 0] (LRU) + // b1 : (MRU) [3] (LRU) + // b2 : (MRU) [] (LRU) + + // Add 4, should evict to b2 + l.Add(5, 5) + if n := l.t1.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 3 { + t.Fatalf("bad: %d", n) + } + if n := l.b2.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + + // Current state + // t1 : (MRU) [5] (LRU) + // t2 : (MRU) [4, 2, 1] (LRU) + // b1 : (MRU) [3] (LRU) + // b2 : (MRU) [0] (LRU) + + // Add 0, should decrease p + l.Add(0, 0) + if n := l.t1.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 4 { + t.Fatalf("bad: %d", n) + } + if n := l.b1.Len(); n != 2 { + t.Fatalf("bad: %d", n) + } + if n := l.b2.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if l.p != 0 { + t.Fatalf("bad: %d", l.p) + } + + // Current state + // t1 : (MRU) [] (LRU) + // t2 : (MRU) [0, 4, 2, 1] (LRU) + // b1 : (MRU) [5, 3] (LRU) + // b2 : (MRU) [0] (LRU) +} + +func TestARC(t *testing.T) { + l, err := NewARC(128) + if err != nil { + t.Fatalf("err: %v", err) + } + + for i := 0; i < 256; i++ { + l.Add(i, i) + } + if l.Len() != 128 { + t.Fatalf("bad len: %v", l.Len()) + } + + for i, k := range l.Keys() { + if v, ok := l.Get(k); !ok || v != k || v != i+128 { + t.Fatalf("bad key: %v", k) + } + } + for i := 0; i < 128; i++ { + _, ok := l.Get(i) + if ok { + t.Fatalf("should be evicted") + } + } + for i := 128; i < 256; i++ { + _, ok := l.Get(i) + if !ok { + t.Fatalf("should not be evicted") + } + } + for i := 128; i < 192; i++ { + l.Remove(i) + _, ok := l.Get(i) + if ok { + t.Fatalf("should be deleted") + } + } + + l.Purge() + if l.Len() != 0 { + t.Fatalf("bad len: %v", l.Len()) + } + if _, ok := l.Get(200); ok { + t.Fatalf("should contain nothing") + } +} + +// Test that Contains doesn't update recent-ness +func TestARC_Contains(t *testing.T) { + l, err := NewARC(2) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + if !l.Contains(1) { + t.Errorf("1 should be contained") + } + + l.Add(3, 3) + if l.Contains(1) { + t.Errorf("Contains should not have updated recent-ness of 1") + } +} + +// Test that Peek doesn't update recent-ness +func TestARC_Peek(t *testing.T) { + l, err := NewARC(2) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + if v, ok := l.Peek(1); !ok || v != 1 { + t.Errorf("1 should be set to 1: %v, %v", v, ok) + } + + l.Add(3, 3) + if l.Contains(1) { + t.Errorf("should not have updated recent-ness of 1") + } +} diff --git a/vendor/github.com/hashicorp/golang-lru/doc.go b/vendor/github.com/hashicorp/golang-lru/doc.go new file mode 100644 index 00000000..2547df97 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/doc.go @@ -0,0 +1,21 @@ +// Package lru provides three different LRU caches of varying sophistication. +// +// Cache is a simple LRU cache. It is based on the +// LRU implementation in groupcache: +// https://github.com/golang/groupcache/tree/master/lru +// +// TwoQueueCache tracks frequently used and recently used entries separately. +// This avoids a burst of accesses from taking out frequently used entries, +// at the cost of about 2x computational overhead and some extra bookkeeping. +// +// ARCCache is an adaptive replacement cache. It tracks recent evictions as +// well as recent usage in both the frequent and recent caches. Its +// computational overhead is comparable to TwoQueueCache, but the memory +// overhead is linear with the size of the cache. +// +// ARC has been patented by IBM, so do not use it if that is problematic for +// your program. +// +// All caches in this package take locks while operating, and are therefore +// thread-safe for consumers. +package lru diff --git a/vendor/github.com/hashicorp/golang-lru/lru.go b/vendor/github.com/hashicorp/golang-lru/lru.go new file mode 100644 index 00000000..c8d9b0a2 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/lru.go @@ -0,0 +1,110 @@ +package lru + +import ( + "sync" + + "github.com/hashicorp/golang-lru/simplelru" +) + +// Cache is a thread-safe fixed size LRU cache. +type Cache struct { + lru simplelru.LRUCache + lock sync.RWMutex +} + +// New creates an LRU of the given size. +func New(size int) (*Cache, error) { + return NewWithEvict(size, nil) +} + +// NewWithEvict constructs a fixed size cache with the given eviction +// callback. +func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) (*Cache, error) { + lru, err := simplelru.NewLRU(size, simplelru.EvictCallback(onEvicted)) + if err != nil { + return nil, err + } + c := &Cache{ + lru: lru, + } + return c, nil +} + +// Purge is used to completely clear the cache. +func (c *Cache) Purge() { + c.lock.Lock() + c.lru.Purge() + c.lock.Unlock() +} + +// Add adds a value to the cache. Returns true if an eviction occurred. +func (c *Cache) Add(key, value interface{}) (evicted bool) { + c.lock.Lock() + defer c.lock.Unlock() + return c.lru.Add(key, value) +} + +// Get looks up a key's value from the cache. +func (c *Cache) Get(key interface{}) (value interface{}, ok bool) { + c.lock.Lock() + defer c.lock.Unlock() + return c.lru.Get(key) +} + +// Contains checks if a key is in the cache, without updating the +// recent-ness or deleting it for being stale. +func (c *Cache) Contains(key interface{}) bool { + c.lock.RLock() + defer c.lock.RUnlock() + return c.lru.Contains(key) +} + +// Peek returns the key value (or undefined if not found) without updating +// the "recently used"-ness of the key. +func (c *Cache) Peek(key interface{}) (value interface{}, ok bool) { + c.lock.RLock() + defer c.lock.RUnlock() + return c.lru.Peek(key) +} + +// ContainsOrAdd checks if a key is in the cache without updating the +// recent-ness or deleting it for being stale, and if not, adds the value. +// Returns whether found and whether an eviction occurred. +func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evicted bool) { + c.lock.Lock() + defer c.lock.Unlock() + + if c.lru.Contains(key) { + return true, false + } + evicted = c.lru.Add(key, value) + return false, evicted +} + +// Remove removes the provided key from the cache. +func (c *Cache) Remove(key interface{}) { + c.lock.Lock() + c.lru.Remove(key) + c.lock.Unlock() +} + +// RemoveOldest removes the oldest item from the cache. +func (c *Cache) RemoveOldest() { + c.lock.Lock() + c.lru.RemoveOldest() + c.lock.Unlock() +} + +// Keys returns a slice of the keys in the cache, from oldest to newest. +func (c *Cache) Keys() []interface{} { + c.lock.RLock() + defer c.lock.RUnlock() + return c.lru.Keys() +} + +// Len returns the number of items in the cache. +func (c *Cache) Len() int { + c.lock.RLock() + defer c.lock.RUnlock() + return c.lru.Len() +} diff --git a/vendor/github.com/hashicorp/golang-lru/lru_test.go b/vendor/github.com/hashicorp/golang-lru/lru_test.go new file mode 100644 index 00000000..e7e23505 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/lru_test.go @@ -0,0 +1,221 @@ +package lru + +import ( + "math/rand" + "testing" +) + +func BenchmarkLRU_Rand(b *testing.B) { + l, err := New(8192) + if err != nil { + b.Fatalf("err: %v", err) + } + + trace := make([]int64, b.N*2) + for i := 0; i < b.N*2; i++ { + trace[i] = rand.Int63() % 32768 + } + + b.ResetTimer() + + var hit, miss int + for i := 0; i < 2*b.N; i++ { + if i%2 == 0 { + l.Add(trace[i], trace[i]) + } else { + _, ok := l.Get(trace[i]) + if ok { + hit++ + } else { + miss++ + } + } + } + b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss)) +} + +func BenchmarkLRU_Freq(b *testing.B) { + l, err := New(8192) + if err != nil { + b.Fatalf("err: %v", err) + } + + trace := make([]int64, b.N*2) + for i := 0; i < b.N*2; i++ { + if i%2 == 0 { + trace[i] = rand.Int63() % 16384 + } else { + trace[i] = rand.Int63() % 32768 + } + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + l.Add(trace[i], trace[i]) + } + var hit, miss int + for i := 0; i < b.N; i++ { + _, ok := l.Get(trace[i]) + if ok { + hit++ + } else { + miss++ + } + } + b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss)) +} + +func TestLRU(t *testing.T) { + evictCounter := 0 + onEvicted := func(k interface{}, v interface{}) { + if k != v { + t.Fatalf("Evict values not equal (%v!=%v)", k, v) + } + evictCounter++ + } + l, err := NewWithEvict(128, onEvicted) + if err != nil { + t.Fatalf("err: %v", err) + } + + for i := 0; i < 256; i++ { + l.Add(i, i) + } + if l.Len() != 128 { + t.Fatalf("bad len: %v", l.Len()) + } + + if evictCounter != 128 { + t.Fatalf("bad evict count: %v", evictCounter) + } + + for i, k := range l.Keys() { + if v, ok := l.Get(k); !ok || v != k || v != i+128 { + t.Fatalf("bad key: %v", k) + } + } + for i := 0; i < 128; i++ { + _, ok := l.Get(i) + if ok { + t.Fatalf("should be evicted") + } + } + for i := 128; i < 256; i++ { + _, ok := l.Get(i) + if !ok { + t.Fatalf("should not be evicted") + } + } + for i := 128; i < 192; i++ { + l.Remove(i) + _, ok := l.Get(i) + if ok { + t.Fatalf("should be deleted") + } + } + + l.Get(192) // expect 192 to be last key in l.Keys() + + for i, k := range l.Keys() { + if (i < 63 && k != i+193) || (i == 63 && k != 192) { + t.Fatalf("out of order key: %v", k) + } + } + + l.Purge() + if l.Len() != 0 { + t.Fatalf("bad len: %v", l.Len()) + } + if _, ok := l.Get(200); ok { + t.Fatalf("should contain nothing") + } +} + +// test that Add returns true/false if an eviction occurred +func TestLRUAdd(t *testing.T) { + evictCounter := 0 + onEvicted := func(k interface{}, v interface{}) { + evictCounter++ + } + + l, err := NewWithEvict(1, onEvicted) + if err != nil { + t.Fatalf("err: %v", err) + } + + if l.Add(1, 1) == true || evictCounter != 0 { + t.Errorf("should not have an eviction") + } + if l.Add(2, 2) == false || evictCounter != 1 { + t.Errorf("should have an eviction") + } +} + +// test that Contains doesn't update recent-ness +func TestLRUContains(t *testing.T) { + l, err := New(2) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + if !l.Contains(1) { + t.Errorf("1 should be contained") + } + + l.Add(3, 3) + if l.Contains(1) { + t.Errorf("Contains should not have updated recent-ness of 1") + } +} + +// test that Contains doesn't update recent-ness +func TestLRUContainsOrAdd(t *testing.T) { + l, err := New(2) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + contains, evict := l.ContainsOrAdd(1, 1) + if !contains { + t.Errorf("1 should be contained") + } + if evict { + t.Errorf("nothing should be evicted here") + } + + l.Add(3, 3) + contains, evict = l.ContainsOrAdd(1, 1) + if contains { + t.Errorf("1 should not have been contained") + } + if !evict { + t.Errorf("an eviction should have occurred") + } + if !l.Contains(1) { + t.Errorf("now 1 should be contained") + } +} + +// test that Peek doesn't update recent-ness +func TestLRUPeek(t *testing.T) { + l, err := New(2) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + if v, ok := l.Peek(1); !ok || v != 1 { + t.Errorf("1 should be set to 1: %v, %v", v, ok) + } + + l.Add(3, 3) + if l.Contains(1) { + t.Errorf("should not have updated recent-ness of 1") + } +} diff --git a/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go b/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go new file mode 100644 index 00000000..5673773b --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go @@ -0,0 +1,161 @@ +package simplelru + +import ( + "container/list" + "errors" +) + +// EvictCallback is used to get a callback when a cache entry is evicted +type EvictCallback func(key interface{}, value interface{}) + +// LRU implements a non-thread safe fixed size LRU cache +type LRU struct { + size int + evictList *list.List + items map[interface{}]*list.Element + onEvict EvictCallback +} + +// entry is used to hold a value in the evictList +type entry struct { + key interface{} + value interface{} +} + +// NewLRU constructs an LRU of the given size +func NewLRU(size int, onEvict EvictCallback) (*LRU, error) { + if size <= 0 { + return nil, errors.New("Must provide a positive size") + } + c := &LRU{ + size: size, + evictList: list.New(), + items: make(map[interface{}]*list.Element), + onEvict: onEvict, + } + return c, nil +} + +// Purge is used to completely clear the cache. +func (c *LRU) Purge() { + for k, v := range c.items { + if c.onEvict != nil { + c.onEvict(k, v.Value.(*entry).value) + } + delete(c.items, k) + } + c.evictList.Init() +} + +// Add adds a value to the cache. Returns true if an eviction occurred. +func (c *LRU) Add(key, value interface{}) (evicted bool) { + // Check for existing item + if ent, ok := c.items[key]; ok { + c.evictList.MoveToFront(ent) + ent.Value.(*entry).value = value + return false + } + + // Add new item + ent := &entry{key, value} + entry := c.evictList.PushFront(ent) + c.items[key] = entry + + evict := c.evictList.Len() > c.size + // Verify size not exceeded + if evict { + c.removeOldest() + } + return evict +} + +// Get looks up a key's value from the cache. +func (c *LRU) Get(key interface{}) (value interface{}, ok bool) { + if ent, ok := c.items[key]; ok { + c.evictList.MoveToFront(ent) + return ent.Value.(*entry).value, true + } + return +} + +// Contains checks if a key is in the cache, without updating the recent-ness +// or deleting it for being stale. +func (c *LRU) Contains(key interface{}) (ok bool) { + _, ok = c.items[key] + return ok +} + +// Peek returns the key value (or undefined if not found) without updating +// the "recently used"-ness of the key. +func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) { + var ent *list.Element + if ent, ok = c.items[key]; ok { + return ent.Value.(*entry).value, true + } + return nil, ok +} + +// Remove removes the provided key from the cache, returning if the +// key was contained. +func (c *LRU) Remove(key interface{}) (present bool) { + if ent, ok := c.items[key]; ok { + c.removeElement(ent) + return true + } + return false +} + +// RemoveOldest removes the oldest item from the cache. +func (c *LRU) RemoveOldest() (key interface{}, value interface{}, ok bool) { + ent := c.evictList.Back() + if ent != nil { + c.removeElement(ent) + kv := ent.Value.(*entry) + return kv.key, kv.value, true + } + return nil, nil, false +} + +// GetOldest returns the oldest entry +func (c *LRU) GetOldest() (key interface{}, value interface{}, ok bool) { + ent := c.evictList.Back() + if ent != nil { + kv := ent.Value.(*entry) + return kv.key, kv.value, true + } + return nil, nil, false +} + +// Keys returns a slice of the keys in the cache, from oldest to newest. +func (c *LRU) Keys() []interface{} { + keys := make([]interface{}, len(c.items)) + i := 0 + for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() { + keys[i] = ent.Value.(*entry).key + i++ + } + return keys +} + +// Len returns the number of items in the cache. +func (c *LRU) Len() int { + return c.evictList.Len() +} + +// removeOldest removes the oldest item from the cache. +func (c *LRU) removeOldest() { + ent := c.evictList.Back() + if ent != nil { + c.removeElement(ent) + } +} + +// removeElement is used to remove a given list element from the cache +func (c *LRU) removeElement(e *list.Element) { + c.evictList.Remove(e) + kv := e.Value.(*entry) + delete(c.items, kv.key) + if c.onEvict != nil { + c.onEvict(kv.key, kv.value) + } +} diff --git a/vendor/github.com/hashicorp/golang-lru/simplelru/lru_interface.go b/vendor/github.com/hashicorp/golang-lru/simplelru/lru_interface.go new file mode 100644 index 00000000..744cac01 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/simplelru/lru_interface.go @@ -0,0 +1,37 @@ +package simplelru + + +// LRUCache is the interface for simple LRU cache. +type LRUCache interface { + // Adds a value to the cache, returns true if an eviction occurred and + // updates the "recently used"-ness of the key. + Add(key, value interface{}) bool + + // Returns key's value from the cache and + // updates the "recently used"-ness of the key. #value, isFound + Get(key interface{}) (value interface{}, ok bool) + + // Check if a key exsists in cache without updating the recent-ness. + Contains(key interface{}) (ok bool) + + // Returns key's value without updating the "recently used"-ness of the key. + Peek(key interface{}) (value interface{}, ok bool) + + // Removes a key from the cache. + Remove(key interface{}) bool + + // Removes the oldest entry from cache. + RemoveOldest() (interface{}, interface{}, bool) + + // Returns the oldest entry from the cache. #key, value, isFound + GetOldest() (interface{}, interface{}, bool) + + // Returns a slice of the keys in the cache, from oldest to newest. + Keys() []interface{} + + // Returns the number of items in the cache. + Len() int + + // Clear all cache entries + Purge() +} diff --git a/vendor/github.com/hashicorp/golang-lru/simplelru/lru_test.go b/vendor/github.com/hashicorp/golang-lru/simplelru/lru_test.go new file mode 100644 index 00000000..ca5676e1 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/simplelru/lru_test.go @@ -0,0 +1,167 @@ +package simplelru + +import "testing" + +func TestLRU(t *testing.T) { + evictCounter := 0 + onEvicted := func(k interface{}, v interface{}) { + if k != v { + t.Fatalf("Evict values not equal (%v!=%v)", k, v) + } + evictCounter++ + } + l, err := NewLRU(128, onEvicted) + if err != nil { + t.Fatalf("err: %v", err) + } + + for i := 0; i < 256; i++ { + l.Add(i, i) + } + if l.Len() != 128 { + t.Fatalf("bad len: %v", l.Len()) + } + + if evictCounter != 128 { + t.Fatalf("bad evict count: %v", evictCounter) + } + + for i, k := range l.Keys() { + if v, ok := l.Get(k); !ok || v != k || v != i+128 { + t.Fatalf("bad key: %v", k) + } + } + for i := 0; i < 128; i++ { + _, ok := l.Get(i) + if ok { + t.Fatalf("should be evicted") + } + } + for i := 128; i < 256; i++ { + _, ok := l.Get(i) + if !ok { + t.Fatalf("should not be evicted") + } + } + for i := 128; i < 192; i++ { + ok := l.Remove(i) + if !ok { + t.Fatalf("should be contained") + } + ok = l.Remove(i) + if ok { + t.Fatalf("should not be contained") + } + _, ok = l.Get(i) + if ok { + t.Fatalf("should be deleted") + } + } + + l.Get(192) // expect 192 to be last key in l.Keys() + + for i, k := range l.Keys() { + if (i < 63 && k != i+193) || (i == 63 && k != 192) { + t.Fatalf("out of order key: %v", k) + } + } + + l.Purge() + if l.Len() != 0 { + t.Fatalf("bad len: %v", l.Len()) + } + if _, ok := l.Get(200); ok { + t.Fatalf("should contain nothing") + } +} + +func TestLRU_GetOldest_RemoveOldest(t *testing.T) { + l, err := NewLRU(128, nil) + if err != nil { + t.Fatalf("err: %v", err) + } + for i := 0; i < 256; i++ { + l.Add(i, i) + } + k, _, ok := l.GetOldest() + if !ok { + t.Fatalf("missing") + } + if k.(int) != 128 { + t.Fatalf("bad: %v", k) + } + + k, _, ok = l.RemoveOldest() + if !ok { + t.Fatalf("missing") + } + if k.(int) != 128 { + t.Fatalf("bad: %v", k) + } + + k, _, ok = l.RemoveOldest() + if !ok { + t.Fatalf("missing") + } + if k.(int) != 129 { + t.Fatalf("bad: %v", k) + } +} + +// Test that Add returns true/false if an eviction occurred +func TestLRU_Add(t *testing.T) { + evictCounter := 0 + onEvicted := func(k interface{}, v interface{}) { + evictCounter++ + } + + l, err := NewLRU(1, onEvicted) + if err != nil { + t.Fatalf("err: %v", err) + } + + if l.Add(1, 1) == true || evictCounter != 0 { + t.Errorf("should not have an eviction") + } + if l.Add(2, 2) == false || evictCounter != 1 { + t.Errorf("should have an eviction") + } +} + +// Test that Contains doesn't update recent-ness +func TestLRU_Contains(t *testing.T) { + l, err := NewLRU(2, nil) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + if !l.Contains(1) { + t.Errorf("1 should be contained") + } + + l.Add(3, 3) + if l.Contains(1) { + t.Errorf("Contains should not have updated recent-ness of 1") + } +} + +// Test that Peek doesn't update recent-ness +func TestLRU_Peek(t *testing.T) { + l, err := NewLRU(2, nil) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + if v, ok := l.Peek(1); !ok || v != 1 { + t.Errorf("1 should be set to 1: %v, %v", v, ok) + } + + l.Add(3, 3) + if l.Contains(1) { + t.Errorf("should not have updated recent-ness of 1") + } +}