Add claim management for lbrycrd client #66
7 changed files with 374 additions and 25 deletions
2
go.mod
2
go.mod
|
@ -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
2
go.sum
|
@ -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
39
lbrycrd/channel.go
Normal 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
86
lbrycrd/claim.go
Normal 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
|
||||
|
||||
}
|
|
@ -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"
|
||||
)
|
||||
|
@ -114,30 +119,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")
|
||||
|
@ -179,3 +160,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
38
lbrycrd/finder.go
Normal 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
76
lbrycrd/script.go
Normal 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()
|
||||
}
|
Loading…
Reference in a new issue