Remove git.gammaspectra.live/P2Pool/go-monero dependency, replace with pkg/rpc and pkg/levin inline

This commit is contained in:
DataHoarder 2024-04-07 19:29:22 +02:00
parent c999597d5e
commit 5a50924816
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
26 changed files with 4057 additions and 6 deletions

9
go.mod
View file

@ -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
View file

@ -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=

View file

@ -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
View 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.

View 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)

View 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)...)...)
}

View 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
}

View 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
}

View 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
View 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()
}

View 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)
}

View 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
View 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.

View 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
View 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
}

View file

@ -0,0 +1,3 @@
package rpc
var EndpointJSONRPC = endpointJSONRPC

View 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())
}

View 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")
}

View 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,
}
}

View 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)
}

View 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

View 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
}

View 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
}

View 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
View 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

View file

@ -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 {