uncommitted stuff

This commit is contained in:
Alex Grintsvayg 2021-10-20 12:23:55 -04:00
parent 0945466653
commit 430fc530f2
No known key found for this signature in database
GPG key ID: AEB3F089F86A22B5
8 changed files with 672 additions and 15 deletions

149
electrum/client.go Normal file
View 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
View 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
View 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
View file

@ -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/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 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/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=
@ -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/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/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.22.100-beta/go.mod h1:u8SaFX4xdGMMR5xasBGfgApC8pvD4rnK2OujZnrq5gs=
github.com/lbryio/lbcd v0.22.101-beta h1:umLIxhyRwPdi91vtsDfgW85viK0AV8RPtIF9zQYtXw0=

View file

@ -3,7 +3,7 @@ package lbrycrd_test
import (
"testing"
"github.com/lbryio/lbry.go/v2/lbrycrd"
"github.com/lbryio/lbry.go/v3/lbrycrd"
)
var claimIdTests = []struct {

View file

@ -1,12 +1,13 @@
package lbrycrd
import (
"encoding/binary"
"encoding/hex"
"github.com/lbryio/lbry.go/v2/extras/errors"
"github.com/lbryio/lbcd/txscript"
"github.com/lbryio/lbcutil"
"github.com/cockroachdb/errors"
)
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)
if err != nil {
return nil, errors.Err(err)
return nil, errors.WithStack(err)
}
bytes, err := hex.DecodeString(claimid)
if err != nil {
return nil, errors.Err(err)
return nil, errors.WithStack(err)
}
return txscript.NewScriptBuilder().
@ -38,7 +39,7 @@ func GetClaimNamePayoutScript(name string, value []byte, address lbcutil.Address
pkscript, err := txscript.PayToAddrScript(address)
if err != nil {
return nil, errors.Err(err)
return nil, errors.WithStack(err)
}
return txscript.NewScriptBuilder().
@ -56,12 +57,12 @@ func GetUpdateClaimPayoutScript(name, claimid string, value []byte, address lbcu
pkscript, err := txscript.PayToAddrScript(address)
if err != nil {
return nil, errors.Err(err)
return nil, errors.WithStack(err)
}
bytes, err := hex.DecodeString(claimid)
if err != nil {
return nil, errors.Err(err)
return nil, errors.WithStack(err)
}
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
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
}

View file

@ -4,11 +4,12 @@ import (
"crypto/sha256"
"encoding/hex"
"github.com/cockroachdb/errors"
"github.com/lbryio/lbry.go/v3/schema/address"
"github.com/lbryio/lbry.go/v3/schema/keys"
"github.com/lbryio/lbcd/btcec"
"github.com/cockroachdb/errors"
)
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) {
txidBytes, err := hex.DecodeString(firstInputTxID)
if err != nil {
return nil, errors.WithStack(err)

View file

@ -44,10 +44,11 @@ func (c *Helper) ValidateAddresses(blockchainName string) error {
fee := c.GetStream().GetFee()
if fee != nil {
return validateAddress(fee.GetAddress(), blockchainName)
} else {
return nil
}
} else if c.Claim.GetChannel() != nil {
return nil
}
if c.Claim.GetChannel() != nil {
return nil
}
}