Remove git.gammaspectra.live/P2Pool/go-monero dependency, replace with pkg/rpc and pkg/levin inline
This commit is contained in:
parent
c999597d5e
commit
5a50924816
9
go.mod
9
go.mod
|
@ -4,7 +4,6 @@ go 1.22
|
|||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20240405085108-e2f706cb5c00
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7
|
||||
git.gammaspectra.live/P2Pool/monero-base58 v1.0.0
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71
|
||||
|
@ -13,6 +12,8 @@ require (
|
|||
github.com/floatdrop/lru v1.3.0
|
||||
github.com/go-zeromq/zmq4 v0.16.1-0.20240124085909-e75c615ba1b3
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/sclevine/spec v1.4.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc
|
||||
golang.org/x/sys v0.19.0
|
||||
lukechampine.com/uint128 v1.3.0
|
||||
|
@ -20,10 +21,16 @@ require (
|
|||
|
||||
require (
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/goccy/go-json => github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887
|
||||
|
|
19
go.sum
19
go.sum
|
@ -1,7 +1,5 @@
|
|||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20240405085108-e2f706cb5c00 h1:mDQY337iKB+kle5RYWL5CoAz+3DmnkAh/B2XD8B+PFk=
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20240405085108-e2f706cb5c00/go.mod h1:FZsrMWGucMP3SZamzrd7m562geIs5zp1O/9MGoiAKH0=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 h1:oIJzm7kQyASS0xlJ79VSWRvvfXp2Qt7M05+E20o9gwE=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523/go.mod h1:TAOAAV972JNDkCzyV5SkbYkKCRvcfhvvFa8LHH4Dg6g=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 h1:bzHDuu1IgETKqPBOlIdCE2LaZIJ+ZpROSprNn+fnzd8=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
|
||||
git.gammaspectra.live/P2Pool/monero-base58 v1.0.0 h1:s8LZxVNc93YEs2NCCNWZ7CKr8RbEb031y6Wkvhn+TS4=
|
||||
|
@ -16,6 +14,8 @@ github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887 h1:P01nqSM
|
|||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
|
@ -24,10 +24,21 @@ github.com/dolthub/swiss v0.2.2-0.20240312182618-f4b2babd2bc1 h1:F7u1ZVCidajlPuJ
|
|||
github.com/dolthub/swiss v0.2.2-0.20240312182618-f4b2babd2bc1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
|
||||
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
|
||||
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
|
@ -40,6 +51,10 @@ golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
|||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
|
|
|
@ -4,11 +4,11 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/consensus/v3/monero/client/rpc"
|
||||
"git.gammaspectra.live/P2Pool/consensus/v3/monero/client/rpc/daemon"
|
||||
"git.gammaspectra.live/P2Pool/consensus/v3/monero/transaction"
|
||||
"git.gammaspectra.live/P2Pool/consensus/v3/types"
|
||||
"git.gammaspectra.live/P2Pool/consensus/v3/utils"
|
||||
"git.gammaspectra.live/P2Pool/go-monero/pkg/rpc"
|
||||
"git.gammaspectra.live/P2Pool/go-monero/pkg/rpc/daemon"
|
||||
"github.com/floatdrop/lru"
|
||||
fasthex "github.com/tmthrgd/go-hex"
|
||||
"sync"
|
||||
|
|
201
monero/client/levin/LICENSE
Normal file
201
monero/client/levin/LICENSE
Normal file
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"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.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"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.
|
||||
|
||||
"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).
|
||||
|
||||
"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.
|
||||
|
||||
"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."
|
||||
|
||||
"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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(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
|
||||
|
||||
(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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your 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.
|
||||
|
||||
Copyright 2021 Ciro S. Costa
|
||||
|
||||
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.
|
3
monero/client/levin/README.md
Normal file
3
monero/client/levin/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# go-monero RPC
|
||||
|
||||
Taken from [git.gammaspectra.live/P2Pool/go-monero](https://git.gammaspectra.live/P2Pool/go-monero/src/commit/910450c4a523a8a21c0bcabf7d303418e6c76d50/pkg/levin)
|
74
monero/client/levin/boost.go
Normal file
74
monero/client/levin/boost.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package levin
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
BoostSerializeTypeInt64 byte = 0x1
|
||||
BoostSerializeTypeInt32 byte = 0x2
|
||||
BoostSerializeTypeInt16 byte = 0x3
|
||||
BoostSerializeTypeInt8 byte = 0x4
|
||||
|
||||
BoostSerializeTypeUint64 byte = 0x5
|
||||
BoostSerializeTypeUint32 byte = 0x6
|
||||
BoostSerializeTypeUint16 byte = 0x7
|
||||
BoostSerializeTypeUint8 byte = 0x8
|
||||
|
||||
BoostSerializeTypeDouble byte = 0x9
|
||||
|
||||
BoostSerializeTypeString byte = 0x0a
|
||||
BoostSerializeTypeBool byte = 0x0b
|
||||
BoostSerializeTypeObject byte = 0x0c
|
||||
BoostSerializeTypeArray byte = 0xd
|
||||
|
||||
BoostSerializeFlagArray byte = 0x80
|
||||
)
|
||||
|
||||
type BoostByte byte
|
||||
|
||||
func (v BoostByte) Bytes() []byte {
|
||||
return []byte{
|
||||
BoostSerializeTypeUint8,
|
||||
byte(v),
|
||||
}
|
||||
}
|
||||
|
||||
type BoostUint32 uint32
|
||||
|
||||
func (v BoostUint32) Bytes() []byte {
|
||||
b := []byte{
|
||||
BoostSerializeTypeUint32,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
binary.LittleEndian.PutUint32(b[1:], uint32(v))
|
||||
return b
|
||||
}
|
||||
|
||||
type BoostUint64 uint64
|
||||
|
||||
func (v BoostUint64) Bytes() []byte {
|
||||
b := []byte{
|
||||
BoostSerializeTypeUint64,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint64(b[1:], uint64(v))
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
type BoostString string
|
||||
|
||||
func (v BoostString) Bytes() []byte {
|
||||
b := []byte{BoostSerializeTypeString}
|
||||
|
||||
varInB, err := VarIn(len(v))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("varin '%d': %w", len(v), err))
|
||||
}
|
||||
|
||||
return append(b, append(varInB, []byte(v)...)...)
|
||||
}
|
144
monero/client/levin/client.go
Normal file
144
monero/client/levin/client.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
package levin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const DialTimeout = 15 * time.Second
|
||||
|
||||
type Client struct {
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
type ClientConfig struct {
|
||||
ContextDialer ContextDialer
|
||||
}
|
||||
|
||||
type ClientOption func(*ClientConfig)
|
||||
|
||||
type ContextDialer interface {
|
||||
DialContext(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
}
|
||||
|
||||
func WithContextDialer(v ContextDialer) func(*ClientConfig) {
|
||||
return func(c *ClientConfig) {
|
||||
c.ContextDialer = v
|
||||
}
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, addr string, opts ...ClientOption) (*Client, error) {
|
||||
cfg := &ClientConfig{
|
||||
ContextDialer: &net.Dialer{},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
conn, err := cfg.ContextDialer.DialContext(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dial ctx: %w", err)
|
||||
}
|
||||
|
||||
return &Client{
|
||||
conn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
if c.conn == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := c.conn.Close(); err != nil {
|
||||
return fmt.Errorf("close: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) Handshake(ctx context.Context) (*Node, error) {
|
||||
payload := (&PortableStorage{
|
||||
Entries: []Entry{
|
||||
{
|
||||
Name: "node_data",
|
||||
Serializable: &Section{
|
||||
Entries: []Entry{
|
||||
{
|
||||
Name: "network_id",
|
||||
Serializable: BoostString(string(MainnetNetworkId)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}).Bytes()
|
||||
|
||||
reqHeaderB := NewRequestHeader(CommandHandshake, uint64(len(payload))).Bytes()
|
||||
|
||||
if _, err := c.conn.Write(reqHeaderB); err != nil {
|
||||
return nil, fmt.Errorf("write header: %w", err)
|
||||
}
|
||||
|
||||
if _, err := c.conn.Write(payload); err != nil {
|
||||
return nil, fmt.Errorf("write payload: %w", err)
|
||||
}
|
||||
|
||||
again:
|
||||
responseHeaderB := make([]byte, LevinHeaderSizeBytes)
|
||||
if _, err := io.ReadFull(c.conn, responseHeaderB); err != nil {
|
||||
return nil, fmt.Errorf("read full header: %w", err)
|
||||
}
|
||||
|
||||
respHeader, err := NewHeaderFromBytesBytes(responseHeaderB)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new header from resp bytes: %w", err)
|
||||
}
|
||||
|
||||
dest := new(bytes.Buffer)
|
||||
|
||||
if respHeader.Length != 0 {
|
||||
if _, err := io.CopyN(dest, c.conn, int64(respHeader.Length)); err != nil {
|
||||
return nil, fmt.Errorf("copy payload to stdout: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if respHeader.Command != CommandHandshake {
|
||||
dest.Reset()
|
||||
goto again
|
||||
}
|
||||
|
||||
ps, err := NewPortableStorageFromBytes(dest.Bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new portable storage from bytes: %w", err)
|
||||
}
|
||||
|
||||
peerList := NewNodeFromEntries(ps.Entries)
|
||||
return &peerList, nil
|
||||
}
|
||||
|
||||
func (c *Client) Ping(ctx context.Context) error {
|
||||
reqHeaderB := NewRequestHeader(CommandPing, 0).Bytes()
|
||||
|
||||
if _, err := c.conn.Write(reqHeaderB); err != nil {
|
||||
return fmt.Errorf("write: %w", err)
|
||||
}
|
||||
|
||||
responseHeaderB := make([]byte, LevinHeaderSizeBytes)
|
||||
if _, err := io.ReadFull(c.conn, responseHeaderB); err != nil {
|
||||
return fmt.Errorf("read full header: %w", err)
|
||||
}
|
||||
|
||||
respHeader, err := NewHeaderFromBytesBytes(responseHeaderB)
|
||||
if err != nil {
|
||||
return fmt.Errorf("new header from resp bytes: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%+v\n", respHeader)
|
||||
|
||||
return nil
|
||||
}
|
307
monero/client/levin/levin.go
Normal file
307
monero/client/levin/levin.go
Normal file
|
@ -0,0 +1,307 @@
|
|||
//
|
||||
// see https://github.com/monero-project/monero/blob/e45619e61e4831eea70a43fe6985f4d57ea02e9e/contrib/epee/include/net/levin_base.h
|
||||
// see https://github.com/monero-project/monero/blob/e45619e61e4831eea70a43fe6985f4d57ea02e9e/docs/LEVIN_PROTOCOL.md
|
||||
|
||||
package levin
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
LevinSignature uint64 = 0x0101010101012101 // Dander's Nightmare
|
||||
|
||||
LevinProtocolVersion uint32 = 1
|
||||
|
||||
LevinPacketRequest uint32 = 0x00000001 // Q flag
|
||||
LevinPacketReponse uint32 = 0x00000002 // S flag
|
||||
LevinPacketMaxDefaultSize uint64 = 100000000 // 100MB _after_ handshake
|
||||
LevinPacketMaxInitialSize uint64 = 256 * 1024 // 256KiB _before_ handshake
|
||||
|
||||
LevinHeaderSizeBytes = 33
|
||||
)
|
||||
|
||||
const (
|
||||
// Return Codes.
|
||||
LevinOk int32 = 0
|
||||
LevinErrorConnection int32 = -1
|
||||
LevinErrorConnectionNotFound int32 = -2
|
||||
LevinErrorConnectionDestroyed int32 = -3
|
||||
LevinErrorConnectionTimedout int32 = -4
|
||||
LevinErrorConnectionNoDuplexProtocol int32 = -5
|
||||
LevinErrorConnectionHandlerNotDefined int32 = -6
|
||||
LevinErrorFormat int32 = -7
|
||||
)
|
||||
|
||||
func IsValidReturnCode(c int32) bool {
|
||||
// anything >= 0 is good (there are some `1`s in the code :shrug:)
|
||||
return c >= LevinErrorFormat
|
||||
}
|
||||
|
||||
const (
|
||||
// p2p admin commands.
|
||||
CommandHandshake uint32 = 1001
|
||||
CommandTimedSync uint32 = 1002
|
||||
CommandPing uint32 = 1003
|
||||
CommandStat uint32 = 1004
|
||||
CommandNetworkState uint32 = 1005
|
||||
CommandPeerID uint32 = 1006
|
||||
CommandSupportFlags uint32 = 1007
|
||||
)
|
||||
|
||||
var (
|
||||
MainnetNetworkId = []byte{
|
||||
0x12, 0x30, 0xf1, 0x71,
|
||||
0x61, 0x04, 0x41, 0x61,
|
||||
0x17, 0x31, 0x00, 0x82,
|
||||
0x16, 0xa1, 0xa1, 0x10,
|
||||
}
|
||||
|
||||
MainnetGenesisTx = "418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3"
|
||||
)
|
||||
|
||||
func IsValidCommand(c uint32) bool {
|
||||
return (c >= CommandHandshake && c <= CommandSupportFlags)
|
||||
}
|
||||
|
||||
//
|
||||
// Header
|
||||
//
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | 0x01 | 0x21 | 0x01 | 0x01 |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | 0x01 | 0x01 | 0x01 | 0x01 |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Length |
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | E. Response | _ Command _
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | _ Return Code _
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |Q|S|B|E| _ Reserved_
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | 0x01 | 0x00 | 0x00 |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | 0x00 |
|
||||
// +-+-+-+-+-+-+-+-+
|
||||
//
|
||||
//
|
||||
// i.e.,
|
||||
//
|
||||
// BYTE(0X01) BYTE(0X21) BYTE(0X01) BYTE(0X01) ---.
|
||||
// +--> protocol identification
|
||||
// BYTE(0X01) BYTE(0X01) BYTE(0X01) BYTE(0X01) ---'
|
||||
//
|
||||
//
|
||||
// UINT64(LENGTH) -----------------------------------> unsigned little-endian 64bit integer
|
||||
// length of the payload _not including_
|
||||
// the header. messages >100MB are rejected.
|
||||
//
|
||||
//
|
||||
// BYTE(E.RESPONSE) 4BYTE(COMMAND) 4BYTE(RET CODE)
|
||||
// | | |
|
||||
// | | |
|
||||
// | | '-> signed 32-bit little endian integer representing the response
|
||||
// | | from the peer from the last command invoked. `0` for request msgs.
|
||||
// | |
|
||||
// | '-> unsigned 32-bit little endian integer
|
||||
// | representing the monero specific cmd
|
||||
// |
|
||||
// '-> zero-byte if no response is expected from the peer, non-zero if response is expected.
|
||||
// peers must respond to requests w/ this flag in the same order as received.
|
||||
//
|
||||
//
|
||||
// BIT(Q) BIT(S) BIT(B) BIT(E) 3BYTE+4BIT(RESERVED)
|
||||
// | | | |
|
||||
// | | | |
|
||||
// | | | '-> set if this is the end of a frag msg
|
||||
// | | |
|
||||
// | | '-> set if this is the beginning of a frag msg
|
||||
// | |
|
||||
// | '-> set if the message is a response
|
||||
// |
|
||||
// '-> set if the message is a request
|
||||
//
|
||||
//
|
||||
//
|
||||
// BYTE(0X01) BYTE(0X00) BYTE(0X00) BYTE(0X00)
|
||||
// |
|
||||
// '--> version
|
||||
//
|
||||
type Header struct {
|
||||
Signature uint64
|
||||
Length uint64
|
||||
ExpectsResponse bool
|
||||
Command uint32
|
||||
ReturnCode int32
|
||||
Flags uint32 // only 4 most significant bits matter (Q|S|B|E)
|
||||
Version uint32
|
||||
}
|
||||
|
||||
func NewRequestHeader(command uint32, length uint64) *Header {
|
||||
return &Header{
|
||||
Signature: LevinSignature,
|
||||
Length: length,
|
||||
ExpectsResponse: true,
|
||||
Command: command,
|
||||
ReturnCode: 0,
|
||||
Flags: LevinPacketRequest,
|
||||
Version: LevinProtocolVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func NewHeaderFromBytesBytes(bytes []byte) (*Header, error) {
|
||||
if len(bytes) != LevinHeaderSizeBytes {
|
||||
return nil, fmt.Errorf("invalid header size: expected %d, has %d",
|
||||
LevinHeaderSizeBytes, len(bytes),
|
||||
)
|
||||
}
|
||||
|
||||
var (
|
||||
size = 0
|
||||
idx = 0
|
||||
)
|
||||
|
||||
header := &Header{}
|
||||
|
||||
{ // signature
|
||||
size = 8
|
||||
header.Signature = binary.LittleEndian.Uint64(bytes[idx : idx+size])
|
||||
idx += size
|
||||
|
||||
if header.Signature != LevinSignature {
|
||||
return nil, fmt.Errorf("signature mismatch: expected %x, got %x",
|
||||
LevinSignature, header.Signature,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
{ // length
|
||||
size = 8
|
||||
header.Length = binary.LittleEndian.Uint64(bytes[idx : idx+size])
|
||||
idx += size
|
||||
}
|
||||
|
||||
{ // expects response
|
||||
size = 1
|
||||
header.ExpectsResponse = (bytes[idx] != 0)
|
||||
idx += size
|
||||
}
|
||||
|
||||
{ // command
|
||||
size = 4
|
||||
header.Command = binary.LittleEndian.Uint32(bytes[idx : idx+size])
|
||||
idx += size
|
||||
|
||||
if !IsValidCommand(header.Command) {
|
||||
return nil, fmt.Errorf("invalid command %d", header.Command)
|
||||
}
|
||||
}
|
||||
|
||||
{ // return code
|
||||
size = 4
|
||||
header.ReturnCode = int32(binary.LittleEndian.Uint32(bytes[idx : idx+size]))
|
||||
idx += size
|
||||
|
||||
if !IsValidReturnCode(header.ReturnCode) {
|
||||
return nil, fmt.Errorf("invalid return code %d", header.ReturnCode)
|
||||
}
|
||||
}
|
||||
|
||||
{ // flags
|
||||
size = 4
|
||||
header.Flags = binary.LittleEndian.Uint32(bytes[idx : idx+size])
|
||||
idx += size
|
||||
}
|
||||
|
||||
{ // version
|
||||
size = 4
|
||||
header.Version = binary.LittleEndian.Uint32(bytes[idx : idx+size])
|
||||
idx += size
|
||||
|
||||
if header.Version != LevinProtocolVersion {
|
||||
return nil, fmt.Errorf("invalid version %x",
|
||||
header.Version)
|
||||
}
|
||||
}
|
||||
|
||||
return header, nil
|
||||
}
|
||||
|
||||
func (h *Header) Bytes() []byte {
|
||||
var (
|
||||
header = make([]byte, LevinHeaderSizeBytes) // full header
|
||||
b = make([]byte, 8) // biggest type
|
||||
|
||||
idx = 0
|
||||
size = 0
|
||||
)
|
||||
|
||||
{ // signature
|
||||
size = 8
|
||||
|
||||
binary.LittleEndian.PutUint64(b, h.Signature)
|
||||
copy(header[idx:], b[:size])
|
||||
idx += size
|
||||
}
|
||||
|
||||
{ // length
|
||||
size = 8
|
||||
|
||||
binary.LittleEndian.PutUint64(b, h.Length)
|
||||
copy(header[idx:], b[:size])
|
||||
idx += size
|
||||
}
|
||||
|
||||
{ // expects response
|
||||
size = 1
|
||||
|
||||
if h.ExpectsResponse {
|
||||
b[0] = 0x01
|
||||
} else {
|
||||
b[0] = 0x00
|
||||
}
|
||||
|
||||
copy(header[idx:], b[:size])
|
||||
idx += size
|
||||
}
|
||||
|
||||
{ // command
|
||||
size = 4
|
||||
|
||||
binary.LittleEndian.PutUint32(b, h.Command)
|
||||
copy(header[idx:], b[:size])
|
||||
idx += size
|
||||
}
|
||||
|
||||
{ // return code
|
||||
size = 4
|
||||
|
||||
binary.LittleEndian.PutUint32(b, uint32(h.ReturnCode))
|
||||
copy(header[idx:], b[:size])
|
||||
idx += size
|
||||
}
|
||||
|
||||
{ // flags
|
||||
size = 4
|
||||
|
||||
binary.LittleEndian.PutUint32(b, h.Flags)
|
||||
copy(header[idx:], b[:size])
|
||||
idx += size
|
||||
}
|
||||
|
||||
{ // version
|
||||
size = 4
|
||||
|
||||
binary.LittleEndian.PutUint32(b, h.Version)
|
||||
copy(header[idx:], b[:size])
|
||||
idx += size
|
||||
}
|
||||
|
||||
return header
|
||||
}
|
151
monero/client/levin/levin_test.go
Normal file
151
monero/client/levin/levin_test.go
Normal file
|
@ -0,0 +1,151 @@
|
|||
package levin_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sclevine/spec"
|
||||
"github.com/sclevine/spec/report"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"git.gammaspectra.live/P2Pool/consensus/v3/monero/client/levin"
|
||||
)
|
||||
|
||||
func TestLevin(t *testing.T) {
|
||||
spec.Run(t, "NewHeaderFromBytes", func(t *testing.T, when spec.G, it spec.S) {
|
||||
it("fails w/ wrong size", func() {
|
||||
bytes := []byte{
|
||||
0xff,
|
||||
}
|
||||
|
||||
_, err := levin.NewHeaderFromBytesBytes(bytes)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
it("fails w/ wrong signature", func() {
|
||||
bytes := []byte{
|
||||
0xff, 0xff, 0xff, 0xff, // signature
|
||||
0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, // length
|
||||
0x00, 0x00, 0x00, 0x00, //
|
||||
0x00, // expects response
|
||||
0x00, 0x00, 0x00, 0x00, // command
|
||||
0x00, 0x00, 0x00, 0x00, // return code
|
||||
0x00, 0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x00, // version
|
||||
}
|
||||
|
||||
_, err := levin.NewHeaderFromBytesBytes(bytes)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "signature mismatch")
|
||||
})
|
||||
|
||||
it("fails w/ invalid command", func() {
|
||||
bytes := []byte{
|
||||
0x01, 0x21, 0x01, 0x01, // signature
|
||||
0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x00, 0x00, 0x00, // length
|
||||
0x00, 0x00, 0x00, 0x00, //
|
||||
0x01, // expects response
|
||||
0xff, 0xff, 0xff, 0xff, // command
|
||||
0x00, 0x00, 0x00, 0x00, // return code
|
||||
0x00, 0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x00, // version
|
||||
}
|
||||
|
||||
_, err := levin.NewHeaderFromBytesBytes(bytes)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "invalid command")
|
||||
})
|
||||
|
||||
it("fails w/ invalid return code", func() {
|
||||
bytes := []byte{
|
||||
0x01, 0x21, 0x01, 0x01, // signature
|
||||
0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x00, 0x00, 0x00, // length
|
||||
0x00, 0x00, 0x00, 0x00, //
|
||||
0x01, // expects response
|
||||
0xe9, 0x03, 0x00, 0x00, // command
|
||||
0xaa, 0xaa, 0xaa, 0xaa, // return code
|
||||
0x00, 0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x00, // version
|
||||
}
|
||||
|
||||
_, err := levin.NewHeaderFromBytesBytes(bytes)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "invalid return code")
|
||||
})
|
||||
|
||||
it("fails w/ invalid version", func() {
|
||||
bytes := []byte{
|
||||
0x01, 0x21, 0x01, 0x01, // signature
|
||||
0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x00, 0x00, 0x00, // length
|
||||
0x00, 0x00, 0x00, 0x00, //
|
||||
0x01, // expects response
|
||||
0xe9, 0x03, 0x00, 0x00, // command
|
||||
0x00, 0x00, 0x00, 0x00, // return code
|
||||
0x02, 0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x00, // version
|
||||
}
|
||||
|
||||
_, err := levin.NewHeaderFromBytesBytes(bytes)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "invalid version")
|
||||
})
|
||||
|
||||
it("assembles properly from pong", func() {
|
||||
bytes := []byte{
|
||||
0x01, 0x21, 0x01, 0x01, // signature
|
||||
0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x00, 0x00, 0x00, // length
|
||||
0x00, 0x00, 0x00, 0x00, //
|
||||
0x01, // expects response
|
||||
0xeb, 0x03, 0x00, 0x00, // command
|
||||
0x00, 0x00, 0x00, 0x00, // return code
|
||||
0x02, 0x00, 0x00, 0x00, // flags
|
||||
0x01, 0x00, 0x00, 0x00, // version
|
||||
}
|
||||
|
||||
header, err := levin.NewHeaderFromBytesBytes(bytes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, header.Command, levin.CommandPing)
|
||||
assert.Equal(t, header.ReturnCode, levin.LevinOk)
|
||||
assert.Equal(t, header.Flags, levin.LevinPacketReponse)
|
||||
assert.Equal(t, header.Version, levin.LevinProtocolVersion)
|
||||
})
|
||||
})
|
||||
|
||||
spec.Run(t, "NewRequestHeader", func(t *testing.T, when spec.G, it spec.S) {
|
||||
it("assembles properly w/ ping", func() {
|
||||
bytes := levin.NewRequestHeader(levin.CommandPing, 1).Bytes()
|
||||
|
||||
assert.ElementsMatch(t, bytes, []byte{
|
||||
0x01, 0x21, 0x01, 0x01, // signature
|
||||
0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x00, 0x00, 0x00, // length -- 0 for a ping msg
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x01, // expects response -- `true` bool
|
||||
0xeb, 0x03, 0x00, 0x00, // command -- 1003 for ping
|
||||
0x00, 0x00, 0x00, 0x00, // return code -- 0 for requests
|
||||
0x01, 0x00, 0x00, 0x00, // flags -- Q(1st lsb) set for req
|
||||
0x01, 0x00, 0x00, 0x00, // version
|
||||
})
|
||||
})
|
||||
|
||||
it("assembles properly w/ handshake", func() {
|
||||
bytes := levin.NewRequestHeader(levin.CommandHandshake, 4).Bytes()
|
||||
|
||||
assert.ElementsMatch(t, bytes, []byte{
|
||||
0x01, 0x21, 0x01, 0x01, // signature
|
||||
0x01, 0x01, 0x01, 0x01,
|
||||
0x04, 0x00, 0x00, 0x00, // length -- 0 for a ping msg
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x01, // expects response -- `true` bool
|
||||
0xe9, 0x03, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, // return code -- 0 for requests
|
||||
0x01, 0x00, 0x00, 0x00, // flags -- Q(1st lsb) set for req
|
||||
0x01, 0x00, 0x00, 0x00, // version
|
||||
})
|
||||
})
|
||||
}, spec.Report(report.Log{}), spec.Parallel(), spec.Random())
|
||||
}
|
133
monero/client/levin/node.go
Normal file
133
monero/client/levin/node.go
Normal file
|
@ -0,0 +1,133 @@
|
|||
package levin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
Peers map[string]*Peer
|
||||
|
||||
Id uint64
|
||||
RPCPort uint16
|
||||
|
||||
CurrentHeight uint64
|
||||
TopVersion uint8
|
||||
}
|
||||
|
||||
func (l *Node) GetPeers() map[string]*Peer {
|
||||
return l.Peers
|
||||
}
|
||||
|
||||
type Peer struct {
|
||||
Ip string
|
||||
Port uint16
|
||||
}
|
||||
|
||||
func (p Peer) Addr() string {
|
||||
return fmt.Sprintf("%s:%d", p.Ip, p.Port)
|
||||
}
|
||||
|
||||
func (p Peer) String() string {
|
||||
return p.Addr()
|
||||
}
|
||||
|
||||
func ParsePeerList(entry Entry) map[string]*Peer {
|
||||
peers := map[string]*Peer{}
|
||||
|
||||
peerList := entry.Entries()
|
||||
|
||||
for _, peer := range peerList {
|
||||
peerListAdr := peer.Entries()
|
||||
|
||||
for _, adr := range peerListAdr {
|
||||
if adr.Name != "adr" {
|
||||
continue
|
||||
}
|
||||
|
||||
addr := adr.Entries()
|
||||
|
||||
for _, addrField := range addr {
|
||||
if addrField.Name != "addr" {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := addrField.Entries()
|
||||
|
||||
var ip string
|
||||
var port uint16
|
||||
|
||||
for _, field := range fields {
|
||||
if field.Name == "m_ip" {
|
||||
ip = ipzify(field.Uint32())
|
||||
}
|
||||
|
||||
if field.Name == "m_port" {
|
||||
port = field.Uint16()
|
||||
}
|
||||
|
||||
if field.Name == "addr" {
|
||||
ip = net.IP([]byte(field.String())).String()
|
||||
}
|
||||
}
|
||||
|
||||
if ip != "" && port != 0 {
|
||||
peer := &Peer{
|
||||
Ip: ip,
|
||||
Port: port,
|
||||
}
|
||||
|
||||
peers[peer.Addr()] = peer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return peers
|
||||
}
|
||||
|
||||
// TODO less panic'ing.
|
||||
func NewNodeFromEntries(entries Entries) Node {
|
||||
lpl := Node{}
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.Name == "node_data" {
|
||||
for _, field := range entry.Entries() {
|
||||
switch field.Name {
|
||||
case "rpc_port":
|
||||
lpl.RPCPort = field.Uint16()
|
||||
case "peer_id":
|
||||
lpl.Id = field.Uint64()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if entry.Name == "payload_data" {
|
||||
for _, field := range entry.Entries() {
|
||||
switch field.Name {
|
||||
case "current_height":
|
||||
lpl.CurrentHeight = field.Uint64()
|
||||
case "top_version":
|
||||
lpl.TopVersion = field.Uint8()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if entry.Name == "local_peerlist_new" {
|
||||
lpl.Peers = ParsePeerList(entry)
|
||||
}
|
||||
}
|
||||
|
||||
return lpl
|
||||
}
|
||||
|
||||
func ipzify(ip uint32) string {
|
||||
result := make(net.IP, 4)
|
||||
|
||||
result[0] = byte(ip)
|
||||
result[1] = byte(ip >> 8)
|
||||
result[2] = byte(ip >> 16)
|
||||
result[3] = byte(ip >> 24)
|
||||
|
||||
return result.String()
|
||||
}
|
410
monero/client/levin/portable_storage.go
Normal file
410
monero/client/levin/portable_storage.go
Normal file
|
@ -0,0 +1,410 @@
|
|||
package levin
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
PortableStorageSignatureA uint32 = 0x01011101
|
||||
PortableStorageSignatureB uint32 = 0x01020101
|
||||
PortableStorageFormatVersion byte = 0x01
|
||||
|
||||
PortableRawSizeMarkMask byte = 0x03
|
||||
PortableRawSizeMarkByte byte = 0x00
|
||||
PortableRawSizeMarkWord uint16 = 0x01
|
||||
PortableRawSizeMarkDword uint32 = 0x02
|
||||
PortableRawSizeMarkInt64 uint64 = 0x03
|
||||
)
|
||||
|
||||
type Entry struct {
|
||||
Name string
|
||||
Serializable Serializable `json:"-,omitempty"`
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (e Entry) String() string {
|
||||
v, ok := e.Value.(string)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("interface couldnt be casted to string"))
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (e Entry) Uint8() uint8 {
|
||||
v, ok := e.Value.(uint8)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("interface couldnt be casted to uint8"))
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (e Entry) Uint16() uint16 {
|
||||
v, ok := e.Value.(uint16)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("interface couldnt be casted to uint16"))
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (e Entry) Uint32() uint32 {
|
||||
v, ok := e.Value.(uint32)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("interface couldnt be casted to uint32"))
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (e Entry) Uint64() uint64 {
|
||||
v, ok := e.Value.(uint64)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("interface couldnt be casted to uint64"))
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (e Entry) Entries() Entries {
|
||||
v, ok := e.Value.(Entries)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("interface couldnt be casted to levin.Entries"))
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (e Entry) Bytes() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
type Entries []Entry
|
||||
|
||||
func (e Entries) Bytes() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
type PortableStorage struct {
|
||||
Entries Entries
|
||||
}
|
||||
|
||||
func NewPortableStorageFromBytes(bytes []byte) (*PortableStorage, error) {
|
||||
var (
|
||||
size = 0
|
||||
idx = 0
|
||||
)
|
||||
|
||||
{ // sig-a
|
||||
size = 4
|
||||
|
||||
if len(bytes[idx:]) < size {
|
||||
return nil, fmt.Errorf("sig-a out of bounds")
|
||||
}
|
||||
|
||||
sig := binary.LittleEndian.Uint32(bytes[idx : idx+size])
|
||||
idx += size
|
||||
|
||||
if sig != uint32(PortableStorageSignatureA) {
|
||||
return nil, fmt.Errorf("sig-a doesn't match")
|
||||
}
|
||||
}
|
||||
|
||||
{ // sig-b
|
||||
size = 4
|
||||
sig := binary.LittleEndian.Uint32(bytes[idx : idx+size])
|
||||
idx += size
|
||||
|
||||
if sig != uint32(PortableStorageSignatureB) {
|
||||
return nil, fmt.Errorf("sig-b doesn't match")
|
||||
}
|
||||
}
|
||||
|
||||
{ // format ver
|
||||
size = 1
|
||||
version := bytes[idx]
|
||||
idx += size
|
||||
|
||||
if version != PortableStorageFormatVersion {
|
||||
return nil, fmt.Errorf("version doesn't match")
|
||||
}
|
||||
}
|
||||
|
||||
ps := &PortableStorage{}
|
||||
|
||||
_, ps.Entries = ReadObject(bytes[idx:])
|
||||
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func ReadString(bytes []byte) (int, string) {
|
||||
idx := 0
|
||||
|
||||
n, strLen := ReadVarInt(bytes)
|
||||
idx += n
|
||||
|
||||
return idx + strLen, string(bytes[idx : idx+strLen])
|
||||
}
|
||||
|
||||
func ReadObject(bytes []byte) (int, Entries) {
|
||||
idx := 0
|
||||
|
||||
n, i := ReadVarInt(bytes[idx:])
|
||||
idx += n
|
||||
|
||||
entries := make(Entries, i)
|
||||
|
||||
for iter := 0; iter < i; iter++ {
|
||||
entries[iter] = Entry{}
|
||||
entry := &entries[iter]
|
||||
|
||||
lenName := int(bytes[idx])
|
||||
idx += 1
|
||||
|
||||
entry.Name = string(bytes[idx : idx+lenName])
|
||||
idx += lenName
|
||||
|
||||
ttype := bytes[idx]
|
||||
idx += 1
|
||||
|
||||
n, obj := ReadAny(bytes[idx:], ttype)
|
||||
idx += n
|
||||
|
||||
entry.Value = obj
|
||||
}
|
||||
|
||||
return idx, entries
|
||||
}
|
||||
|
||||
func ReadArray(ttype byte, bytes []byte) (int, Entries) {
|
||||
var (
|
||||
idx = 0
|
||||
n = 0
|
||||
)
|
||||
|
||||
n, i := ReadVarInt(bytes[idx:])
|
||||
idx += n
|
||||
|
||||
entries := make(Entries, i)
|
||||
|
||||
for iter := 0; iter < i; iter++ {
|
||||
n, obj := ReadAny(bytes[idx:], ttype)
|
||||
idx += n
|
||||
|
||||
entries[iter] = Entry{
|
||||
Value: obj,
|
||||
}
|
||||
}
|
||||
|
||||
return idx, entries
|
||||
}
|
||||
|
||||
func ReadAny(bytes []byte, ttype byte) (int, interface{}) {
|
||||
var (
|
||||
idx = 0
|
||||
n = 0
|
||||
)
|
||||
|
||||
if ttype&BoostSerializeFlagArray != 0 {
|
||||
internalType := ttype &^ BoostSerializeFlagArray
|
||||
n, obj := ReadArray(internalType, bytes[idx:])
|
||||
idx += n
|
||||
|
||||
return idx, obj
|
||||
}
|
||||
|
||||
if ttype == BoostSerializeTypeObject {
|
||||
n, obj := ReadObject(bytes[idx:])
|
||||
idx += n
|
||||
|
||||
return idx, obj
|
||||
}
|
||||
|
||||
if ttype == BoostSerializeTypeUint8 {
|
||||
obj := uint8(bytes[idx])
|
||||
n += 1
|
||||
idx += n
|
||||
|
||||
return idx, obj
|
||||
}
|
||||
|
||||
if ttype == BoostSerializeTypeUint16 {
|
||||
obj := binary.LittleEndian.Uint16(bytes[idx:])
|
||||
n += 2
|
||||
idx += n
|
||||
|
||||
return idx, obj
|
||||
}
|
||||
|
||||
if ttype == BoostSerializeTypeUint32 {
|
||||
obj := binary.LittleEndian.Uint32(bytes[idx:])
|
||||
n += 4
|
||||
idx += n
|
||||
|
||||
return idx, obj
|
||||
}
|
||||
|
||||
if ttype == BoostSerializeTypeUint64 {
|
||||
obj := binary.LittleEndian.Uint64(bytes[idx:])
|
||||
n += 8
|
||||
idx += n
|
||||
|
||||
return idx, obj
|
||||
}
|
||||
|
||||
if ttype == BoostSerializeTypeInt64 {
|
||||
obj := binary.LittleEndian.Uint64(bytes[idx:])
|
||||
n += 8
|
||||
idx += n
|
||||
|
||||
return idx, int64(obj)
|
||||
}
|
||||
|
||||
if ttype == BoostSerializeTypeString {
|
||||
n, obj := ReadString(bytes[idx:])
|
||||
idx += n
|
||||
|
||||
return idx, obj
|
||||
}
|
||||
|
||||
if ttype == BoostSerializeTypeBool {
|
||||
obj := bytes[idx] > 0
|
||||
n += 1
|
||||
idx += n
|
||||
|
||||
return idx, obj
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("unknown ttype %x", ttype))
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
// reads var int, returning number of bytes read and the integer in that byte
|
||||
// sequence.
|
||||
func ReadVarInt(b []byte) (int, int) {
|
||||
sizeMask := b[0] & PortableRawSizeMarkMask
|
||||
|
||||
switch uint32(sizeMask) {
|
||||
case uint32(PortableRawSizeMarkByte):
|
||||
return 1, int(b[0] >> 2)
|
||||
case uint32(PortableRawSizeMarkWord):
|
||||
return 2, int((binary.LittleEndian.Uint16(b[0:2])) >> 2)
|
||||
case PortableRawSizeMarkDword:
|
||||
return 4, int((binary.LittleEndian.Uint32(b[0:4])) >> 2)
|
||||
case uint32(PortableRawSizeMarkInt64):
|
||||
panic("int64 not supported") // TODO
|
||||
// return int((binary.LittleEndian.Uint64(b[0:8])) >> 2)
|
||||
// '-> bad
|
||||
default:
|
||||
panic(fmt.Errorf("malformed sizemask: %+v", sizeMask))
|
||||
}
|
||||
|
||||
return -1, -1
|
||||
}
|
||||
|
||||
func (s *PortableStorage) Bytes() []byte {
|
||||
var (
|
||||
body = make([]byte, 9) // fit _at least_ signatures + format ver
|
||||
b = make([]byte, 8) // biggest type
|
||||
|
||||
idx = 0
|
||||
size = 0
|
||||
)
|
||||
|
||||
{ // signature a
|
||||
size = 4
|
||||
|
||||
binary.LittleEndian.PutUint32(b, PortableStorageSignatureA)
|
||||
copy(body[idx:], b[:size])
|
||||
idx += size
|
||||
}
|
||||
|
||||
{ // signature b
|
||||
size = 4
|
||||
|
||||
binary.LittleEndian.PutUint32(b, PortableStorageSignatureB)
|
||||
copy(body[idx:], b[:size])
|
||||
idx += size
|
||||
}
|
||||
|
||||
{ // format ver
|
||||
size = 1
|
||||
|
||||
b[0] = PortableStorageFormatVersion
|
||||
copy(body[idx:], b[:size])
|
||||
idx += size
|
||||
}
|
||||
|
||||
// // write_var_in
|
||||
varInB, err := VarIn(len(s.Entries))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("varin '%d': %w", len(s.Entries), err))
|
||||
}
|
||||
|
||||
body = append(body, varInB...)
|
||||
for _, entry := range s.Entries {
|
||||
body = append(body, byte(len(entry.Name))) // section name length
|
||||
body = append(body, []byte(entry.Name)...) // section name
|
||||
body = append(body, entry.Serializable.Bytes()...)
|
||||
}
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
type Serializable interface {
|
||||
Bytes() []byte
|
||||
}
|
||||
|
||||
type Section struct {
|
||||
Entries []Entry
|
||||
}
|
||||
|
||||
func (s Section) Bytes() []byte {
|
||||
body := []byte{
|
||||
BoostSerializeTypeObject,
|
||||
}
|
||||
|
||||
varInB, err := VarIn(len(s.Entries))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("varin '%d': %w", len(s.Entries), err))
|
||||
}
|
||||
|
||||
body = append(body, varInB...)
|
||||
for _, entry := range s.Entries {
|
||||
body = append(body, byte(len(entry.Name))) // section name length
|
||||
body = append(body, []byte(entry.Name)...) // section name
|
||||
body = append(body, entry.Serializable.Bytes()...)
|
||||
}
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
func VarIn(i int) ([]byte, error) {
|
||||
if i <= 63 {
|
||||
return []byte{
|
||||
(byte(i) << 2) | PortableRawSizeMarkByte,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if i <= 16383 {
|
||||
b := []byte{0x00, 0x00}
|
||||
binary.LittleEndian.PutUint16(b,
|
||||
(uint16(i)<<2)|PortableRawSizeMarkWord,
|
||||
)
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
if i <= 1073741823 {
|
||||
b := []byte{0x00, 0x00, 0x00, 0x00}
|
||||
binary.LittleEndian.PutUint32(b,
|
||||
(uint32(i)<<2)|PortableRawSizeMarkDword,
|
||||
)
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("int %d too big", i)
|
||||
}
|
225
monero/client/levin/portable_storage_test.go
Normal file
225
monero/client/levin/portable_storage_test.go
Normal file
|
@ -0,0 +1,225 @@
|
|||
package levin_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sclevine/spec"
|
||||
"github.com/sclevine/spec/report"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"git.gammaspectra.live/P2Pool/consensus/v3/monero/client/levin"
|
||||
)
|
||||
|
||||
func TestPortableStorage(t *testing.T) {
|
||||
spec.Run(t, "NewPortableStorageFromBytes", func(t *testing.T, when spec.G, it spec.S) {
|
||||
it("fails w/ wrong sigA", func() {
|
||||
bytes := []byte{
|
||||
0xaa, 0xaa, 0xaa, 0xaa,
|
||||
}
|
||||
|
||||
_, err := levin.NewPortableStorageFromBytes(bytes)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "sig-a doesn't match")
|
||||
})
|
||||
|
||||
it("fails w/ wrong sigB", func() {
|
||||
bytes := []byte{
|
||||
0x01, 0x11, 0x01, 0x01,
|
||||
0xaa, 0xaa, 0xaa, 0xaa,
|
||||
}
|
||||
|
||||
_, err := levin.NewPortableStorageFromBytes(bytes)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "sig-b doesn't match")
|
||||
})
|
||||
|
||||
it("fails w/ wrong format ver", func() {
|
||||
bytes := []byte{
|
||||
0x01, 0x11, 0x01, 0x01,
|
||||
0x01, 0x01, 0x02, 0x01,
|
||||
0xaa,
|
||||
}
|
||||
|
||||
_, err := levin.NewPortableStorageFromBytes(bytes)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "version doesn't match")
|
||||
})
|
||||
|
||||
it("reads the contents", func() {
|
||||
bytes := []byte{
|
||||
0x01, 0x11, 0x01, 0x01, // sig a
|
||||
0x01, 0x01, 0x02, 0x01, // sig b
|
||||
0x01, // format ver
|
||||
|
||||
0x08, // var_in(len(entries))
|
||||
|
||||
// node_data
|
||||
0x09, // len("node_data")
|
||||
0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, // "node_data"
|
||||
0x0c, // boost_serialized_obj
|
||||
0x04, // var_in(node_data.entries)
|
||||
|
||||
// for i in range node_data
|
||||
0x03, // len("foo")
|
||||
0x66, 0x6f, 0x6f, // "foo"
|
||||
0x0a, // boost_serialized_string
|
||||
0xc, // var_in(len("bar"))
|
||||
0x62, 0x61, 0x72, // "bar"
|
||||
|
||||
// payload_data
|
||||
0x0c, // len("payload_data")
|
||||
0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, // "payload_data"
|
||||
0x0c, // boost_serialized_obj
|
||||
0x04, // var_in(payload_data.entries)
|
||||
|
||||
// for i in range payload_data.entries
|
||||
0x06, // len("number")
|
||||
0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, // "number"
|
||||
0x06, // boost_serialized_uint32
|
||||
0x01, 0x00, 0x00, 0x00, // uint32(1)
|
||||
}
|
||||
|
||||
ps, err := levin.NewPortableStorageFromBytes(bytes)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, ps.Entries, 2)
|
||||
assert.Equal(t, ps.Entries[0].Name, "node_data")
|
||||
assert.EqualValues(t, ps.Entries[0].Value, levin.Entries{
|
||||
{
|
||||
Name: "foo",
|
||||
Value: "bar",
|
||||
},
|
||||
})
|
||||
|
||||
assert.Equal(t, ps.Entries[1].Name, "payload_data")
|
||||
assert.EqualValues(t, ps.Entries[1].Value, levin.Entries{
|
||||
{
|
||||
Name: "number",
|
||||
Value: uint32(1),
|
||||
},
|
||||
})
|
||||
})
|
||||
}, spec.Report(report.Log{}), spec.Parallel(), spec.Random())
|
||||
|
||||
spec.Run(t, "ReadVarIn", func(t *testing.T, when spec.G, it spec.S) {
|
||||
it("i <= 63", func() {
|
||||
b := []byte{0x08}
|
||||
n, v := levin.ReadVarInt(b)
|
||||
|
||||
assert.Equal(t, n, 1)
|
||||
assert.Equal(t, v, 2)
|
||||
})
|
||||
|
||||
it("64 <= i <= 16383", func() {
|
||||
b := []byte{0x01, 0x02}
|
||||
n, v := levin.ReadVarInt(b)
|
||||
assert.Equal(t, n, 2)
|
||||
assert.Equal(t, v, 128)
|
||||
})
|
||||
|
||||
it("16384 <= i <= 1073741823", func() {
|
||||
b := []byte{0x02, 0x00, 0x01, 0x00}
|
||||
n, v := levin.ReadVarInt(b)
|
||||
assert.Equal(t, n, 4)
|
||||
assert.Equal(t, v, 16384)
|
||||
})
|
||||
}, spec.Report(report.Log{}), spec.Parallel(), spec.Random())
|
||||
|
||||
spec.Run(t, "VarrIn", func(t *testing.T, when spec.G, it spec.S) {
|
||||
it("i <= 63", func() {
|
||||
i := 2 // 0b00000010
|
||||
|
||||
b, err := levin.VarIn(i)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, b, []byte{
|
||||
0x08, // 0b00001000 (shift left twice, union 0)
|
||||
})
|
||||
})
|
||||
|
||||
it("64 <= i <= 16383", func() {
|
||||
i := 128 // 0b010000000
|
||||
|
||||
b, err := levin.VarIn(i)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, b, []byte{
|
||||
0x01, 0x02, // 0b1000000001 ((128 * 2 * 2) | 1) == 513
|
||||
// ' '
|
||||
// 1 2 * 256
|
||||
})
|
||||
})
|
||||
|
||||
it("16384 <= i <= 1073741823", func() {
|
||||
i := 16384 // 1 << 14
|
||||
|
||||
b, err := levin.VarIn(i)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, b, []byte{
|
||||
0x02, 0x00, 0x01, 0x00, // (1 << 16) | 2
|
||||
})
|
||||
})
|
||||
}, spec.Report(report.Log{}), spec.Parallel(), spec.Random())
|
||||
|
||||
spec.Run(t, "PortableStorage", func(t *testing.T, when spec.G, it spec.S) {
|
||||
it("bytes", func() {
|
||||
ps := &levin.PortableStorage{
|
||||
Entries: []levin.Entry{
|
||||
{
|
||||
Name: "node_data",
|
||||
Serializable: &levin.Section{
|
||||
Entries: []levin.Entry{
|
||||
{
|
||||
Name: "foo",
|
||||
Serializable: levin.BoostString("bar"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "payload_data",
|
||||
Serializable: &levin.Section{
|
||||
Entries: []levin.Entry{
|
||||
{
|
||||
Name: "number",
|
||||
Serializable: levin.BoostUint32(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, []byte{
|
||||
0x01, 0x11, 0x01, 0x01, // sig a
|
||||
0x01, 0x01, 0x02, 0x01, // sig b
|
||||
0x01, // format ver
|
||||
0x08, // var_in(len(entries))
|
||||
|
||||
// node_data
|
||||
0x09, // len("node_data")
|
||||
0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, // "node_data"
|
||||
0x0c, // boost_serialized_obj
|
||||
0x04, // var_in(node_data.entries)
|
||||
|
||||
// for i in range node_data
|
||||
0x03, // len("foo")
|
||||
0x66, 0x6f, 0x6f, // "foo"
|
||||
0x0a, // boost_serialized_string
|
||||
0xc, // var_in(len("bar"))
|
||||
0x62, 0x61, 0x72, // "bar"
|
||||
|
||||
// payload_data
|
||||
0x0c, // len("payload_data")
|
||||
0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, // "payload_data"
|
||||
0x0c, // boost_serialized_obj
|
||||
0x04, // var_in(payload_data.entries)
|
||||
|
||||
// for i in range payload_data.entries
|
||||
0x06, // len("number")
|
||||
0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, // "number"
|
||||
0x06, // boost_serialized_uint32
|
||||
0x01, 0x00, 0x00, 0x00, // uint32(1)
|
||||
|
||||
}, ps.Bytes())
|
||||
})
|
||||
}, spec.Report(report.Log{}), spec.Parallel(), spec.Random())
|
||||
}
|
201
monero/client/rpc/LICENSE
Normal file
201
monero/client/rpc/LICENSE
Normal file
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"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.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"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.
|
||||
|
||||
"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).
|
||||
|
||||
"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.
|
||||
|
||||
"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."
|
||||
|
||||
"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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(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
|
||||
|
||||
(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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your 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.
|
||||
|
||||
Copyright 2021 Ciro S. Costa
|
||||
|
||||
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.
|
3
monero/client/rpc/README.md
Normal file
3
monero/client/rpc/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# go-monero RPC
|
||||
|
||||
Taken from [git.gammaspectra.live/P2Pool/go-monero](https://git.gammaspectra.live/P2Pool/go-monero/src/commit/910450c4a523a8a21c0bcabf7d303418e6c76d50/pkg/rpc)
|
223
monero/client/rpc/client.go
Normal file
223
monero/client/rpc/client.go
Normal file
|
@ -0,0 +1,223 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const (
|
||||
// endpointJSONRPC is the common endpoint used for all the RPC calls
|
||||
// that make use of epee's JSONRPC invocation format for requests and
|
||||
// responses.
|
||||
//
|
||||
endpointJSONRPC = "/json_rpc"
|
||||
|
||||
// versionJSONRPC is the version of the JSONRPC format.
|
||||
//
|
||||
versionJSONRPC = "2.0"
|
||||
)
|
||||
|
||||
// Client is a wrapper over a plain HTTP client providing methods that
|
||||
// correspond to all RPC invocations to a `monerod` daemon, including
|
||||
// restricted and non-restricted ones.
|
||||
type Client struct {
|
||||
// http is the underlying http client that takes care of sending
|
||||
// requests and receiving the responses.
|
||||
//
|
||||
// To provide your own, make use of `WithHTTPClient` when instantiating
|
||||
// the client via the `NewClient` constructor.
|
||||
//
|
||||
http *http.Client
|
||||
|
||||
// address is the address of the monerod instance serving the RPC
|
||||
// endpoints.
|
||||
//
|
||||
address *url.URL
|
||||
}
|
||||
|
||||
// clientOptions is a set of options that can be overridden to tweak the
|
||||
// client's behavior.
|
||||
type clientOptions struct {
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// ClientOption defines a functional option for overriding optional client
|
||||
// configuration parameters.
|
||||
type ClientOption func(o *clientOptions)
|
||||
|
||||
// WithHTTPClient is a functional option for providing a custom HTTP client to
|
||||
// be used for the HTTP requests made to a monero daemon.
|
||||
func WithHTTPClient(v *http.Client) func(o *clientOptions) {
|
||||
return func(o *clientOptions) {
|
||||
o.HTTPClient = v
|
||||
}
|
||||
}
|
||||
|
||||
// NewClient instantiates a new Client that is able to communicate with
|
||||
// monerod's RPC endpoints.
|
||||
//
|
||||
// The `address` might be either restricted (typically <ip>:18089) or not
|
||||
// (typically <ip>:18081).
|
||||
func NewClient(address string, opts ...ClientOption) (*Client, error) {
|
||||
options := &clientOptions{}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
if options.HTTPClient == nil {
|
||||
options.HTTPClient = http.DefaultClient
|
||||
}
|
||||
|
||||
parsedAddress, err := url.Parse(address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("url parse: %w", err)
|
||||
}
|
||||
|
||||
return &Client{
|
||||
address: parsedAddress,
|
||||
http: options.HTTPClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ResponseEnvelope wraps all responses from the RPC server.
|
||||
type ResponseEnvelope struct {
|
||||
ID string `json:"id"`
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
Error struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
} `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// RequestEnvelope wraps all requests made to the RPC server.
|
||||
type RequestEnvelope struct {
|
||||
ID string `json:"id"`
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
Method string `json:"method"`
|
||||
Params interface{} `json:"params,omitempty"`
|
||||
}
|
||||
|
||||
// RawBinaryRequest makes requests to any endpoints, not assuming any particular format.
|
||||
func (c *Client) RawBinaryRequest(ctx context.Context, endpoint string, body io.Reader) (io.ReadCloser, error) {
|
||||
address := *c.address
|
||||
address.Path = endpoint
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", address.String(), body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new req '%s': %w", address.String(), err)
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/octet-stream")
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("do: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
defer resp.Body.Close()
|
||||
return nil, fmt.Errorf("non-2xx status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
// RawRequest makes requests to any endpoints, not assuming any particular format except of response is JSON.
|
||||
func (c *Client) RawRequest(ctx context.Context, endpoint string, params interface{}, response interface{}) error {
|
||||
address := *c.address
|
||||
address.Path = endpoint
|
||||
|
||||
var body io.Reader
|
||||
|
||||
if params != nil {
|
||||
b, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal: %w", err)
|
||||
}
|
||||
|
||||
body = bytes.NewReader(b)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", address.String(), body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("new req '%s': %w", address.String(), err)
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
if err := c.submitRequest(req, response); err != nil {
|
||||
return fmt.Errorf("submit request: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// JSONRPC issues a request for a particular method under the JSONRPC endpoint
|
||||
// with the proper envolope for its requests and unwrapping of results for
|
||||
// responses.
|
||||
func (c *Client) JSONRPC(ctx context.Context, method string, params interface{}, response interface{}) error {
|
||||
address := *c.address
|
||||
address.Path = endpointJSONRPC
|
||||
|
||||
b, err := json.Marshal(&RequestEnvelope{
|
||||
ID: "0",
|
||||
JSONRPC: versionJSONRPC,
|
||||
Method: method,
|
||||
Params: params,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", address.String(), bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return fmt.Errorf("new req '%s': %w", address.String(), err)
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
rpcResponseBody := &ResponseEnvelope{
|
||||
Result: response,
|
||||
}
|
||||
|
||||
if err := c.submitRequest(req, rpcResponseBody); err != nil {
|
||||
return fmt.Errorf("submit request: %w", err)
|
||||
}
|
||||
|
||||
if rpcResponseBody.Error.Code != 0 || rpcResponseBody.Error.Message != "" {
|
||||
return fmt.Errorf("rpc error: code=%d message=%s",
|
||||
rpcResponseBody.Error.Code,
|
||||
rpcResponseBody.Error.Message,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// submitRequest performs any generic HTTP request to the monero node targeted
|
||||
// by this client making no assumptions about a particular endpoint.
|
||||
func (c *Client) submitRequest(req *http.Request, response interface{}) error {
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("do: %w", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
return fmt.Errorf("non-2xx status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(response); err != nil {
|
||||
return fmt.Errorf("decode: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
3
monero/client/rpc/client_export_test.go
Normal file
3
monero/client/rpc/client_export_test.go
Normal file
|
@ -0,0 +1,3 @@
|
|||
package rpc
|
||||
|
||||
var EndpointJSONRPC = endpointJSONRPC
|
159
monero/client/rpc/client_test.go
Normal file
159
monero/client/rpc/client_test.go
Normal file
|
@ -0,0 +1,159 @@
|
|||
package rpc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/sclevine/spec"
|
||||
"github.com/sclevine/spec/report"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"git.gammaspectra.live/P2Pool/consensus/v3/monero/client/rpc"
|
||||
)
|
||||
|
||||
// nolint:funlen
|
||||
func TestClient(t *testing.T) {
|
||||
spec.Run(t, "JSONRPC", func(t *testing.T, when spec.G, it spec.S) {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
client *rpc.Client
|
||||
err error
|
||||
)
|
||||
|
||||
it("errors when daemon down", func() {
|
||||
daemon := httptest.NewServer(http.HandlerFunc(nil))
|
||||
daemon.Close()
|
||||
|
||||
client, err = rpc.NewClient(daemon.URL, rpc.WithHTTPClient(daemon.Client()))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.JSONRPC(ctx, "method", nil, nil)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "do:")
|
||||
})
|
||||
|
||||
it("errors w/ empty response", func() {
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {}
|
||||
|
||||
daemon := httptest.NewServer(http.HandlerFunc(handler))
|
||||
defer daemon.Close()
|
||||
|
||||
client, err = rpc.NewClient(daemon.URL, rpc.WithHTTPClient(daemon.Client()))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.JSONRPC(ctx, "method", nil, nil)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "decode")
|
||||
})
|
||||
|
||||
it("errors w/ non-200 response", func() {
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(500)
|
||||
}
|
||||
|
||||
daemon := httptest.NewServer(http.HandlerFunc(handler))
|
||||
defer daemon.Close()
|
||||
|
||||
client, err = rpc.NewClient(daemon.URL, rpc.WithHTTPClient(daemon.Client()))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.JSONRPC(ctx, "method", nil, nil)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "non-2xx status")
|
||||
})
|
||||
|
||||
it("makes GET request to the jsonrpc endpoint", func() {
|
||||
var (
|
||||
endpoint string
|
||||
method string
|
||||
)
|
||||
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
endpoint = r.URL.Path
|
||||
method = r.Method
|
||||
}
|
||||
|
||||
daemon := httptest.NewServer(http.HandlerFunc(handler))
|
||||
defer daemon.Close()
|
||||
|
||||
client, err = rpc.NewClient(daemon.URL, rpc.WithHTTPClient(daemon.Client()))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.JSONRPC(ctx, "method", nil, nil)
|
||||
assert.Equal(t, rpc.EndpointJSONRPC, endpoint)
|
||||
assert.Equal(t, method, "GET")
|
||||
})
|
||||
|
||||
it("encodes rpc in request", func() {
|
||||
var (
|
||||
body = &rpc.RequestEnvelope{}
|
||||
|
||||
params = map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"caz": 123.123,
|
||||
}
|
||||
)
|
||||
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
err := json.NewDecoder(r.Body).Decode(body)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
daemon := httptest.NewServer(http.HandlerFunc(handler))
|
||||
defer daemon.Close()
|
||||
|
||||
client, err = rpc.NewClient(daemon.URL, rpc.WithHTTPClient(daemon.Client()))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.JSONRPC(ctx, "rpc-method", params, nil)
|
||||
assert.Equal(t, body.ID, "0")
|
||||
assert.Equal(t, body.JSONRPC, "2.0")
|
||||
assert.Equal(t, body.Method, "rpc-method")
|
||||
assert.Equal(t, body.Params, params)
|
||||
})
|
||||
|
||||
it("captures result", func() {
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, `{"id":"id", "jsonrpc":"jsonrpc", "result": {"foo": "bar"}}`)
|
||||
}
|
||||
|
||||
daemon := httptest.NewServer(http.HandlerFunc(handler))
|
||||
defer daemon.Close()
|
||||
|
||||
client, err = rpc.NewClient(daemon.URL, rpc.WithHTTPClient(daemon.Client()))
|
||||
require.NoError(t, err)
|
||||
|
||||
result := map[string]string{}
|
||||
|
||||
err = client.JSONRPC(ctx, "rpc-method", nil, &result)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, result, map[string]string{"foo": "bar"})
|
||||
})
|
||||
|
||||
it("fails if rpc errored", func() {
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, `{"id":"id", "jsonrpc":"jsonrpc", "error": {"code": -1, "message":"foo"}}`)
|
||||
}
|
||||
|
||||
daemon := httptest.NewServer(http.HandlerFunc(handler))
|
||||
defer daemon.Close()
|
||||
|
||||
client, err = rpc.NewClient(daemon.URL, rpc.WithHTTPClient(daemon.Client()))
|
||||
require.NoError(t, err)
|
||||
|
||||
result := map[string]string{}
|
||||
|
||||
err = client.JSONRPC(ctx, "rpc-method", nil, &result)
|
||||
assert.Error(t, err)
|
||||
|
||||
assert.Contains(t, err.Error(), "foo")
|
||||
assert.Contains(t, err.Error(), "-1")
|
||||
})
|
||||
}, spec.Report(report.Terminal{}), spec.Parallel(), spec.Random())
|
||||
}
|
69
monero/client/rpc/daemon/binary_endpoints.go
Normal file
69
monero/client/rpc/daemon/binary_endpoints.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package daemon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"git.gammaspectra.live/P2Pool/consensus/v3/monero/client/levin"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
endpointGetOIndexes = "/get_o_indexes.bin"
|
||||
)
|
||||
|
||||
func (c *Client) GetOIndexes(
|
||||
ctx context.Context, txid string,
|
||||
) (indexes []uint64, finalError error) {
|
||||
|
||||
binaryTxId, err := hex.DecodeString(txid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storage := levin.PortableStorage{Entries: levin.Entries{
|
||||
levin.Entry{
|
||||
Name: "txid",
|
||||
Serializable: levin.BoostString(binaryTxId),
|
||||
},
|
||||
}}
|
||||
|
||||
data := storage.Bytes()
|
||||
|
||||
resp, err := c.RawBinaryRequest(ctx, endpointGetOIndexes, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Close()
|
||||
|
||||
if buf, err := io.ReadAll(resp); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
indexes = nil
|
||||
finalError = errors.New("error decoding")
|
||||
}
|
||||
}()
|
||||
responseStorage, err := levin.NewPortableStorageFromBytes(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, e := range responseStorage.Entries {
|
||||
if e.Name == "o_indexes" {
|
||||
if entries, ok := e.Value.(levin.Entries); ok {
|
||||
indexes = make([]uint64, 0, len(entries))
|
||||
for _, e2 := range entries {
|
||||
if v, ok := e2.Value.(uint64); ok {
|
||||
indexes = append(indexes, v)
|
||||
}
|
||||
}
|
||||
return indexes, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("could not get outputs")
|
||||
}
|
51
monero/client/rpc/daemon/client.go
Normal file
51
monero/client/rpc/daemon/client.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Requester is responsible for making concrete request to Monero's endpoints,
|
||||
// i.e., either `jsonrpc` methods or those "raw" endpoints.
|
||||
type Requester interface {
|
||||
// JSONRPC is used for callind methods under `/json_rpc` that follow
|
||||
// monero's `v2` response and error encapsulation.
|
||||
//
|
||||
JSONRPC(
|
||||
ctx context.Context, method string, params, result interface{},
|
||||
) error
|
||||
|
||||
// RawRequest is used for making a request to an arbitrary endpoint
|
||||
// `endpoint` whose response (in JSON format) should be unmarshalled to
|
||||
// `response`.
|
||||
//
|
||||
RawRequest(
|
||||
ctx context.Context,
|
||||
endpoint string,
|
||||
params interface{},
|
||||
response interface{},
|
||||
) error
|
||||
|
||||
// RawBinaryRequest is used for making a request to an arbitrary endpoint
|
||||
// `endpoint` whose response will be returned (which MUST be closed in error = nil)
|
||||
//
|
||||
RawBinaryRequest(
|
||||
ctx context.Context,
|
||||
endpoint string,
|
||||
body io.Reader,
|
||||
) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
// Client provides access to the daemon's JSONRPC methods and regular
|
||||
// endpoints.
|
||||
type Client struct {
|
||||
Requester
|
||||
}
|
||||
|
||||
// NewClient instantiates a new client for interacting with monero's daemon
|
||||
// api.
|
||||
func NewClient(c Requester) *Client {
|
||||
return &Client{
|
||||
Requester: c,
|
||||
}
|
||||
}
|
32
monero/client/rpc/daemon/daemon_example_test.go
Normal file
32
monero/client/rpc/daemon/daemon_example_test.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package daemon_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.gammaspectra.live/P2Pool/consensus/v3/monero/client/rpc"
|
||||
"git.gammaspectra.live/P2Pool/consensus/v3/monero/client/rpc/daemon"
|
||||
)
|
||||
|
||||
// nolint
|
||||
func ExampleGetHeight() {
|
||||
ctx := context.Background()
|
||||
addr := "http://localhost:18081"
|
||||
|
||||
// instantiate a generic RPC client
|
||||
//
|
||||
client, err := rpc.NewClient(addr)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("new client for '%s': %w", addr, err))
|
||||
}
|
||||
|
||||
// instantiate a daemon-specific client and call the `get_height`
|
||||
// remote procedure.
|
||||
//
|
||||
height, err := daemon.NewClient(client).GetHeight(ctx)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("get height: %w", err))
|
||||
}
|
||||
|
||||
fmt.Printf("height=%d hash=%s\n", height.Height, height.Hash)
|
||||
}
|
4
monero/client/rpc/daemon/doc.go
Normal file
4
monero/client/rpc/daemon/doc.go
Normal file
|
@ -0,0 +1,4 @@
|
|||
// Package daemon provides a client that encapsulates RPC methods and endpoints
|
||||
// that can be hit in a daemon.
|
||||
//
|
||||
package daemon
|
432
monero/client/rpc/daemon/jsonrpc.go
Normal file
432
monero/client/rpc/daemon/jsonrpc.go
Normal file
|
@ -0,0 +1,432 @@
|
|||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
methodGenerateBlocks = "generateblocks"
|
||||
methodGetAlternateChains = "get_alternate_chains"
|
||||
methodGetBans = "get_bans"
|
||||
methodGetBlock = "get_block"
|
||||
methodGetBlockCount = "get_block_count"
|
||||
methodGetBlockHeadersRange = "get_block_headers_range"
|
||||
methodGetBlockHeaderByHash = "get_block_header_by_hash"
|
||||
methodGetBlockHeaderByHeight = "get_block_header_by_height"
|
||||
methodGetBlockTemplate = "get_block_template"
|
||||
methodGetMinerData = "get_miner_data"
|
||||
methodGetCoinbaseTxSum = "get_coinbase_tx_sum"
|
||||
methodGetConnections = "get_connections"
|
||||
methodGetFeeEstimate = "get_fee_estimate"
|
||||
methodGetInfo = "get_info"
|
||||
methodGetLastBlockHeader = "get_last_block_header"
|
||||
methodGetVersion = "get_version"
|
||||
methodHardForkInfo = "hard_fork_info"
|
||||
methodOnGetBlockHash = "on_get_block_hash"
|
||||
methodRPCAccessTracking = "rpc_access_tracking"
|
||||
methodRelayTx = "relay_tx"
|
||||
methodSetBans = "set_bans"
|
||||
methodSyncInfo = "sync_info"
|
||||
methodSubmitBlock = "submit_block"
|
||||
)
|
||||
|
||||
// GetAlternateChains displays alternative chains seen by the node.
|
||||
//
|
||||
// (restricted).
|
||||
func (c *Client) GetAlternateChains(
|
||||
ctx context.Context,
|
||||
) (*GetAlternateChainsResult, error) {
|
||||
resp := &GetAlternateChainsResult{}
|
||||
|
||||
err := c.JSONRPC(ctx, methodGetAlternateChains, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// RPCAccessTracking retrieves statistics that the monero daemon keeps track of
|
||||
// about the use of each RPC method and endpoint.
|
||||
//
|
||||
// (restricted).
|
||||
func (c *Client) RPCAccessTracking(
|
||||
ctx context.Context,
|
||||
) (*RPCAccessTrackingResult, error) {
|
||||
resp := &RPCAccessTrackingResult{}
|
||||
|
||||
err := c.JSONRPC(ctx, methodRPCAccessTracking, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// HardForkInfo looks up informaiton about the last hard fork.
|
||||
func (c *Client) HardForkInfo(
|
||||
ctx context.Context,
|
||||
) (*HardForkInfoResult, error) {
|
||||
resp := &HardForkInfoResult{}
|
||||
|
||||
err := c.JSONRPC(ctx, methodHardForkInfo, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetBans retrieves the list of banned IPs.
|
||||
//
|
||||
// (restricted).
|
||||
func (c *Client) GetBans(ctx context.Context) (*GetBansResult, error) {
|
||||
resp := &GetBansResult{}
|
||||
|
||||
err := c.JSONRPC(ctx, methodGetBans, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type SetBansBan struct {
|
||||
Host string `json:"host"`
|
||||
Ban bool `json:"ban"`
|
||||
Seconds int64 `json:"seconds"`
|
||||
}
|
||||
|
||||
type SetBansRequestParameters struct {
|
||||
Bans []SetBansBan `json:"bans"`
|
||||
}
|
||||
|
||||
// SetBans bans a particular host.
|
||||
//
|
||||
// (restricted).
|
||||
func (c *Client) SetBans(
|
||||
ctx context.Context, params SetBansRequestParameters,
|
||||
) (*SetBansResult, error) {
|
||||
resp := &SetBansResult{}
|
||||
|
||||
err := c.JSONRPC(ctx, methodSetBans, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetVersion retrieves the version of monerod that the node uses.
|
||||
//
|
||||
// (restricted).
|
||||
func (c *Client) GetVersion(ctx context.Context) (*GetVersionResult, error) {
|
||||
resp := &GetVersionResult{}
|
||||
|
||||
err := c.JSONRPC(ctx, methodGetVersion, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GenerateBlocksRequestParameters is the set of parameters to be passed to the
|
||||
// GenerateBlocks RPC method.
|
||||
type GenerateBlocksRequestParameters struct {
|
||||
// AmountOfBlocks is the number of blocks to be generated.
|
||||
//
|
||||
AmountOfBlocks uint64 `json:"amount_of_blocks,omitempty"`
|
||||
|
||||
// WalletAddress is the address of the wallet that will get the rewards
|
||||
// of the coinbase transaction for such the blocks generates.
|
||||
//
|
||||
WalletAddress string `json:"wallet_address,omitempty"`
|
||||
|
||||
// PreviousBlock TODO
|
||||
//
|
||||
PreviousBlock string `json:"prev_block,omitempty"`
|
||||
|
||||
// StartingNonce TODO
|
||||
//
|
||||
StartingNonce uint32 `json:"starting_nonce,omitempty"`
|
||||
}
|
||||
|
||||
// GenerateBlocks combines functionality from `GetBlockTemplate` and
|
||||
// `SubmitBlock` RPC calls to allow rapid block creation.
|
||||
//
|
||||
// Difficulty is set permanently to 1 for regtest.
|
||||
//
|
||||
// (restricted).
|
||||
func (c *Client) GenerateBlocks(
|
||||
ctx context.Context, params GenerateBlocksRequestParameters,
|
||||
) (*GenerateBlocksResult, error) {
|
||||
resp := &GenerateBlocksResult{}
|
||||
|
||||
err := c.JSONRPC(ctx, methodGenerateBlocks, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetBlockCount(
|
||||
ctx context.Context,
|
||||
) (*GetBlockCountResult, error) {
|
||||
resp := &GetBlockCountResult{}
|
||||
|
||||
err := c.JSONRPC(ctx, methodGetBlockCount, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) OnGetBlockHash(
|
||||
ctx context.Context, height uint64,
|
||||
) (string, error) {
|
||||
resp := ""
|
||||
params := []uint64{height}
|
||||
|
||||
err := c.JSONRPC(ctx, methodOnGetBlockHash, params, &resp)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) RelayTx(
|
||||
ctx context.Context, txns []string,
|
||||
) (*RelayTxResult, error) {
|
||||
resp := &RelayTxResult{}
|
||||
params := map[string]interface{}{
|
||||
"txids": txns,
|
||||
}
|
||||
|
||||
err := c.JSONRPC(ctx, methodRelayTx, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetBlockTemplate gets a block template on which mining a new block.
|
||||
func (c *Client) GetBlockTemplate(
|
||||
ctx context.Context, walletAddress string, reserveSize uint,
|
||||
) (*GetBlockTemplateResult, error) {
|
||||
resp := &GetBlockTemplateResult{}
|
||||
params := map[string]interface{}{
|
||||
"wallet_address": walletAddress,
|
||||
"reserve_size": reserveSize,
|
||||
}
|
||||
|
||||
err := c.JSONRPC(ctx, methodGetBlockTemplate, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetMinerData(ctx context.Context) (*GetMinerDataResult, error) {
|
||||
resp := &GetMinerDataResult{}
|
||||
|
||||
err := c.JSONRPC(ctx, methodGetMinerData, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) SubmitBlock(ctx context.Context, blobs ...[]byte) (*SubmitBlockResult, error) {
|
||||
resp := &SubmitBlockResult{}
|
||||
|
||||
params := make([]string, 0, len(blobs))
|
||||
for _, blob := range blobs {
|
||||
params = append(params, hex.EncodeToString(blob))
|
||||
}
|
||||
|
||||
err := c.JSONRPC(ctx, methodSubmitBlock, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetConnections(
|
||||
ctx context.Context,
|
||||
) (*GetConnectionsResult, error) {
|
||||
resp := &GetConnectionsResult{}
|
||||
|
||||
err := c.JSONRPC(ctx, methodGetConnections, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetInfo retrieves general information about the state of the node and the
|
||||
// network.
|
||||
func (c *Client) GetInfo(ctx context.Context) (*GetInfoResult, error) {
|
||||
resp := &GetInfoResult{}
|
||||
|
||||
if err := c.JSONRPC(ctx, methodGetInfo, nil, resp); err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetLastBlockHeader(
|
||||
ctx context.Context,
|
||||
) (*GetLastBlockHeaderResult, error) {
|
||||
resp := &GetLastBlockHeaderResult{}
|
||||
|
||||
err := c.JSONRPC(ctx, methodGetLastBlockHeader, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetCoinbaseTxSum(
|
||||
ctx context.Context, height, count uint64,
|
||||
) (*GetCoinbaseTxSumResult, error) {
|
||||
resp := &GetCoinbaseTxSumResult{}
|
||||
params := map[string]uint64{
|
||||
"height": height,
|
||||
"count": count,
|
||||
}
|
||||
|
||||
err := c.JSONRPC(ctx, methodGetCoinbaseTxSum, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// InnerJSON parses the content of the JSON embedded in `GetBlockResult`.
|
||||
func (j *GetBlockResult) InnerJSON() (*GetBlockResultJSON, error) {
|
||||
res := &GetBlockResultJSON{}
|
||||
|
||||
err := json.Unmarshal([]byte(j.JSON), res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshal: %w", err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetBlockHeadersRange(
|
||||
ctx context.Context, start, end uint64,
|
||||
) (*GetBlockHeadersRangeResult, error) {
|
||||
resp := &GetBlockHeadersRangeResult{}
|
||||
params := map[string]interface{}{
|
||||
"start_height": start,
|
||||
"end_height": end,
|
||||
}
|
||||
|
||||
err := c.JSONRPC(ctx, methodGetBlockHeadersRange, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetBlockHeaderByHeight retrieves block header information for either one or
|
||||
// multiple blocks.
|
||||
func (c *Client) GetBlockHeaderByHeight(
|
||||
ctx context.Context, height uint64,
|
||||
) (*GetBlockHeaderByHeightResult, error) {
|
||||
resp := &GetBlockHeaderByHeightResult{}
|
||||
params := map[string]interface{}{
|
||||
"height": height,
|
||||
}
|
||||
|
||||
err := c.JSONRPC(ctx, methodGetBlockHeaderByHeight, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetBlockHeaderByHash retrieves block header information for either one or
|
||||
// multiple blocks.
|
||||
func (c *Client) GetBlockHeaderByHash(
|
||||
ctx context.Context, hashes []string,
|
||||
) (*GetBlockHeaderByHashResult, error) {
|
||||
resp := &GetBlockHeaderByHashResult{}
|
||||
params := map[string]interface{}{
|
||||
"hashes": hashes,
|
||||
}
|
||||
|
||||
err := c.JSONRPC(ctx, methodGetBlockHeaderByHash, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetBlockRequestParameters represents the set of possible parameters that can
|
||||
// be used for submitting a call to the `get_block` jsonrpc method.
|
||||
type GetBlockRequestParameters struct {
|
||||
Height uint64 `json:"height,omitempty"`
|
||||
Hash string `json:"hash,omitempty"`
|
||||
}
|
||||
|
||||
// GetBlock fetches full block information from a block at a particular hash OR
|
||||
// height.
|
||||
func (c *Client) GetBlock(
|
||||
ctx context.Context, params GetBlockRequestParameters,
|
||||
) (*GetBlockResult, error) {
|
||||
resp := &GetBlockResult{}
|
||||
|
||||
err := c.JSONRPC(ctx, methodGetBlock, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetFeeEstimate(
|
||||
ctx context.Context, graceBlocks uint64,
|
||||
) (*GetFeeEstimateResult, error) {
|
||||
resp := &GetFeeEstimateResult{}
|
||||
params := map[string]uint64{
|
||||
"grace_blocks": graceBlocks,
|
||||
}
|
||||
|
||||
err := c.JSONRPC(ctx, methodGetFeeEstimate, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) SyncInfo(ctx context.Context) (*SyncInfoResult, error) {
|
||||
resp := &SyncInfoResult{}
|
||||
|
||||
err := c.JSONRPC(ctx, methodSyncInfo, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
259
monero/client/rpc/daemon/raw_endpoints.go
Normal file
259
monero/client/rpc/daemon/raw_endpoints.go
Normal file
|
@ -0,0 +1,259 @@
|
|||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
endpointGetHeight = "/get_height"
|
||||
endpointGetLimit = "/get_limit"
|
||||
endpointGetNetStats = "/get_net_stats"
|
||||
endpointGetOuts = "/get_outs"
|
||||
endpointGetPeerList = "/get_peer_list"
|
||||
endpointGetPublicNodes = "/get_public_nodes"
|
||||
endpointGetTransactionPool = "/get_transaction_pool"
|
||||
endpointGetTransactionPoolStats = "/get_transaction_pool_stats"
|
||||
endpointGetTransactions = "/get_transactions"
|
||||
endpointMiningStatus = "/mining_status"
|
||||
endpointSetLimit = "/set_limit"
|
||||
endpointSetLogLevel = "/set_log_level"
|
||||
endpointSetLogCategories = "/set_log_categories"
|
||||
endpointStartMining = "/start_mining"
|
||||
endpointStopMining = "/stop_mining"
|
||||
)
|
||||
|
||||
func (c *Client) StopMining(
|
||||
ctx context.Context,
|
||||
) (*StopMiningResult, error) {
|
||||
resp := &StopMiningResult{}
|
||||
|
||||
err := c.RawRequest(ctx, endpointStopMining, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("raw request: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetLimit(ctx context.Context) (*GetLimitResult, error) {
|
||||
resp := &GetLimitResult{}
|
||||
|
||||
err := c.RawRequest(ctx, endpointGetLimit, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("raw request: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetLogCategories(
|
||||
ctx context.Context, params SetLogCategoriesRequestParameters,
|
||||
) (*SetLogCategoriesResult, error) {
|
||||
resp := &SetLogCategoriesResult{}
|
||||
|
||||
err := c.RawRequest(ctx, endpointSetLogCategories, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("raw request: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetLogLevel(
|
||||
ctx context.Context, params SetLogLevelRequestParameters,
|
||||
) (*SetLogLevelResult, error) {
|
||||
resp := &SetLogLevelResult{}
|
||||
|
||||
err := c.RawRequest(ctx, endpointSetLogLevel, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("raw request: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetLimit(
|
||||
ctx context.Context, params SetLimitRequestParameters,
|
||||
) (*SetLimitResult, error) {
|
||||
resp := &SetLimitResult{}
|
||||
|
||||
err := c.RawRequest(ctx, endpointSetLimit, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("raw request: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) StartMining(
|
||||
ctx context.Context, params StartMiningRequestParameters,
|
||||
) (*StartMiningResult, error) {
|
||||
resp := &StartMiningResult{}
|
||||
|
||||
err := c.RawRequest(ctx, endpointStartMining, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("raw request: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) MiningStatus(
|
||||
ctx context.Context,
|
||||
) (*MiningStatusResult, error) {
|
||||
resp := &MiningStatusResult{}
|
||||
|
||||
err := c.RawRequest(ctx, endpointMiningStatus, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("raw request: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetTransactionPool(
|
||||
ctx context.Context,
|
||||
) (*GetTransactionPoolResult, error) {
|
||||
resp := &GetTransactionPoolResult{}
|
||||
|
||||
err := c.RawRequest(ctx, endpointGetTransactionPool, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("raw request: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetTransactionPoolStats(
|
||||
ctx context.Context,
|
||||
) (*GetTransactionPoolStatsResult, error) {
|
||||
resp := &GetTransactionPoolStatsResult{}
|
||||
|
||||
err := c.RawRequest(ctx, endpointGetTransactionPoolStats, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("raw request: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetPeerList(
|
||||
ctx context.Context,
|
||||
) (*GetPeerListResult, error) {
|
||||
resp := &GetPeerListResult{}
|
||||
|
||||
err := c.RawRequest(ctx, endpointGetPeerList, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("raw request: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type GetPublicNodesRequestParameters struct {
|
||||
Gray bool `json:"gray"`
|
||||
White bool `json:"white"`
|
||||
IncludeBlocked bool `json:"include_blocked"`
|
||||
}
|
||||
|
||||
func (c *Client) GetPublicNodes(
|
||||
ctx context.Context, params GetPublicNodesRequestParameters,
|
||||
) (*GetPublicNodesResult, error) {
|
||||
resp := &GetPublicNodesResult{}
|
||||
|
||||
err := c.RawRequest(ctx, endpointGetPublicNodes, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("raw request: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetOuts(
|
||||
ctx context.Context, outputs []uint, gettxid bool,
|
||||
) (*GetOutsResult, error) {
|
||||
resp := &GetOutsResult{}
|
||||
|
||||
type output struct {
|
||||
Index uint `json:"index"`
|
||||
}
|
||||
|
||||
params := struct {
|
||||
Outputs []output `json:"outputs"`
|
||||
GetTxID bool `json:"get_txid,omitempty"`
|
||||
}{GetTxID: gettxid}
|
||||
|
||||
for _, out := range outputs {
|
||||
params.Outputs = append(params.Outputs, output{out})
|
||||
}
|
||||
|
||||
err := c.RawRequest(ctx, endpointGetOuts, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("raw request: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetHeight(ctx context.Context) (*GetHeightResult, error) {
|
||||
resp := &GetHeightResult{}
|
||||
|
||||
err := c.RawRequest(ctx, endpointGetHeight, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("raw request: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetNetStats(ctx context.Context) (*GetNetStatsResult, error) {
|
||||
resp := &GetNetStatsResult{}
|
||||
|
||||
err := c.RawRequest(ctx, endpointGetNetStats, nil, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("raw request: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (r *GetTransactionsResult) GetTransactions() ([]*TransactionJSON, error) {
|
||||
txns := make([]*TransactionJSON, len(r.Txs))
|
||||
|
||||
for idx, txn := range r.Txs {
|
||||
if len(txn.AsJSON) == 0 {
|
||||
return nil, fmt.Errorf("txn w/ empty `.as_json`: %s",
|
||||
txn.TxHash)
|
||||
}
|
||||
|
||||
t := &TransactionJSON{}
|
||||
err := json.Unmarshal([]byte(txn.AsJSON), t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshal txn '%s': %w",
|
||||
txn.TxHash, err)
|
||||
}
|
||||
|
||||
txns[idx] = t
|
||||
}
|
||||
|
||||
return txns, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetTransactions(
|
||||
ctx context.Context, txns []string,
|
||||
) (*GetTransactionsResult, error) {
|
||||
resp := &GetTransactionsResult{}
|
||||
params := map[string]interface{}{
|
||||
"txs_hashes": txns,
|
||||
"decode_as_json": true,
|
||||
}
|
||||
|
||||
err := c.RawRequest(ctx, endpointGetTransactions, params, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("raw request: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
941
monero/client/rpc/daemon/types.go
Normal file
941
monero/client/rpc/daemon/types.go
Normal file
|
@ -0,0 +1,941 @@
|
|||
package daemon
|
||||
|
||||
// RPCResultFooter contains the set of fields that every RPC result message
|
||||
// will contain.
|
||||
type RPCResultFooter struct {
|
||||
// Status dictates whether the request worked or not. "OK" means good.
|
||||
//
|
||||
Status string `json:"status"`
|
||||
|
||||
// States if the result is obtained using the bootstrap mode, and is
|
||||
// therefore not trusted (`true`), or when the daemon is fully synced
|
||||
// and thus handles the RPC locally (`false`).
|
||||
//
|
||||
Untrusted bool `json:"untrusted"`
|
||||
|
||||
// Credits indicates the number of credits available to the requesting
|
||||
// client, if payment for RPC is enabled, otherwise, 0.
|
||||
//
|
||||
Credits uint64 `json:"credits,omitempty"`
|
||||
|
||||
// TopHash is the hash of the highest block in the chain, If payment
|
||||
// for RPC is enabled, otherwise, empty.
|
||||
//
|
||||
TopHash string `json:"top_hash,omitempty"`
|
||||
}
|
||||
|
||||
// GetAlternateChainsResult is the result of a call to the GetAlternateChains
|
||||
// RPC method.
|
||||
type GetAlternateChainsResult struct {
|
||||
// Chains is the array of alternate chains seen by the node.
|
||||
//
|
||||
Chains []struct {
|
||||
// BlockHash is the hash of the first diverging block of this
|
||||
// alternative chain.
|
||||
//
|
||||
BlockHash string `json:"block_hash"`
|
||||
|
||||
// BlockHashes TODO
|
||||
//
|
||||
BlockHashes []string `json:"block_hashes"`
|
||||
|
||||
// Difficulty is the cumulative difficulty of all blocks in the
|
||||
// alternative chain.
|
||||
//
|
||||
Difficulty int64 `json:"difficulty"`
|
||||
|
||||
// DifficultyTop64 is the most-significant 64 bits of the
|
||||
// 128-bit network difficulty.
|
||||
//
|
||||
DifficultyTop64 int `json:"difficulty_top64"`
|
||||
|
||||
// Height is the block height of the first diverging block of
|
||||
// this alternative chain.
|
||||
//
|
||||
Height uint64 `json:"height"`
|
||||
|
||||
// Length is the length in blocks of this alternative chain,
|
||||
// after divergence.
|
||||
//
|
||||
Length uint64 `json:"length"`
|
||||
|
||||
// MainChainParentBlock TODO
|
||||
//
|
||||
MainChainParentBlock string `json:"main_chain_parent_block"`
|
||||
|
||||
// WideDifficulty is the network difficulty as a hexadecimal
|
||||
// string representing a 128-bit number.
|
||||
//
|
||||
WideDifficulty string `json:"wide_difficulty"`
|
||||
} `json:"chains"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// AccessTrackingResult is the result of a call to the RPCAccessTracking RPC
|
||||
// method.
|
||||
type RPCAccessTrackingResult struct {
|
||||
Data []struct {
|
||||
// Count is the number of times that the monero daemon received
|
||||
// a request for this RPC method.
|
||||
//
|
||||
Count uint64 `json:"count"`
|
||||
|
||||
// RPC is the name of the remote procedure call.
|
||||
//
|
||||
RPC string `json:"rpc"`
|
||||
|
||||
// Time indicates how much time the daemon spent serving this
|
||||
// procedure.
|
||||
//
|
||||
Time uint64 `json:"time"`
|
||||
|
||||
// Credits indicates the number of credits consumed for this
|
||||
// method.
|
||||
//
|
||||
Credits uint64 `json:"credits"`
|
||||
} `json:"data"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// HardForkInfoResult is the result of a call to the HardForkInfo RPC method.
|
||||
type HardForkInfoResult struct {
|
||||
// EarliestHeight is the earliest height at which <version> is allowed.
|
||||
//
|
||||
EarliestHeight int `json:"earliest_height"`
|
||||
|
||||
// Whether of not the hard fork is enforced.
|
||||
//
|
||||
Enabled bool `json:"enabled"`
|
||||
|
||||
// State indicates the current hard fork state:
|
||||
//
|
||||
// 0 - likely forked
|
||||
// 1 - update needed
|
||||
// 2 - ready
|
||||
//
|
||||
State int `json:"state"`
|
||||
|
||||
// The number of votes required to enable <version>.
|
||||
//
|
||||
Threshold int `json:"threshold"`
|
||||
|
||||
// Version (<version>) corresponds to the major block version for the
|
||||
// fork.
|
||||
//
|
||||
Version int `json:"version"`
|
||||
|
||||
// Votes is the number of votes to enable <version>
|
||||
//
|
||||
Votes int `json:"votes"`
|
||||
|
||||
// Voting indicates which version this node is voting for/using.
|
||||
//
|
||||
Voting int `json:"voting"`
|
||||
|
||||
// Window is the size of the voting window.
|
||||
//
|
||||
Window int `json:"window"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetVersionResult is the result of a call to the GetVersion RPC method.
|
||||
type GetVersionResult struct {
|
||||
Release bool `json:"release"`
|
||||
Version uint64 `json:"version"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetBansResult is the result of a call to the GetBans RPC method.
|
||||
type GetBansResult struct {
|
||||
// Bans contains the list of nodes banned by this node.
|
||||
//
|
||||
Bans []struct {
|
||||
// Host is the string representation of the node that is
|
||||
// banned.
|
||||
//
|
||||
Host string `json:"host"`
|
||||
|
||||
// IP is the integer representation of the host banned.
|
||||
//
|
||||
IP int `json:"ip"`
|
||||
|
||||
// Seconds represents how many seconds are left for the ban to
|
||||
// be lifted.
|
||||
//
|
||||
Seconds uint `json:"seconds"`
|
||||
} `json:"bans"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// SetBansResult is the result of a call to the SetBans RPC method.
|
||||
type SetBansResult struct {
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetFeeEstimateResult is the result of a call to the GetFeeEstimate RPC
|
||||
// method.
|
||||
type GetFeeEstimateResult struct {
|
||||
// Fee is the per kB fee estimate.
|
||||
//
|
||||
Fee int `json:"fee"`
|
||||
|
||||
// QuantizationMask indicates that the fee should be rounded up to an
|
||||
// even multiple of this value.
|
||||
//
|
||||
QuantizationMask int `json:"quantization_mask"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetInfoResult is the result of a call to the GetInfo RPC method.
|
||||
type GetInfoResult struct {
|
||||
AdjustedTime uint64 `json:"adjusted_time"`
|
||||
AltBlocksCount int `json:"alt_blocks_count"`
|
||||
BlockSizeLimit uint64 `json:"block_size_limit"`
|
||||
BlockSizeMedian uint64 `json:"block_size_median"`
|
||||
BlockWeightLimit uint64 `json:"block_weight_limit"`
|
||||
BlockWeightMedian uint64 `json:"block_weight_median"`
|
||||
BootstrapDaemonAddress string `json:"bootstrap_daemon_address"`
|
||||
BusySyncing bool `json:"busy_syncing"`
|
||||
CumulativeDifficulty int64 `json:"cumulative_difficulty"`
|
||||
CumulativeDifficultyTop64 uint64 `json:"cumulative_difficulty_top64"`
|
||||
DatabaseSize uint64 `json:"database_size"`
|
||||
Difficulty uint64 `json:"difficulty"`
|
||||
DifficultyTop64 uint64 `json:"difficulty_top64"`
|
||||
FreeSpace uint64 `json:"free_space"`
|
||||
GreyPeerlistSize uint `json:"grey_peerlist_size"`
|
||||
Height uint64 `json:"height"`
|
||||
HeightWithoutBootstrap uint64 `json:"height_without_bootstrap"`
|
||||
IncomingConnectionsCount uint `json:"incoming_connections_count"`
|
||||
Mainnet bool `json:"mainnet"`
|
||||
Nettype string `json:"nettype"`
|
||||
Offline bool `json:"offline"`
|
||||
OutgoingConnectionsCount uint `json:"outgoing_connections_count"`
|
||||
RPCConnectionsCount uint `json:"rpc_connections_count"`
|
||||
Stagenet bool `json:"stagenet"`
|
||||
StartTime uint64 `json:"start_time"`
|
||||
Synchronized bool `json:"synchronized"`
|
||||
Target uint64 `json:"target"`
|
||||
TargetHeight uint64 `json:"target_height"`
|
||||
Testnet bool `json:"testnet"`
|
||||
TopBlockHash string `json:"top_block_hash"`
|
||||
TxCount uint64 `json:"tx_count"`
|
||||
TxPoolSize uint64 `json:"tx_pool_size"`
|
||||
UpdateAvailable bool `json:"update_available"`
|
||||
Version string `json:"version"`
|
||||
WasBootstrapEverUsed bool `json:"was_bootstrap_ever_used"`
|
||||
WhitePeerlistSize uint `json:"white_peerlist_size"`
|
||||
WideCumulativeDifficulty string `json:"wide_cumulative_difficulty"`
|
||||
WideDifficulty string `json:"wide_difficulty"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetBlockTemplateResult is the result of a call to the GetBlockTemplate RPC
|
||||
// method.
|
||||
type GetBlockTemplateResult struct {
|
||||
// BlockhashingBlob is the blob on which to try to find a valid nonce.
|
||||
//
|
||||
BlockhashingBlob string `json:"blockhashing_blob"`
|
||||
|
||||
// BlocktemplateBlob is the blob on which to try to mine a new block.
|
||||
//
|
||||
BlocktemplateBlob string `json:"blocktemplate_blob"`
|
||||
|
||||
// Difficulty is the difficulty of the next block.
|
||||
Difficulty int64 `json:"difficulty"`
|
||||
|
||||
// ExpectedReward is the coinbase reward expected to be received if the
|
||||
// block is successfully mined.
|
||||
//
|
||||
ExpectedReward int64 `json:"expected_reward"`
|
||||
|
||||
// Height is the height on which to mine.
|
||||
//
|
||||
Height int `json:"height"`
|
||||
|
||||
// PrevHash is the hash of the most recent block on which to mine the
|
||||
// next block.
|
||||
//
|
||||
PrevHash string `json:"prev_hash"`
|
||||
|
||||
// ReservedOffset TODO
|
||||
//
|
||||
ReservedOffset int `json:"reserved_offset"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetMinerDataResult is the result of a call to the GetMinerData RPC
|
||||
// method.
|
||||
type GetMinerDataResult struct {
|
||||
MajorVersion uint8 `json:"major_version"`
|
||||
Height uint64 `json:"height"`
|
||||
PrevId string `json:"prev_id"`
|
||||
SeedHash string `json:"seed_hash"`
|
||||
Difficulty string `json:"difficulty"`
|
||||
MedianWeight uint64 `json:"median_weight"`
|
||||
AlreadyGeneratedCoins uint64 `json:"already_generated_coins"`
|
||||
MedianTimestamp uint64 `json:"median_timestamp"`
|
||||
TxBacklog []struct {
|
||||
Id string `json:"id"`
|
||||
BlobSize uint64 `json:"blob_size"`
|
||||
Weight uint64 `json:"weight"`
|
||||
Fee uint64 `json:"fee"`
|
||||
} `json:"tx_backlog"`
|
||||
}
|
||||
|
||||
// SubmitBlockResult is the result of a call to the SubmitBlock RPC
|
||||
// method.
|
||||
type SubmitBlockResult struct {
|
||||
Status string `json:"status"`
|
||||
Error *submitBlockResultError `json:"error"`
|
||||
}
|
||||
|
||||
type submitBlockResultError struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type Peer struct {
|
||||
Host string `json:"host"`
|
||||
ID uint64 `json:"id"`
|
||||
IP uint32 `json:"ip"`
|
||||
LastSeen int64 `json:"last_seen"`
|
||||
Port uint16 `json:"port"`
|
||||
PruningSeed uint32 `json:"pruning_seed"`
|
||||
RPCPort uint16 `json:"rpc_port"`
|
||||
}
|
||||
|
||||
// GetPeerListResult is the result of a call to the GetPeerList RPC method.
|
||||
type GetPeerListResult struct {
|
||||
GrayList []Peer `json:"gray_list"`
|
||||
WhiteList []Peer `json:"white_list"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetConnectionsResult is the result of a call to the GetConnections RPC
|
||||
// method.
|
||||
type GetConnectionsResult struct {
|
||||
Connections []struct {
|
||||
Address string `json:"address"`
|
||||
AvgDownload uint64 `json:"avg_download"`
|
||||
AvgUpload uint64 `json:"avg_upload"`
|
||||
ConnectionID string `json:"connection_id"`
|
||||
CurrentDownload uint64 `json:"current_download"`
|
||||
CurrentUpload uint64 `json:"current_upload"`
|
||||
Height uint64 `json:"height"`
|
||||
Host string `json:"host"`
|
||||
Incoming bool `json:"incoming"`
|
||||
IP string `json:"ip"`
|
||||
LiveTime uint64 `json:"live_time"`
|
||||
LocalIP bool `json:"local_ip"`
|
||||
Localhost bool `json:"localhost"`
|
||||
PeerID string `json:"peer_id"`
|
||||
Port string `json:"port"`
|
||||
RecvCount uint64 `json:"recv_count"`
|
||||
RecvIdleTime uint64 `json:"recv_idle_time"`
|
||||
SendCount uint64 `json:"send_count"`
|
||||
SendIdleTime uint64 `json:"send_idle_time"`
|
||||
State string `json:"state"`
|
||||
SupportFlags uint64 `json:"support_flags"`
|
||||
} `json:"connections"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
type GetOutsResult struct {
|
||||
Outs []struct {
|
||||
Height uint64 `json:"height"`
|
||||
Key string `json:"key"`
|
||||
Mask string `json:"mask"`
|
||||
Txid string `json:"txid"`
|
||||
Unlocked bool `json:"unlocked"`
|
||||
} `json:"outs"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetHeightResult is the result of a call to the GetHeight RPC method.
|
||||
type GetHeightResult struct {
|
||||
Hash string `json:"hash"`
|
||||
Height uint64 `json:"height"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetNetStatsResult is the result of a call to the GetNetStats RPC method.
|
||||
type GetNetStatsResult struct {
|
||||
StartTime int64 `json:"start_time"`
|
||||
TotalBytesIn uint64 `json:"total_bytes_in"`
|
||||
TotalBytesOut uint64 `json:"total_bytes_out"`
|
||||
TotalPacketsIn uint64 `json:"total_packets_in"`
|
||||
TotalPacketsOut uint64 `json:"total_packets_out"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetPublicNodesResult is the result of a call to the GetPublicNodes RPC
|
||||
// method.
|
||||
type GetPublicNodesResult struct {
|
||||
WhiteList []Peer `json:"white"`
|
||||
GrayList []Peer `json:"gray"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GenerateBlocksResult is the result of a call to the GenerateBlocks RPC
|
||||
// method.
|
||||
type GenerateBlocksResult struct {
|
||||
Blocks []string `json:"blocks"`
|
||||
Height int `json:"height"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetBlockCountResult is the result of a call to the GetBlockCount RPC method.
|
||||
type GetBlockCountResult struct {
|
||||
Count uint64 `json:"count"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// RelayTxResult is the result of a call to the RelayTx RPC method.
|
||||
type RelayTxResult struct {
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetCoinbaseTxSumResult is the result of a call to the GetCoinbaseTxSum RPC
|
||||
// method.
|
||||
type GetCoinbaseTxSumResult struct {
|
||||
EmissionAmount int64 `json:"emission_amount"`
|
||||
EmissionAmountTop64 int `json:"emission_amount_top64"`
|
||||
FeeAmount int `json:"fee_amount"`
|
||||
FeeAmountTop64 int `json:"fee_amount_top64"`
|
||||
WideEmissionAmount string `json:"wide_emission_amount"`
|
||||
WideFeeAmount string `json:"wide_fee_amount"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
type BlockHeader struct {
|
||||
// BlockSize is the block size in bytes.
|
||||
//
|
||||
BlockSize uint64 `json:"block_size"`
|
||||
|
||||
// BlockWeight TODO
|
||||
//
|
||||
BlockWeight uint64 `json:"block_weight"`
|
||||
|
||||
// CumulativeDifficulty is the cumulative difficulty of all
|
||||
// blocks up to this one.
|
||||
//
|
||||
CumulativeDifficulty uint64 `json:"cumulative_difficulty"`
|
||||
|
||||
// CumulativeDifficultyTop64 most significant 64 bits of the
|
||||
// 128-bit cumulative difficulty.
|
||||
//
|
||||
CumulativeDifficultyTop64 uint64 `json:"cumulative_difficulty_top64"`
|
||||
|
||||
// Depth is the number of blocks succeeding this block on the
|
||||
// blockchain. (the larger this number, the oldest this block
|
||||
// is).
|
||||
//
|
||||
Depth uint64 `json:"depth"`
|
||||
|
||||
// Difficulty is the difficulty that was set for mining this block.
|
||||
//
|
||||
Difficulty uint64 `json:"difficulty"`
|
||||
|
||||
// DifficultyTop64 corresponds to the most significant 64-bit of
|
||||
// the 128-bit difficulty.
|
||||
//
|
||||
DifficultyTop64 uint64 `json:"difficulty_top64"`
|
||||
|
||||
// Hash is the hash of this block.
|
||||
//
|
||||
Hash string `json:"hash"`
|
||||
|
||||
// Height is the number of blocks preceding this block on the
|
||||
// blockchain.
|
||||
//
|
||||
Height uint64 `json:"height"`
|
||||
|
||||
// LongTermWeight TODO
|
||||
//
|
||||
LongTermWeight uint64 `json:"long_term_weight"`
|
||||
|
||||
// MajorVersion is the major version of the monero protocol at
|
||||
// this block height.
|
||||
//
|
||||
MajorVersion uint `json:"major_version"`
|
||||
|
||||
// MinerTxHash TODO
|
||||
//
|
||||
MinerTxHash string `json:"miner_tx_hash"`
|
||||
|
||||
// MinorVersion is the minor version of the monero protocol at
|
||||
// this block height.
|
||||
//
|
||||
MinorVersion uint `json:"minor_version"`
|
||||
|
||||
// Nonce is the cryptographic random one-time number used in
|
||||
// mining this block.
|
||||
//
|
||||
Nonce uint64 `json:"nonce"`
|
||||
|
||||
// NumTxes is the number of transactions in this block, not
|
||||
// counting the coinbase tx.
|
||||
//
|
||||
NumTxes uint `json:"num_txes"`
|
||||
|
||||
// OrphanStatus indicates whether this block is part of the
|
||||
// longest chain or not (true == not part of it).
|
||||
//
|
||||
OrphanStatus bool `json:"orphan_status"`
|
||||
|
||||
// PowHash TODO
|
||||
//
|
||||
PowHash string `json:"pow_hash"`
|
||||
|
||||
// PrevHash is the hash of the block immediately preceding this
|
||||
// block in the chain.
|
||||
//
|
||||
PrevHash string `json:"prev_hash"`
|
||||
|
||||
// Reward the amount of new atomic-units generated in this
|
||||
// block and rewarded to the miner (1XMR = 1e12 atomic units).
|
||||
//
|
||||
Reward uint64 `json:"reward"`
|
||||
|
||||
// Timestamp is the unix timestamp at which the block was
|
||||
// recorded into the blockchain.
|
||||
//
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
|
||||
// WideCumulativeDifficulty is the cumulative difficulty of all
|
||||
// blocks in the blockchain as a hexadecimal string
|
||||
// representing a 128-bit number.
|
||||
//
|
||||
WideCumulativeDifficulty string `json:"wide_cumulative_difficulty"`
|
||||
|
||||
// WideDifficulty is the network difficulty as a hexadecimal
|
||||
// string representing a 128-bit number.
|
||||
//
|
||||
WideDifficulty string `json:"wide_difficulty"`
|
||||
}
|
||||
|
||||
// GetBlockResult is the result of a call to the GetBlock RPC method.
|
||||
type GetBlockResult struct {
|
||||
// Blob is a hexadecimal representation of the block.
|
||||
//
|
||||
Blob string `json:"blob"`
|
||||
|
||||
// BlockHeader contains the details from the block header.
|
||||
//
|
||||
BlockHeader BlockHeader `json:"block_header"`
|
||||
|
||||
// JSON is a json representation of the block - see
|
||||
// `GetBlockResultJSON`.
|
||||
//
|
||||
JSON string `json:"json"`
|
||||
|
||||
// MinerTxHash is the hash of the coinbase transaction
|
||||
//
|
||||
MinerTxHash string `json:"miner_tx_hash"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetBlockResultJSON is the internal json-formatted block information.
|
||||
type GetBlockResultJSON struct {
|
||||
// MajorVersion (same as in the block header)
|
||||
//
|
||||
MajorVersion uint `json:"major_version"`
|
||||
|
||||
// MinorVersion (same as in the block header)
|
||||
//
|
||||
MinorVersion uint `json:"minor_version"`
|
||||
|
||||
// Timestamp (same as in the block header)
|
||||
//
|
||||
Timestamp uint64 `json:"timestamp"`
|
||||
|
||||
// PrevID (same as `block_hash` in the block header)
|
||||
//
|
||||
PrevID string `json:"prev_id"`
|
||||
|
||||
// Nonce (same as in the block header)
|
||||
//
|
||||
Nonce int `json:"nonce"`
|
||||
|
||||
// MinerTx contains the miner transaction information.
|
||||
//
|
||||
MinerTx struct {
|
||||
// Version is the transaction version number
|
||||
//
|
||||
Version int `json:"version"`
|
||||
|
||||
// UnlockTime is the block height when the coinbase transaction
|
||||
// becomes spendable.
|
||||
//
|
||||
UnlockTime int `json:"unlock_time"`
|
||||
|
||||
// Vin lists the transaction inputs.
|
||||
//
|
||||
Vin []struct {
|
||||
Gen struct {
|
||||
Height int `json:"height"`
|
||||
} `json:"gen"`
|
||||
} `json:"vin"`
|
||||
|
||||
// Vout lists the transaction outputs.
|
||||
//
|
||||
Vout []struct {
|
||||
Amount uint64 `json:"amount"`
|
||||
Target struct {
|
||||
Key string `json:"key"`
|
||||
} `json:"target"`
|
||||
} `json:"vout"`
|
||||
// Extra (aka the transaction id) can be used to include any
|
||||
// random 32byte/64char hex string.
|
||||
//
|
||||
Extra []int `json:"extra"`
|
||||
|
||||
// RctSignatures contain the signatures of tx signers.
|
||||
//
|
||||
// ps.: coinbase txs DO NOT have signatures.
|
||||
//
|
||||
RctSignatures struct {
|
||||
Type int `json:"type"`
|
||||
} `json:"rct_signatures"`
|
||||
} `json:"miner_tx"`
|
||||
|
||||
// TxHashes is the list of hashes of non-coinbase transactions in the
|
||||
// block.
|
||||
//
|
||||
TxHashes []string `json:"tx_hashes"`
|
||||
}
|
||||
|
||||
func (c *GetBlockResultJSON) MinerOutputs() uint64 {
|
||||
res := uint64(0)
|
||||
|
||||
for _, vout := range c.MinerTx.Vout {
|
||||
res += vout.Amount
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// SyncInfoResult is the result of a call to the SyncInfo RPC method.
|
||||
type SyncInfoResult struct {
|
||||
Credits uint64 `json:"credits"`
|
||||
|
||||
Height uint64 `json:"height"`
|
||||
NextNeededPruningSeed uint64 `json:"next_needed_pruning_seed"`
|
||||
Overview string `json:"overview"`
|
||||
Status string `json:"status"`
|
||||
TargetHeight uint64 `json:"target_height"`
|
||||
TopHash string `json:"top_hash"`
|
||||
Untrusted bool `json:"untrusted"`
|
||||
Peers []struct {
|
||||
Info struct {
|
||||
Address string `json:"address"`
|
||||
AddressType uint64 `json:"address_type"`
|
||||
AvgDownload uint64 `json:"avg_download"`
|
||||
AvgUpload uint64 `json:"avg_upload"`
|
||||
ConnectionID string `json:"connection_id"`
|
||||
CurrentDownload uint64 `json:"current_download"`
|
||||
CurrentUpload uint64 `json:"current_upload"`
|
||||
Height uint64 `json:"height"`
|
||||
Host string `json:"host"`
|
||||
IP string `json:"ip"`
|
||||
Incoming bool `json:"incoming"`
|
||||
LiveTime uint64 `json:"live_time"`
|
||||
LocalIP bool `json:"local_ip"`
|
||||
Localhost bool `json:"localhost"`
|
||||
PeerID string `json:"peer_id"`
|
||||
Port string `json:"port"`
|
||||
PruningSeed uint64 `json:"pruning_seed"`
|
||||
RPCCreditsPerHash uint64 `json:"rpc_credits_per_hash"`
|
||||
RPCPort uint64 `json:"rpc_port"`
|
||||
RecvCount uint64 `json:"recv_count"`
|
||||
RecvIdleTime uint64 `json:"recv_idle_time"`
|
||||
SendCount uint64 `json:"send_count"`
|
||||
SendIdleTime uint64 `json:"send_idle_time"`
|
||||
State string `json:"state"`
|
||||
SupportFlags int `json:"support_flags"`
|
||||
} `json:"info"`
|
||||
} `json:"peers"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetLastBlockHeaderResult is the result of a call to the GetLastBlockHeader
|
||||
// RPC method.
|
||||
type GetLastBlockHeaderResult struct {
|
||||
BlockHeader BlockHeader `json:"block_header"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetBlockHeadersRangeResult is the result of a call to the
|
||||
// GetBlockHeadersRange RPC method.
|
||||
type GetBlockHeadersRangeResult struct {
|
||||
Headers []BlockHeader `json:"headers"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetBlockHeaderByHeightResult is the result of a call to the
|
||||
// GetBlockHeaderByHeight RPC method.
|
||||
type GetBlockHeaderByHeightResult struct {
|
||||
BlockHeader BlockHeader `json:"block_header"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetBlockHeaderByHashResult is the result of a call to the
|
||||
// GetBlockHeaderByHash RPC method.
|
||||
type GetBlockHeaderByHashResult struct {
|
||||
BlockHeader BlockHeader `json:"block_header"`
|
||||
BlockHeaders []BlockHeader `json:"block_headers"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
type MiningStatusResult struct {
|
||||
Active bool `json:"active"`
|
||||
Address string `json:"address"`
|
||||
BgIdleThreshold int `json:"bg_idle_threshold"`
|
||||
BgIgnoreBattery bool `json:"bg_ignore_battery"`
|
||||
BgMinIdleSeconds uint64 `json:"bg_min_idle_seconds"`
|
||||
BgTarget uint64 `json:"bg_target"`
|
||||
BlockReward uint64 `json:"block_reward"`
|
||||
BlockTarget uint64 `json:"block_target"`
|
||||
Difficulty uint64 `json:"difficulty"`
|
||||
DifficultyTop64 uint64 `json:"difficulty_top64"`
|
||||
IsBackgroundMiningEnabled bool `json:"is_background_mining_enabled"`
|
||||
PowAlgorithm string `json:"pow_algorithm"`
|
||||
Speed uint64 `json:"speed"`
|
||||
ThreadsCount uint64 `json:"threads_count"`
|
||||
WideDifficulty string `json:"wide_difficulty"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
// GetTransactionPoolStatsResult is the result of a call to the
|
||||
// GetTransactionPoolStats RPC method.
|
||||
type GetTransactionPoolStatsResult struct {
|
||||
PoolStats struct {
|
||||
BytesMax uint64 `json:"bytes_max"`
|
||||
BytesMed uint64 `json:"bytes_med"`
|
||||
BytesMin uint64 `json:"bytes_min"`
|
||||
BytesTotal uint64 `json:"bytes_total"`
|
||||
FeeTotal uint64 `json:"fee_total"`
|
||||
Histo []struct {
|
||||
Bytes uint64 `json:"bytes"`
|
||||
Txs uint64 `json:"txs"`
|
||||
} `json:"histo"`
|
||||
Histo98Pc uint64 `json:"histo_98pc"`
|
||||
Num10M uint64 `json:"num_10m"`
|
||||
NumDoubleSpends uint64 `json:"num_double_spends"`
|
||||
NumFailing uint64 `json:"num_failing"`
|
||||
NumNotRelayed uint64 `json:"num_not_relayed"`
|
||||
Oldest int64 `json:"oldest"`
|
||||
TxsTotal uint64 `json:"txs_total"`
|
||||
} `json:"pool_stats"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
type GetTransactionsResultTransaction struct {
|
||||
AsHex string `json:"as_hex"`
|
||||
AsJSON string `json:"as_json"`
|
||||
BlockHeight uint64 `json:"block_height"`
|
||||
BlockTimestamp int64 `json:"block_timestamp"`
|
||||
DoubleSpendSeen bool `json:"double_spend_seen"`
|
||||
InPool bool `json:"in_pool"`
|
||||
OutputIndices []int `json:"output_indices"`
|
||||
PrunableAsHex string `json:"prunable_as_hex"`
|
||||
PrunableHash string `json:"prunable_hash"`
|
||||
PrunedAsHex string `json:"pruned_as_hex"`
|
||||
TxHash string `json:"tx_hash"`
|
||||
}
|
||||
|
||||
type GetTransactionsResult struct {
|
||||
Credits int `json:"credits"`
|
||||
Status string `json:"status"`
|
||||
TopHash string `json:"top_hash"`
|
||||
Txs []GetTransactionsResultTransaction `json:"txs"`
|
||||
TxsAsHex []string `json:"txs_as_hex"`
|
||||
Untrusted bool `json:"untrusted"`
|
||||
}
|
||||
|
||||
type TransactionJSON struct {
|
||||
Version int `json:"version"`
|
||||
UnlockTime int `json:"unlock_time"`
|
||||
Vin []struct {
|
||||
Key struct {
|
||||
Amount int `json:"amount"`
|
||||
KeyOffsets []uint `json:"key_offsets"`
|
||||
KImage string `json:"k_image"`
|
||||
} `json:"key"`
|
||||
} `json:"vin"`
|
||||
Vout []struct {
|
||||
Amount uint64 `json:"amount"`
|
||||
Target struct {
|
||||
Key string `json:"key"`
|
||||
} `json:"target"`
|
||||
} `json:"vout"`
|
||||
Extra []byte `json:"extra"`
|
||||
RctSignatures struct {
|
||||
Type int `json:"type"`
|
||||
Txnfee uint64 `json:"txnFee"`
|
||||
Ecdhinfo []struct {
|
||||
Amount string `json:"amount"`
|
||||
} `json:"ecdhInfo"`
|
||||
Outpk []string `json:"outPk"`
|
||||
} `json:"rct_signatures"`
|
||||
RctsigPrunable struct {
|
||||
Nbp int `json:"nbp"`
|
||||
Bp []struct {
|
||||
A string `json:"A"`
|
||||
S string `json:"S"`
|
||||
T1 string `json:"T1"`
|
||||
T2 string `json:"T2"`
|
||||
Taux string `json:"taux"`
|
||||
Mu string `json:"mu"`
|
||||
L []string `json:"L"`
|
||||
R []string `json:"R"`
|
||||
LowerA string `json:"a"`
|
||||
B string `json:"b"`
|
||||
T string `json:"t"`
|
||||
} `json:"bp,omitempty"`
|
||||
Bpp []struct {
|
||||
A string `json:"A"`
|
||||
A1 string `json:"A1"`
|
||||
B string `json:"B"`
|
||||
R1 string `json:"r1"`
|
||||
S1 string `json:"s1"`
|
||||
D1 string `json:"d1"`
|
||||
L []string `json:"L"`
|
||||
R []string `json:"R"`
|
||||
} `json:"bpp,omitempty"`
|
||||
Clsags []struct {
|
||||
S []string `json:"s"`
|
||||
C1 string `json:"c1"`
|
||||
D string `json:"D"`
|
||||
} `json:"CLSAGs,omitempty"`
|
||||
Pseudoouts []string `json:"pseudoOuts"`
|
||||
} `json:"rctsig_prunable"`
|
||||
}
|
||||
|
||||
type GetTransactionPoolResult struct {
|
||||
Credits int `json:"credits"`
|
||||
SpentKeyImages []struct {
|
||||
IDHash string `json:"id_hash"`
|
||||
TxsHashes []string `json:"txs_hashes"`
|
||||
} `json:"spent_key_images"`
|
||||
Status string `json:"status"`
|
||||
TopHash string `json:"top_hash"`
|
||||
Transactions []struct {
|
||||
BlobSize uint64 `json:"blob_size"`
|
||||
DoNotRelay bool `json:"do_not_relay"`
|
||||
DoubleSpendSeen bool `json:"double_spend_seen"`
|
||||
Fee uint64 `json:"fee"`
|
||||
IDHash string `json:"id_hash"`
|
||||
KeptByBlock bool `json:"kept_by_block"`
|
||||
LastFailedHeight uint64 `json:"last_failed_height"`
|
||||
LastFailedIDHash string `json:"last_failed_id_hash"`
|
||||
LastRelayedTime uint64 `json:"last_relayed_time"`
|
||||
MaxUsedBlockHeight uint64 `json:"max_used_block_height"`
|
||||
MaxUsedBlockIDHash string `json:"max_used_block_id_hash"`
|
||||
ReceiveTime int64 `json:"receive_time"`
|
||||
Relayed bool `json:"relayed"`
|
||||
TxBlob string `json:"tx_blob"`
|
||||
TxJSON string `json:"tx_json"`
|
||||
Weight uint64 `json:"weight"`
|
||||
} `json:"transactions"`
|
||||
Untrusted bool `json:"untrusted"`
|
||||
}
|
||||
|
||||
type SetLogCategoriesRequestParameters struct {
|
||||
// Categories to log with their corresponding levels formatted as a
|
||||
// comma-separated list of <category>:<level> pairs.
|
||||
//
|
||||
// For instance, to activate verbosity 1 for the `net.http` category
|
||||
// and verbosity 4 for `net.dns`:
|
||||
//
|
||||
// net.htpp:1,net.dns:4
|
||||
//
|
||||
Categories string `json:"categories"`
|
||||
}
|
||||
|
||||
type SetLogCategoriesResult struct {
|
||||
Categories string `json:"categories"`
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
type SetLogLevelRequestParameters struct {
|
||||
// Level is the log level that the daemon should use. From 0 to 4 (less
|
||||
// verbose to more verbose).
|
||||
//
|
||||
Level int8 `json:"level"`
|
||||
}
|
||||
|
||||
type SetLogLevelResult struct {
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
type SetLimitRequestParameters struct {
|
||||
// LimitUp is the upload limit in kB/s
|
||||
//
|
||||
LimitUp uint64 `json:"limit_up"`
|
||||
// LimitDown is the download limit in kB/s
|
||||
//
|
||||
LimitDown uint64 `json:"limit_down"`
|
||||
}
|
||||
|
||||
type SetLimitResult struct {
|
||||
// LimitUp is the upload limit in kB/s
|
||||
//
|
||||
LimitUp uint64 `json:"limit_up"`
|
||||
// LimitDOwn is the download limit in kB/s
|
||||
//
|
||||
LimitDown uint64 `json:"limit_down"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
type GetLimitResult struct {
|
||||
// LimitUp is the upload limit in kB/s
|
||||
//
|
||||
LimitUp uint64 `json:"limit_up"`
|
||||
// LimitDown is the download limit in kB/s
|
||||
//
|
||||
LimitDown uint64 `json:"limit_down"`
|
||||
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
type StartMiningRequestParameters struct {
|
||||
MinerAddress string `json:"miner_address"`
|
||||
BackgroundMining bool `json:"background_mining"`
|
||||
IgnoreBattery bool `json:"ignore_battery"`
|
||||
ThreadsCount uint `json:"threads_count"`
|
||||
}
|
||||
|
||||
type StartMiningResult struct {
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
||||
|
||||
type StopMiningResult struct {
|
||||
RPCResultFooter `json:",inline"`
|
||||
}
|
4
monero/client/rpc/doc.go
Normal file
4
monero/client/rpc/doc.go
Normal file
|
@ -0,0 +1,4 @@
|
|||
// Package rpc provides a client that's able to communicate with a `monerod`
|
||||
// daemon via its RPC interfaces.
|
||||
//
|
||||
package rpc
|
|
@ -1,9 +1,9 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/P2Pool/consensus/v3/monero/client/rpc/daemon"
|
||||
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/mempool"
|
||||
"git.gammaspectra.live/P2Pool/consensus/v3/types"
|
||||
"git.gammaspectra.live/P2Pool/go-monero/pkg/rpc/daemon"
|
||||
)
|
||||
|
||||
func isRctBulletproof(t int) bool {
|
||||
|
|
Loading…
Reference in a new issue