uncommitted stuff
This commit is contained in:
parent
0945466653
commit
430fc530f2
8 changed files with 672 additions and 15 deletions
149
electrum/client.go
Normal file
149
electrum/client.go
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
package electrum
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v3/lbrycrd"
|
||||||
|
"github.com/lbryio/lbry.go/v3/schema/stake"
|
||||||
|
types "github.com/lbryio/types/v2/go"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/errors"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/lbryio/lbcutil"
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Raw makes a raw wallet server request
|
||||||
|
func (n *Node) Raw(method string, params []string, v interface{}) error {
|
||||||
|
return n.request(method, params, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerVersion returns the server's version.
|
||||||
|
// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#server-version
|
||||||
|
func (n *Node) ServerVersion() (string, error) {
|
||||||
|
resp := &struct {
|
||||||
|
Result []string `json:"result"`
|
||||||
|
}{}
|
||||||
|
err := n.request("server.version", []string{"reflector.go", ProtocolVersion}, resp)
|
||||||
|
|
||||||
|
var v string
|
||||||
|
if len(resp.Result) >= 2 {
|
||||||
|
v = resp.Result[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) Resolve(url string) (*types.Output, error) {
|
||||||
|
outputs := &types.Outputs{}
|
||||||
|
resp := &struct {
|
||||||
|
Result string `json:"result"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
err := n.request("blockchain.claimtrie.resolve", []string{url}, resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := base64.StdEncoding.DecodeString(resp.Result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = proto.Unmarshal(b, outputs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(outputs.GetTxos()) != 1 {
|
||||||
|
return nil, errors.New("expected 1 output, got " + cast.ToString(len(outputs.GetTxos())))
|
||||||
|
}
|
||||||
|
|
||||||
|
if e := outputs.GetTxos()[0].GetError(); e != nil {
|
||||||
|
return nil, errors.Newf("%s: %s", e.GetCode(), e.GetText())
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputs.GetTxos()[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetClaimsInTxResp struct {
|
||||||
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
|
ID int `json:"id"`
|
||||||
|
Result []struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
ClaimID string `json:"claim_id"`
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Nout int `json:"nout"`
|
||||||
|
Amount int `json:"amount"`
|
||||||
|
Depth int `json:"depth"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
ClaimSequence int `json:"claim_sequence"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Supports []interface{} `json:"supports"` // TODO: finish me
|
||||||
|
EffectiveAmount int `json:"effective_amount"`
|
||||||
|
ValidAtHeight int `json:"valid_at_height"`
|
||||||
|
} `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) GetClaimsInTx(txid string) (*GetClaimsInTxResp, error) {
|
||||||
|
var resp GetClaimsInTxResp
|
||||||
|
err := n.request("blockchain.claimtrie.getclaimsintx", []string{txid}, &resp)
|
||||||
|
return &resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) GetTx(txid string) (string, error) {
|
||||||
|
resp := &struct {
|
||||||
|
Result string `json:"result"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
err := n.request("blockchain.transaction.get", []string{txid}, resp)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) GetClaimInTx(txid string, nout int) (*types.Claim, error) {
|
||||||
|
hexTx, err := n.GetTx(txid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawTx, err := hex.DecodeString(hexTx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := lbcutil.NewTxFromBytes(rawTx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tx.MsgTx().TxOut) <= nout {
|
||||||
|
return nil, errors.WithStack(errors.New("nout not found"))
|
||||||
|
}
|
||||||
|
|
||||||
|
script := tx.MsgTx().TxOut[nout].PkScript
|
||||||
|
|
||||||
|
var value []byte
|
||||||
|
if lbrycrd.IsClaimNameScript(script) {
|
||||||
|
_, value, _, err = lbrycrd.ParseClaimNameScript(script)
|
||||||
|
} else if lbrycrd.IsClaimUpdateScript(script) {
|
||||||
|
_, _, value, _, err = lbrycrd.ParseClaimUpdateScript(script)
|
||||||
|
} else {
|
||||||
|
err = errors.New("no claim found in output")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ch, err := stake.DecodeClaimBytes(value, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ch.Claim, nil
|
||||||
|
}
|
279
electrum/network.go
Normal file
279
electrum/network.go
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
package electrum
|
||||||
|
|
||||||
|
// copied from https://github.com/d4l3k/go-electrum
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v3/extras/stop"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"go.uber.org/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ClientVersion = "0.0.1"
|
||||||
|
ProtocolVersion = "1.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotImplemented = errors.New("not implemented")
|
||||||
|
ErrNodeConnected = errors.New("node already connected")
|
||||||
|
ErrConnectFailed = errors.New("failed to connect")
|
||||||
|
ErrTimeout = errors.New("timeout")
|
||||||
|
)
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
data []byte
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Node struct {
|
||||||
|
transport *TCPTransport
|
||||||
|
nextId atomic.Uint32
|
||||||
|
grp *stop.Group
|
||||||
|
|
||||||
|
handlersMu *sync.RWMutex
|
||||||
|
handlers map[uint32]chan response
|
||||||
|
|
||||||
|
pushHandlersMu *sync.RWMutex
|
||||||
|
pushHandlers map[string][]chan response
|
||||||
|
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNode creates a new node.
|
||||||
|
func NewNode() *Node {
|
||||||
|
return &Node{
|
||||||
|
handlers: make(map[uint32]chan response),
|
||||||
|
pushHandlers: make(map[string][]chan response),
|
||||||
|
handlersMu: &sync.RWMutex{},
|
||||||
|
pushHandlersMu: &sync.RWMutex{},
|
||||||
|
grp: stop.New(),
|
||||||
|
timeout: 1 * time.Second,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect creates a new connection to the specified address.
|
||||||
|
func (n *Node) Connect(addrs []string, config *tls.Config) error {
|
||||||
|
if n.transport != nil {
|
||||||
|
return errors.WithStack(ErrNodeConnected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shuffle addresses for load balancing
|
||||||
|
rand.Shuffle(len(addrs), func(i, j int) { addrs[i], addrs[j] = addrs[j], addrs[i] })
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
n.transport, err = NewTransport(addr, config)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if errors.Is(err, ErrTimeout) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if e, ok := err.(*net.OpError); ok && e.Err.Error() == "no such host" {
|
||||||
|
// net.errNoSuchHost is not exported, so we have to string-match
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.transport == nil {
|
||||||
|
return errors.WithStack(ErrConnectFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("wallet connected to %s", n.transport.conn.RemoteAddr())
|
||||||
|
|
||||||
|
n.grp.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer n.grp.Done()
|
||||||
|
<-n.grp.Ch()
|
||||||
|
n.transport.Shutdown()
|
||||||
|
}()
|
||||||
|
|
||||||
|
n.grp.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer n.grp.Done()
|
||||||
|
n.handleErrors()
|
||||||
|
}()
|
||||||
|
|
||||||
|
n.grp.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer n.grp.Done()
|
||||||
|
n.listen()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) Shutdown() {
|
||||||
|
var addr net.Addr
|
||||||
|
if n.transport != nil {
|
||||||
|
addr = n.transport.conn.RemoteAddr()
|
||||||
|
}
|
||||||
|
log.Debugf("shutting down wallet %s", addr)
|
||||||
|
n.grp.StopAndWait()
|
||||||
|
log.Debugf("wallet stopped")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) handleErrors() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-n.grp.Ch():
|
||||||
|
return
|
||||||
|
case err := <-n.transport.Errors():
|
||||||
|
n.err(errors.WithStack(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// err handles errors produced by the foreign node.
|
||||||
|
func (n *Node) err(err error) {
|
||||||
|
// TODO: Better error handling.
|
||||||
|
log.Error(errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// listen processes messages from the server.
|
||||||
|
func (n *Node) listen() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-n.grp.Ch():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-n.grp.Ch():
|
||||||
|
return
|
||||||
|
case bytes := <-n.transport.Responses():
|
||||||
|
msg := &struct {
|
||||||
|
Id uint32 `json:"id"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Error struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
} `json:"error"`
|
||||||
|
}{}
|
||||||
|
msg2 := &struct {
|
||||||
|
Id uint32 `json:"id"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Error struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
} `json:"message"`
|
||||||
|
} `json:"error"`
|
||||||
|
}{}
|
||||||
|
r := response{}
|
||||||
|
|
||||||
|
err := json.Unmarshal(bytes, msg)
|
||||||
|
if err != nil {
|
||||||
|
// try msg2, a hack around the weird error-in-error response we sometimes get from wallet server
|
||||||
|
// maybe that happens because the wallet server passes a lbrycrd error through to us?
|
||||||
|
if err2 := json.Unmarshal(bytes, msg2); err2 == nil {
|
||||||
|
err = nil
|
||||||
|
msg.Id = msg2.Id
|
||||||
|
msg.Method = msg2.Method
|
||||||
|
msg.Error = msg2.Error.Message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
r.err = errors.WithStack(err)
|
||||||
|
n.err(r.err)
|
||||||
|
} else if len(msg.Error.Message) > 0 {
|
||||||
|
r.err = errors.WithStack(errors.Newf("%d: %s", msg.Error.Code, msg.Error.Message))
|
||||||
|
} else {
|
||||||
|
r.data = bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(msg.Method) > 0 {
|
||||||
|
n.pushHandlersMu.RLock()
|
||||||
|
handlers := n.pushHandlers[msg.Method]
|
||||||
|
n.pushHandlersMu.RUnlock()
|
||||||
|
|
||||||
|
for _, handler := range handlers {
|
||||||
|
select {
|
||||||
|
case handler <- r:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n.handlersMu.RLock()
|
||||||
|
c, ok := n.handlers[msg.Id]
|
||||||
|
n.handlersMu.RUnlock()
|
||||||
|
if ok {
|
||||||
|
c <- r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// listenPush returns a channel of messages matching the method.
|
||||||
|
//func (n *Node) listenPush(method string) <-chan []byte {
|
||||||
|
// c := make(chan []byte, 1)
|
||||||
|
// n.pushHandlersMu.Lock()
|
||||||
|
// defer n.pushHandlersMu.Unlock()
|
||||||
|
// n.pushHandlers[method] = append(n.pushHandlers[method], c)
|
||||||
|
// return c
|
||||||
|
//}
|
||||||
|
|
||||||
|
// request makes a request to the server and unmarshals the response into v.
|
||||||
|
func (n *Node) request(method string, params []string, v interface{}) error {
|
||||||
|
msg := struct {
|
||||||
|
Id uint32 `json:"id"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Params []string `json:"params"`
|
||||||
|
}{
|
||||||
|
Id: n.nextId.Load(),
|
||||||
|
Method: method,
|
||||||
|
Params: params,
|
||||||
|
}
|
||||||
|
n.nextId.Inc()
|
||||||
|
|
||||||
|
bytes, err := json.Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
bytes = append(bytes, delimiter)
|
||||||
|
|
||||||
|
c := make(chan response, 1)
|
||||||
|
|
||||||
|
n.handlersMu.Lock()
|
||||||
|
n.handlers[msg.Id] = c
|
||||||
|
n.handlersMu.Unlock()
|
||||||
|
|
||||||
|
err = n.transport.Send(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r response
|
||||||
|
select {
|
||||||
|
case <-n.grp.Ch():
|
||||||
|
return nil
|
||||||
|
case r = <-c:
|
||||||
|
case <-time.After(n.timeout):
|
||||||
|
r = response{err: ErrTimeout}
|
||||||
|
}
|
||||||
|
|
||||||
|
n.handlersMu.Lock()
|
||||||
|
delete(n.handlers, msg.Id)
|
||||||
|
n.handlersMu.Unlock()
|
||||||
|
|
||||||
|
if r.err != nil {
|
||||||
|
return errors.WithStack(r.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.WithStack(json.Unmarshal(r.data, v))
|
||||||
|
}
|
136
electrum/transport.go
Normal file
136
electrum/transport.go
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
package electrum
|
||||||
|
|
||||||
|
// copied from https://github.com/d4l3k/go-electrum
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v3/extras/stop"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TCPTransport struct {
|
||||||
|
conn net.Conn
|
||||||
|
responses chan []byte
|
||||||
|
errors chan error
|
||||||
|
grp *stop.Group
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransport(addr string, config *tls.Config) (*TCPTransport, error) {
|
||||||
|
var conn net.Conn
|
||||||
|
var err error
|
||||||
|
|
||||||
|
timeout := 5 * time.Second
|
||||||
|
if config != nil {
|
||||||
|
conn, err = tls.DialWithDialer(&net.Dialer{Timeout: timeout}, "tcp", addr, config)
|
||||||
|
} else {
|
||||||
|
conn, err = net.DialTimeout("tcp", addr, timeout)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &TCPTransport{
|
||||||
|
conn: conn,
|
||||||
|
responses: make(chan []byte),
|
||||||
|
errors: make(chan error),
|
||||||
|
grp: stop.New(),
|
||||||
|
}
|
||||||
|
|
||||||
|
t.grp.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer t.grp.Done()
|
||||||
|
<-t.grp.Ch()
|
||||||
|
t.close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
t.grp.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer t.grp.Done()
|
||||||
|
t.listen()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = t.test()
|
||||||
|
if err != nil {
|
||||||
|
t.grp.StopAndWait()
|
||||||
|
return nil, errors.WithMessage(err, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const delimiter = byte('\n')
|
||||||
|
|
||||||
|
func (t *TCPTransport) Send(body []byte) error {
|
||||||
|
log.Debugf("%s <- %s", t.conn.RemoteAddr(), body)
|
||||||
|
_, err := t.conn.Write(body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TCPTransport) Responses() <-chan []byte { return t.responses }
|
||||||
|
func (t *TCPTransport) Errors() <-chan error { return t.errors }
|
||||||
|
func (t *TCPTransport) Shutdown() { t.grp.StopAndWait() }
|
||||||
|
|
||||||
|
func (t *TCPTransport) listen() {
|
||||||
|
reader := bufio.NewReader(t.conn)
|
||||||
|
for {
|
||||||
|
line, err := reader.ReadBytes(delimiter)
|
||||||
|
if err != nil {
|
||||||
|
t.error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("%s -> %s", t.conn.RemoteAddr(), line)
|
||||||
|
|
||||||
|
t.responses <- line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TCPTransport) error(err error) {
|
||||||
|
select {
|
||||||
|
case t.errors <- err:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TCPTransport) test() error {
|
||||||
|
err := t.Send([]byte(`{"id":1,"method":"server.version"}` + "\n"))
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
select {
|
||||||
|
case data = <-t.Responses():
|
||||||
|
case <-time.Tick(1 * time.Second):
|
||||||
|
return errors.WithStack(ErrTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response struct {
|
||||||
|
Error struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
} `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &response)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
if response.Error.Message != "" {
|
||||||
|
return errors.WithStack(errors.New(response.Error.Message))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TCPTransport) close() {
|
||||||
|
err := t.conn.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.error(err)
|
||||||
|
}
|
||||||
|
}
|
2
go.sum
2
go.sum
|
@ -39,7 +39,6 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
|
||||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
|
||||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||||
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ=
|
|
||||||
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o=
|
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o=
|
||||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
|
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
|
||||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||||
|
@ -265,7 +264,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
||||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||||
github.com/lbryio/lbcd v0.0.0-20200203050410-e1076f12bf19 h1:Cm9IuRnz5Pj1v/RsdkZ6UrDALn7wyfNtsGTpi/aVf2w=
|
|
||||||
github.com/lbryio/lbcd v0.0.0-20200203050410-e1076f12bf19/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
github.com/lbryio/lbcd v0.0.0-20200203050410-e1076f12bf19/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||||
github.com/lbryio/lbcd v0.22.100-beta/go.mod h1:u8SaFX4xdGMMR5xasBGfgApC8pvD4rnK2OujZnrq5gs=
|
github.com/lbryio/lbcd v0.22.100-beta/go.mod h1:u8SaFX4xdGMMR5xasBGfgApC8pvD4rnK2OujZnrq5gs=
|
||||||
github.com/lbryio/lbcd v0.22.101-beta h1:umLIxhyRwPdi91vtsDfgW85viK0AV8RPtIF9zQYtXw0=
|
github.com/lbryio/lbcd v0.22.101-beta h1:umLIxhyRwPdi91vtsDfgW85viK0AV8RPtIF9zQYtXw0=
|
||||||
|
|
|
@ -3,7 +3,7 @@ package lbrycrd_test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/lbrycrd"
|
"github.com/lbryio/lbry.go/v3/lbrycrd"
|
||||||
)
|
)
|
||||||
|
|
||||||
var claimIdTests = []struct {
|
var claimIdTests = []struct {
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package lbrycrd
|
package lbrycrd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
|
||||||
|
|
||||||
"github.com/lbryio/lbcd/txscript"
|
"github.com/lbryio/lbcd/txscript"
|
||||||
"github.com/lbryio/lbcutil"
|
"github.com/lbryio/lbcutil"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetClaimSupportPayoutScript(name, claimid string, address lbcutil.Address) ([]byte, error) {
|
func GetClaimSupportPayoutScript(name, claimid string, address lbcutil.Address) ([]byte, error) {
|
||||||
|
@ -14,12 +15,12 @@ func GetClaimSupportPayoutScript(name, claimid string, address lbcutil.Address)
|
||||||
|
|
||||||
pkscript, err := txscript.PayToAddrScript(address)
|
pkscript, err := txscript.PayToAddrScript(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Err(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes, err := hex.DecodeString(claimid)
|
bytes, err := hex.DecodeString(claimid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Err(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return txscript.NewScriptBuilder().
|
return txscript.NewScriptBuilder().
|
||||||
|
@ -38,7 +39,7 @@ func GetClaimNamePayoutScript(name string, value []byte, address lbcutil.Address
|
||||||
|
|
||||||
pkscript, err := txscript.PayToAddrScript(address)
|
pkscript, err := txscript.PayToAddrScript(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Err(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return txscript.NewScriptBuilder().
|
return txscript.NewScriptBuilder().
|
||||||
|
@ -56,12 +57,12 @@ func GetUpdateClaimPayoutScript(name, claimid string, value []byte, address lbcu
|
||||||
|
|
||||||
pkscript, err := txscript.PayToAddrScript(address)
|
pkscript, err := txscript.PayToAddrScript(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Err(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes, err := hex.DecodeString(claimid)
|
bytes, err := hex.DecodeString(claimid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Err(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return txscript.NewScriptBuilder().
|
return txscript.NewScriptBuilder().
|
||||||
|
@ -74,3 +75,96 @@ func GetUpdateClaimPayoutScript(name, claimid string, value []byte, address lbcu
|
||||||
AddOps(pkscript). //OP_DUP OP_HASH160 <address> OP_EQUALVERIFY OP_CHECKSIG
|
AddOps(pkscript). //OP_DUP OP_HASH160 <address> OP_EQUALVERIFY OP_CHECKSIG
|
||||||
Script()
|
Script()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsClaimNameScript returns true if the script for the vout contains the OP_CLAIM_NAME code.
|
||||||
|
func IsClaimNameScript(script []byte) bool {
|
||||||
|
return len(script) > 0 && script[0] == txscript.OP_CLAIMNAME
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsClaimUpdateScript returns true if the script for the vout contains the OP_CLAIM_UPDATE code.
|
||||||
|
func IsClaimUpdateScript(script []byte) bool {
|
||||||
|
return len(script) > 0 && script[0] == txscript.OP_UPDATECLAIM
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseClaimNameScript parses a script for the claim of a name.
|
||||||
|
func ParseClaimNameScript(script []byte) (name string, value []byte, pubkeyscript []byte, err error) {
|
||||||
|
// Already validated by blockchain so can be assumed
|
||||||
|
// opClaimName Name Value OP_2DROP OP_DROP pubkeyscript
|
||||||
|
nameBytesToRead := int(script[1])
|
||||||
|
nameStart := 2
|
||||||
|
if nameBytesToRead == txscript.OP_PUSHDATA1 {
|
||||||
|
nameBytesToRead = int(script[2])
|
||||||
|
nameStart = 3
|
||||||
|
} else if nameBytesToRead > txscript.OP_PUSHDATA1 {
|
||||||
|
return "", nil, nil, errors.WithStack(errors.New("bytes to read is more than next byte"))
|
||||||
|
}
|
||||||
|
nameEnd := nameStart + nameBytesToRead
|
||||||
|
name = string(script[nameStart:nameEnd])
|
||||||
|
dataPushType := int(script[nameEnd])
|
||||||
|
valueBytesToRead := int(script[nameEnd])
|
||||||
|
valueStart := nameEnd + 1
|
||||||
|
if dataPushType == txscript.OP_PUSHDATA1 {
|
||||||
|
valueBytesToRead = int(script[nameEnd+1])
|
||||||
|
valueStart = nameEnd + 2
|
||||||
|
} else if dataPushType == txscript.OP_PUSHDATA2 {
|
||||||
|
valueStart = nameEnd + 3
|
||||||
|
valueBytesToRead = int(binary.LittleEndian.Uint16(script[nameEnd+1 : valueStart]))
|
||||||
|
} else if dataPushType == txscript.OP_PUSHDATA4 {
|
||||||
|
valueStart = nameEnd + 5
|
||||||
|
valueBytesToRead = int(binary.LittleEndian.Uint32(script[nameEnd+2 : valueStart]))
|
||||||
|
}
|
||||||
|
valueEnd := valueStart + valueBytesToRead
|
||||||
|
value = script[valueStart:valueEnd]
|
||||||
|
pksStart := valueEnd + 2 // +2 to ignore OP_2DROP and OP_DROP
|
||||||
|
pubkeyscript = script[pksStart:] //Remainder is always pubkeyscript
|
||||||
|
|
||||||
|
return name, value, pubkeyscript, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseClaimUpdateScript parses a script for an update of a claim.
|
||||||
|
func ParseClaimUpdateScript(script []byte) (name string, claimid string, value []byte, pubkeyscript []byte, err error) {
|
||||||
|
// opUpdateClaim Name ClaimID Value OP_2DROP OP_2DROP pubkeyscript
|
||||||
|
|
||||||
|
//Name
|
||||||
|
nameBytesToRead := int(script[1])
|
||||||
|
nameStart := 2
|
||||||
|
if nameBytesToRead == txscript.OP_PUSHDATA1 {
|
||||||
|
nameBytesToRead = int(script[2])
|
||||||
|
nameStart = 3
|
||||||
|
} else if nameBytesToRead > txscript.OP_PUSHDATA1 {
|
||||||
|
err = errors.WithStack(errors.New("ParseClaimUpdateScript: Bytes to read is more than next byte! "))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nameEnd := nameStart + nameBytesToRead
|
||||||
|
name = string(script[nameStart:nameEnd])
|
||||||
|
|
||||||
|
//ClaimID
|
||||||
|
claimidBytesToRead := int(script[nameEnd])
|
||||||
|
claimidStart := nameEnd + 1
|
||||||
|
claimidEnd := claimidStart + claimidBytesToRead
|
||||||
|
bytes := rev(script[claimidStart:claimidEnd])
|
||||||
|
claimid = hex.EncodeToString(bytes)
|
||||||
|
|
||||||
|
//Value
|
||||||
|
dataPushType := int(script[claimidEnd])
|
||||||
|
valueBytesToRead := int(script[claimidEnd])
|
||||||
|
valueStart := claimidEnd + 1
|
||||||
|
if dataPushType == txscript.OP_PUSHDATA1 {
|
||||||
|
valueBytesToRead = int(script[claimidEnd+1])
|
||||||
|
valueStart = claimidEnd + 2
|
||||||
|
} else if dataPushType == txscript.OP_PUSHDATA2 {
|
||||||
|
valueStart = claimidEnd + 3
|
||||||
|
valueBytesToRead = int(binary.LittleEndian.Uint16(script[claimidEnd+1 : valueStart]))
|
||||||
|
} else if dataPushType == txscript.OP_PUSHDATA4 {
|
||||||
|
valueStart = claimidEnd + 5
|
||||||
|
valueBytesToRead = int(binary.LittleEndian.Uint32(script[claimidEnd+2 : valueStart]))
|
||||||
|
}
|
||||||
|
valueEnd := valueStart + valueBytesToRead
|
||||||
|
value = script[valueStart:valueEnd]
|
||||||
|
|
||||||
|
//PublicKeyScript
|
||||||
|
pksStart := valueEnd + 2 // +2 to ignore OP_2DROP and OP_DROP
|
||||||
|
pubkeyscript = script[pksStart:] //Remainder is always pubkeyscript
|
||||||
|
|
||||||
|
return name, claimid, value, pubkeyscript, err
|
||||||
|
}
|
||||||
|
|
|
@ -4,11 +4,12 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
|
||||||
"github.com/cockroachdb/errors"
|
|
||||||
"github.com/lbryio/lbry.go/v3/schema/address"
|
"github.com/lbryio/lbry.go/v3/schema/address"
|
||||||
"github.com/lbryio/lbry.go/v3/schema/keys"
|
"github.com/lbryio/lbry.go/v3/schema/keys"
|
||||||
|
|
||||||
"github.com/lbryio/lbcd/btcec"
|
"github.com/lbryio/lbcd/btcec"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Sign(privKey btcec.PrivateKey, channel Helper, claim Helper, k string) (*keys.Signature, error) {
|
func Sign(privKey btcec.PrivateKey, channel Helper, claim Helper, k string) (*keys.Signature, error) {
|
||||||
|
@ -23,7 +24,6 @@ func Sign(privKey btcec.PrivateKey, channel Helper, claim Helper, k string) (*ke
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Helper) sign(privKey btcec.PrivateKey, channel Helper, firstInputTxID string) (*keys.Signature, error) {
|
func (c *Helper) sign(privKey btcec.PrivateKey, channel Helper, firstInputTxID string) (*keys.Signature, error) {
|
||||||
|
|
||||||
txidBytes, err := hex.DecodeString(firstInputTxID)
|
txidBytes, err := hex.DecodeString(firstInputTxID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
|
|
|
@ -44,10 +44,11 @@ func (c *Helper) ValidateAddresses(blockchainName string) error {
|
||||||
fee := c.GetStream().GetFee()
|
fee := c.GetStream().GetFee()
|
||||||
if fee != nil {
|
if fee != nil {
|
||||||
return validateAddress(fee.GetAddress(), blockchainName)
|
return validateAddress(fee.GetAddress(), blockchainName)
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
} else if c.Claim.GetChannel() != nil {
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Claim.GetChannel() != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue