ec17654986
add dao which takes care about all CRUD operations on storage remove blockchain state since everything is stored on change remove storage operations from structs(entities) move structs to entities package
221 lines
5.2 KiB
Go
221 lines
5.2 KiB
Go
package rpc
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/CityOfZion/neo-go/pkg/core/entities"
|
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
|
"github.com/CityOfZion/neo-go/pkg/util"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const (
|
|
defaultDialTimeout = 4 * time.Second
|
|
defaultRequestTimeout = 4 * time.Second
|
|
defaultClientVersion = "2.0"
|
|
)
|
|
|
|
// Client represents the middleman for executing JSON RPC calls
|
|
// to remote NEO RPC nodes.
|
|
type Client struct {
|
|
// The underlying http client. It's never a good practice to use
|
|
// the http.DefaultClient, therefore we will role our own.
|
|
cliMu *sync.Mutex
|
|
cli *http.Client
|
|
endpoint *url.URL
|
|
ctx context.Context
|
|
version string
|
|
wifMu *sync.Mutex
|
|
wif *keys.WIF
|
|
balancerMu *sync.Mutex
|
|
balancer BalanceGetter
|
|
}
|
|
|
|
// ClientOptions defines options for the RPC client.
|
|
// All Values are optional. If any duration is not specified
|
|
// a default of 3 seconds will be used.
|
|
type ClientOptions struct {
|
|
Cert string
|
|
Key string
|
|
CACert string
|
|
DialTimeout time.Duration
|
|
Client *http.Client
|
|
// Version is the version of the client that will be send
|
|
// along with the request body. If no version is specified
|
|
// the default version (currently 2.0) will be used.
|
|
Version string
|
|
}
|
|
|
|
// NewClient returns a new Client ready to use.
|
|
func NewClient(ctx context.Context, endpoint string, opts ClientOptions) (*Client, error) {
|
|
url, err := url.Parse(endpoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if opts.Version == "" {
|
|
opts.Version = defaultClientVersion
|
|
}
|
|
|
|
if opts.Client == nil {
|
|
opts.Client = &http.Client{
|
|
Transport: &http.Transport{
|
|
DialContext: (&net.Dialer{
|
|
Timeout: opts.DialTimeout,
|
|
}).DialContext,
|
|
},
|
|
}
|
|
}
|
|
|
|
// TODO(@antdm): Enable SSL.
|
|
if opts.Cert != "" && opts.Key != "" {
|
|
}
|
|
|
|
if opts.Client.Timeout == 0 {
|
|
opts.Client.Timeout = defaultRequestTimeout
|
|
}
|
|
|
|
return &Client{
|
|
ctx: ctx,
|
|
cli: opts.Client,
|
|
cliMu: new(sync.Mutex),
|
|
balancerMu: new(sync.Mutex),
|
|
wifMu: new(sync.Mutex),
|
|
endpoint: url,
|
|
version: opts.Version,
|
|
}, nil
|
|
}
|
|
|
|
// WIF returns WIF structure associated with the client.
|
|
func (c *Client) WIF() keys.WIF {
|
|
c.wifMu.Lock()
|
|
defer c.wifMu.Unlock()
|
|
return keys.WIF{
|
|
Version: c.wif.Version,
|
|
Compressed: c.wif.Compressed,
|
|
PrivateKey: c.wif.PrivateKey,
|
|
S: c.wif.S,
|
|
}
|
|
}
|
|
|
|
// SetWIF decodes given WIF and adds some wallet
|
|
// data to client. Useful for RPC calls that require an open wallet.
|
|
func (c *Client) SetWIF(wif string) error {
|
|
c.wifMu.Lock()
|
|
defer c.wifMu.Unlock()
|
|
decodedWif, err := keys.WIFDecode(wif, 0x00)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to decode WIF; failed to add WIF to client ")
|
|
}
|
|
c.wif = decodedWif
|
|
return nil
|
|
}
|
|
|
|
// Balancer is a getter for balance field.
|
|
func (c *Client) Balancer() BalanceGetter {
|
|
c.balancerMu.Lock()
|
|
defer c.balancerMu.Unlock()
|
|
return c.balancer
|
|
}
|
|
|
|
// SetBalancer is a setter for balance field.
|
|
func (c *Client) SetBalancer(b BalanceGetter) {
|
|
c.balancerMu.Lock()
|
|
defer c.balancerMu.Unlock()
|
|
|
|
if b != nil {
|
|
c.balancer = b
|
|
}
|
|
}
|
|
|
|
// Client is a getter for client field.
|
|
func (c *Client) Client() *http.Client {
|
|
c.cliMu.Lock()
|
|
defer c.cliMu.Unlock()
|
|
return c.cli
|
|
}
|
|
|
|
// SetClient is a setter for client field.
|
|
func (c *Client) SetClient(cli *http.Client) {
|
|
c.cliMu.Lock()
|
|
defer c.cliMu.Unlock()
|
|
|
|
if cli != nil {
|
|
c.cli = cli
|
|
}
|
|
}
|
|
|
|
// CalculateInputs creates input transactions for the specified amount of given
|
|
// asset belonging to specified address. This implementation uses GetUnspents
|
|
// JSON-RPC call internally, so make sure your RPC server suppors that.
|
|
func (c *Client) CalculateInputs(address string, asset util.Uint256, cost util.Fixed8) ([]transaction.Input, util.Fixed8, error) {
|
|
var utxos entities.UnspentBalances
|
|
|
|
resp, err := c.GetUnspents(address)
|
|
if err != nil || resp.Error != nil {
|
|
if err == nil {
|
|
err = fmt.Errorf("remote returned %d: %s", resp.Error.Code, resp.Error.Message)
|
|
}
|
|
return nil, util.Fixed8(0), errors.Wrapf(err, "cannot get balance for address %v", address)
|
|
}
|
|
for _, ubi := range resp.Result.Balance {
|
|
if asset.Equals(ubi.AssetHash) {
|
|
utxos = ubi.Unspents
|
|
break
|
|
}
|
|
}
|
|
return unspentsToInputs(utxos, cost)
|
|
|
|
}
|
|
|
|
func (c *Client) performRequest(method string, p params, v interface{}) error {
|
|
var (
|
|
r = request{
|
|
JSONRPC: c.version,
|
|
Method: method,
|
|
Params: p.values,
|
|
ID: 1,
|
|
}
|
|
buf = new(bytes.Buffer)
|
|
)
|
|
|
|
if err := json.NewEncoder(buf).Encode(r); err != nil {
|
|
return err
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", c.endpoint.String(), buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp, err := c.Client().Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("remote responded with a non 200 response: %d", resp.StatusCode)
|
|
}
|
|
|
|
return json.NewDecoder(resp.Body).Decode(v)
|
|
}
|
|
|
|
// Ping attempts to create a connection to the endpoint.
|
|
// and returns an error if there is one.
|
|
func (c *Client) Ping() error {
|
|
conn, err := net.DialTimeout("tcp", c.endpoint.Host, defaultDialTimeout)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_ = conn.Close()
|
|
return nil
|
|
}
|