Merge pull request #66 from lbryio/claims

Add claim management for lbrycrd client
This commit is contained in:
Mark 2019-08-27 21:05:07 -04:00 committed by GitHub
commit 564595cfc3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 374 additions and 25 deletions

2
go.mod
View file

@ -18,7 +18,7 @@ require (
github.com/gorilla/websocket v1.2.0 // indirect
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/lbryio/errors.go v0.0.0-20180223142025-ad03d3cc6a5c
github.com/lbryio/lbryschema.go v0.0.0-20190428231007-c54836bca002
github.com/lbryio/lbryschema.go v0.0.0-20190602173230-6d2f69a36f46
github.com/lbryio/ozzo-validation v0.0.0-20170323141101-d1008ad1fd04
github.com/lbryio/types v0.0.0-20190422033210-321fb2abda9c
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect

2
go.sum
View file

@ -70,6 +70,8 @@ github.com/lbryio/errors.go v0.0.0-20180223142025-ad03d3cc6a5c h1:BhdcWGsuKif/Xo
github.com/lbryio/errors.go v0.0.0-20180223142025-ad03d3cc6a5c/go.mod h1:muH7wpUqE8hRA3OrYYosw9+Sl681BF9cwcjzE+OCNK8=
github.com/lbryio/lbryschema.go v0.0.0-20190428231007-c54836bca002 h1:urfYK5ElpUrAv90auPLldoVC60LwiGAcY0OE6HJB9KI=
github.com/lbryio/lbryschema.go v0.0.0-20190428231007-c54836bca002/go.mod h1:dAzPCBj3CKKWBGYBZxK6tKBP5SCgY2tqd9SnQd/OyKo=
github.com/lbryio/lbryschema.go v0.0.0-20190602173230-6d2f69a36f46 h1:LemfR+rMxhf7nnOrzy2HqS7Me7SZ5gEwOcNFzKC8ySQ=
github.com/lbryio/lbryschema.go v0.0.0-20190602173230-6d2f69a36f46/go.mod h1:dAzPCBj3CKKWBGYBZxK6tKBP5SCgY2tqd9SnQd/OyKo=
github.com/lbryio/ozzo-validation v0.0.0-20170323141101-d1008ad1fd04 h1:Nze+C2HbeKvhjI/kVn+9Poj/UuEW5sOQxcsxqO7L3GI=
github.com/lbryio/ozzo-validation v0.0.0-20170323141101-d1008ad1fd04/go.mod h1:fbG/dzobG8r95KzMwckXiLMHfFjZaBRQqC9hPs2XAQ4=
github.com/lbryio/types v0.0.0-20190422033210-321fb2abda9c h1:m3O7561xBQ00lfUVayW4c6SnpVbUDQtPUwGcGYSUYQA=

39
lbrycrd/channel.go Normal file
View file

@ -0,0 +1,39 @@
package lbrycrd
import (
"github.com/btcsuite/btcd/btcec"
"github.com/lbryio/lbry.go/extras/errors"
c "github.com/lbryio/lbryschema.go/claim"
pb "github.com/lbryio/types/v2/go"
)
func NewChannel() (*c.ClaimHelper, *btcec.PrivateKey, error) {
claimChannel := new(pb.Claim_Channel)
channel := new(pb.Channel)
claimChannel.Channel = channel
pbClaim := new(pb.Claim)
pbClaim.Type = claimChannel
privateKey, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
return nil, nil, errors.Err(err)
}
pubkeyBytes, err := c.PublicKeyToDER(privateKey.PubKey())
if err != nil {
return nil, nil, errors.Err(err)
}
helper := c.ClaimHelper{Claim: pbClaim}
helper.Version = c.NoSig
helper.GetChannel().PublicKey = pubkeyBytes
helper.Tags = []string{}
coverSrc := new(pb.Source)
helper.GetChannel().Cover = coverSrc
helper.Languages = []*pb.Language{}
thumbnailSrc := new(pb.Source)
helper.Thumbnail = thumbnailSrc
helper.Locations = []*pb.Location{}
return &helper, privateKey, nil
}

86
lbrycrd/claim.go Normal file
View file

@ -0,0 +1,86 @@
package lbrycrd
import (
"encoding/hex"
"github.com/lbryio/lbry.go/extras/errors"
c "github.com/lbryio/lbryschema.go/claim"
pb "github.com/lbryio/types/v2/go"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
)
func NewImageStreamClaim() (*c.ClaimHelper, error) {
streamClaim := new(pb.Claim_Stream)
stream := new(pb.Stream)
image := new(pb.Stream_Image)
image.Image = new(pb.Image)
stream.Type = image
streamClaim.Stream = stream
pbClaim := new(pb.Claim)
pbClaim.Type = streamClaim
helper := c.ClaimHelper{Claim: pbClaim}
return &helper, nil
}
func NewVideoStreamClaim() (*c.ClaimHelper, error) {
streamClaim := new(pb.Claim_Stream)
stream := new(pb.Stream)
video := new(pb.Stream_Video)
video.Video = new(pb.Video)
stream.Type = video
streamClaim.Stream = stream
pbClaim := new(pb.Claim)
pbClaim.Type = streamClaim
helper := c.ClaimHelper{Claim: pbClaim}
return &helper, nil
}
func NewStreamClaim(title, description string) (*c.ClaimHelper, error) {
streamClaim := new(pb.Claim_Stream)
stream := new(pb.Stream)
streamClaim.Stream = stream
pbClaim := new(pb.Claim)
pbClaim.Type = streamClaim
helper := c.ClaimHelper{Claim: pbClaim}
helper.Title = title
helper.Description = description
return &helper, nil
}
func SignClaim(rawTx *wire.MsgTx, privKey btcec.PrivateKey, claim, channel *c.ClaimHelper, channelClaimID string) error {
claimIDHexBytes, err := hex.DecodeString(channelClaimID)
if err != nil {
return errors.Err(err)
}
claim.Version = c.WithSig
claim.ClaimID = rev(claimIDHexBytes)
hash, err := c.GetOutpointHash(rawTx.TxIn[0].PreviousOutPoint.Hash.String(), rawTx.TxIn[0].PreviousOutPoint.Index)
if err != nil {
return err
}
sig, err := c.Sign(privKey, *channel, *claim, hash)
if err != nil {
return err
}
lbrySig, err := sig.LBRYSDKEncode()
if err != nil {
return err
}
claim.Signature = lbrySig
return nil
}

View file

@ -1,15 +1,20 @@
package lbrycrd
import (
"encoding/hex"
"net/url"
"os"
"strconv"
"github.com/lbryio/lbry.go/extras/errors"
c "github.com/lbryio/lbryschema.go/claim"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/go-ini/ini"
)
@ -115,30 +120,6 @@ func (c *Client) SimpleSend(toAddress string, amount float64) (*chainhash.Hash,
return hash, nil
}
//func (c *Client) SendWithSplit(toAddress string, amount float64, numUTXOs int) (*chainhash.Hash, error) {
// decodedAddress, err := DecodeAddress(toAddress, &MainNetParams)
// if err != nil {
// return nil, errors.Wrap(err, 0)
// }
//
// amountPerAddress, err := btcutil.NewAmount(amount / float64(numUTXOs))
// if err != nil {
// return nil, errors.Wrap(err, 0)
// }
//
// amounts := map[btcutil.Address]btcutil.Amount{}
// for i := 0; i < numUTXOs; i++ {
// addr := decodedAddress // to give it a new address, so
// amounts[addr] = amountPerAddress
// }
//
// hash, err := c.Client.SendManyMinConf("", amounts, 0)
// if err != nil && err.Error() == "-6: Insufficient funds" {
// err = errors.Wrap(errInsufficientFunds, 0)
// }
// return hash, errors.Wrap(err, 0)
//}
func getLbrycrdURLFromConfFile() (string, error) {
if os.Getenv("HOME") == "" {
return "", errors.Err("no $HOME var found")
@ -180,3 +161,130 @@ func getLbrycrdURLFromConfFile() (string, error) {
return "rpc://" + userpass + host + ":" + port, nil
}
func (c *Client) CreateBaseRawTx(inputs []btcjson.TransactionInput, change float64) (*wire.MsgTx, error) {
addresses := make(map[btcutil.Address]btcutil.Amount)
changeAddress, err := c.GetNewAddress("")
if err != nil {
return nil, errors.Err(err)
}
changeAmount, err := btcutil.NewAmount(change)
if err != nil {
return nil, errors.Err(err)
}
addresses[changeAddress] = changeAmount
lockTime := int64(0)
return c.CreateRawTransaction(inputs, addresses, &lockTime)
}
func (c *Client) GetEmptyTx(totalOutputSpend float64) (*wire.MsgTx, error) {
totalFees := 0.1
unspentResults, err := c.ListUnspentMin(1)
if err != nil {
return nil, errors.Err(err)
}
finder := newOutputFinder(unspentResults)
outputs, err := finder.nextBatch(totalOutputSpend + totalFees)
if err != nil {
return nil, err
}
if len(outputs) == 0 {
return nil, errors.Err("Not enough spendable outputs to create transaction")
}
inputs := make([]btcjson.TransactionInput, len(outputs))
var totalInputSpend float64
for i, output := range outputs {
inputs[i] = btcjson.TransactionInput{Txid: output.TxID, Vout: output.Vout}
totalInputSpend = totalInputSpend + output.Amount
}
change := totalInputSpend - totalOutputSpend - totalFees
return c.CreateBaseRawTx(inputs, change)
}
func (c *Client) SignTxAndSend(rawTx *wire.MsgTx) (*chainhash.Hash, error) {
signedTx, allInputsSigned, err := c.SignRawTransaction(rawTx)
if err != nil {
return nil, errors.Err(err)
}
if !allInputsSigned {
return nil, errors.Err("Not all inputs for the tx could be signed!")
}
return c.SendRawTransaction(signedTx, false)
}
type ScriptType int
const (
ClaimName ScriptType = iota
ClaimUpdate
ClaimSupport
)
func (c *Client) AddStakeToTx(rawTx *wire.MsgTx, claim *c.ClaimHelper, name string, claimAmount float64, scriptType ScriptType) error {
address, err := c.GetNewAddress("")
if err != nil {
return errors.Err(err)
}
amount, err := btcutil.NewAmount(claimAmount)
if err != nil {
return errors.Err(err)
}
value, err := claim.CompileValue()
if err != nil {
return errors.Err(err)
}
var claimID string
if len(claim.ClaimID) > 0 {
claimID = hex.EncodeToString(rev(claim.ClaimID))
}
var script []byte
switch scriptType {
case ClaimName:
script, err = getClaimNamePayoutScript(name, value, address)
if err != nil {
return errors.Err(err)
}
case ClaimUpdate:
script, err = getUpdateClaimPayoutScript(name, claimID, value, address)
if err != nil {
return errors.Err(err)
}
case ClaimSupport:
script, err = getUpdateClaimPayoutScript(name, claimID, value, address)
if err != nil {
return errors.Err(err)
}
}
rawTx.AddTxOut(wire.NewTxOut(int64(amount), script))
return nil
}
func (c *Client) CreateChannel(name string, amount float64) (*c.ClaimHelper, *btcec.PrivateKey, error) {
channel, key, err := NewChannel()
if err != nil {
return nil, nil, err
}
rawTx, err := c.GetEmptyTx(amount)
if err != nil {
return nil, nil, err
}
err = c.AddStakeToTx(rawTx, channel, name, amount, ClaimName)
if err != nil {
return nil, nil, err
}
_, err = c.SignTxAndSend(rawTx)
if err != nil {
return nil, nil, err
}
return channel, key, nil
}

38
lbrycrd/finder.go Normal file
View file

@ -0,0 +1,38 @@
package lbrycrd
import (
"github.com/lbryio/lbry.go/extras/errors"
"github.com/btcsuite/btcd/btcjson"
)
type outputFinder struct {
unspent []btcjson.ListUnspentResult
lastChecked int
}
func newOutputFinder(unspentResults []btcjson.ListUnspentResult) *outputFinder {
return &outputFinder{unspent: unspentResults, lastChecked: -1}
}
func (f *outputFinder) nextBatch(minAmount float64) ([]btcjson.ListUnspentResult, error) {
var batch []btcjson.ListUnspentResult
var lbcBatched float64
for i, unspent := range f.unspent {
if i > f.lastChecked {
if unspent.Spendable {
batch = append(batch, unspent)
lbcBatched = lbcBatched + unspent.Amount
}
}
if lbcBatched >= minAmount {
f.lastChecked = i
break
}
if i == len(f.unspent)-1 {
return nil, errors.Err("Not enough unspent outputs to spend %d on supports.", minAmount)
}
}
return batch, nil
}

76
lbrycrd/script.go Normal file
View file

@ -0,0 +1,76 @@
package lbrycrd
import (
"encoding/hex"
"github.com/lbryio/lbry.go/extras/errors"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcutil"
)
func getClaimSupportPayoutScript(name, claimid string, address btcutil.Address) ([]byte, error) {
//OP_SUPPORT_CLAIM <name> <claimid> OP_2DROP OP_DROP OP_DUP OP_HASH160 <address> OP_EQUALVERIFY OP_CHECKSIG
pkscript, err := txscript.PayToAddrScript(address)
if err != nil {
return nil, errors.Err(err)
}
bytes, err := hex.DecodeString(claimid)
if err != nil {
return nil, errors.Err(err)
}
return txscript.NewScriptBuilder().
AddOp(txscript.OP_NOP7). //OP_SUPPORT_CLAIM
AddData([]byte(name)). //<name>
AddData(rev(bytes)). //<claimid>
AddOp(txscript.OP_2DROP). //OP_2DROP
AddOp(txscript.OP_DROP). //OP_DROP
AddOps(pkscript). //OP_DUP OP_HASH160 <address> OP_EQUALVERIFY OP_CHECKSIG
Script()
}
func getClaimNamePayoutScript(name string, value []byte, address btcutil.Address) ([]byte, error) {
//OP_CLAIM_NAME <name> <value> OP_2DROP OP_DROP OP_DUP OP_HASH160 <address> OP_EQUALVERIFY OP_CHECKSIG
pkscript, err := txscript.PayToAddrScript(address)
if err != nil {
return nil, errors.Err(err)
}
return txscript.NewScriptBuilder().
AddOp(txscript.OP_NOP6). //OP_CLAIMNAME
AddData([]byte(name)). //<name>
AddData(value). //<value>
AddOp(txscript.OP_2DROP). //OP_2DROP
AddOp(txscript.OP_DROP). //OP_DROP
AddOps(pkscript). //OP_DUP OP_HASH160 <address> OP_EQUALVERIFY OP_CHECKSIG
Script()
}
func getUpdateClaimPayoutScript(name, claimid string, value []byte, address btcutil.Address) ([]byte, error) {
//OP_UPDATE_CLAIM <name> <claimid> <value> OP_2DROP OP_DROP OP_DUP OP_HASH160 <address> OP_EQUALVERIFY OP_CHECKSIG
pkscript, err := txscript.PayToAddrScript(address)
if err != nil {
return nil, errors.Err(err)
}
bytes, err := hex.DecodeString(claimid)
if err != nil {
return nil, errors.Err(err)
}
return txscript.NewScriptBuilder().
AddOp(txscript.OP_NOP8). //OP_UPDATE_CLAIM
AddData([]byte(name)). //<name>
AddData(rev(bytes)). //<claimid>
AddData(value). //<value>
AddOp(txscript.OP_2DROP). //OP_2DROP
AddOp(txscript.OP_DROP). //OP_DROP
AddOps(pkscript). //OP_DUP OP_HASH160 <address> OP_EQUALVERIFY OP_CHECKSIG
Script()
}