Compare commits

..

No commits in common. "master" and "lbrycrd_go" have entirely different histories.

58 changed files with 519 additions and 3921 deletions

View file

@ -1,8 +1,8 @@
os: linux os: linux
dist: bionic dist: xenial
language: go language: go
go: go:
- 1.17.x - 1.11.x
env: env:
global: global:

View file

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2016-2020 LBRY Inc Copyright (c) 2016-2019 LBRY Inc
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish,distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish,distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View file

@ -17,7 +17,7 @@ const (
// TODO: all these constants should be defaults, and should be used to set values in the standard Config. then the code should use values in the config // TODO: all these constants should be defaults, and should be used to set values in the standard Config. then the code should use values in the config
// TODO: alternatively, have a global Config for constants. at least that way tests can modify the values // TODO: alternatively, have a global Config for constants. at least that way tests can modify the values
alpha = 5 // this is the constant alpha in the spec alpha = 3 // this is the constant alpha in the spec
bucketSize = 8 // this is the constant k in the spec bucketSize = 8 // this is the constant k in the spec
nodeIDLength = bits.NumBytes // bytes. this is the constant B in the spec nodeIDLength = bits.NumBytes // bytes. this is the constant B in the spec
messageIDLength = 20 // bytes. messageIDLength = 20 // bytes.
@ -65,10 +65,9 @@ func NewStandardConfig() *Config {
return &Config{ return &Config{
Address: "0.0.0.0:" + strconv.Itoa(DefaultPort), Address: "0.0.0.0:" + strconv.Itoa(DefaultPort),
SeedNodes: []string{ SeedNodes: []string{
"lbrynet1.lbry.com:4444", "lbrynet1.lbry.io:4444",
"lbrynet2.lbry.com:4444", "lbrynet2.lbry.io:4444",
"lbrynet3.lbry.com:4444", "lbrynet3.lbry.io:4444",
"lbrynet4.lbry.com:4444",
}, },
PeerProtocolPort: DefaultPeerPort, PeerProtocolPort: DefaultPeerPort,
ReannounceTime: DefaultReannounceTime, ReannounceTime: DefaultReannounceTime,

View file

@ -2,7 +2,6 @@ package dht
import ( import (
"bytes" "bytes"
"encoding/json"
"net" "net"
"sort" "sort"
"strconv" "strconv"
@ -42,20 +41,6 @@ func (c Contact) String() string {
return str return str
} }
func (c Contact) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
ID string
IP string
Port int
PeerPort int
}{
ID: c.ID.Hex(),
IP: c.IP.String(),
Port: c.Port,
PeerPort: c.PeerPort,
})
}
// MarshalCompact returns a compact byteslice representation of the contact // MarshalCompact returns a compact byteslice representation of the contact
// NOTE: The compact representation always uses the tcp PeerPort, not the udp Port. This is dumb, but that's how the python daemon does it // NOTE: The compact representation always uses the tcp PeerPort, not the udp Port. This is dumb, but that's how the python daemon does it
func (c Contact) MarshalCompact() ([]byte, error) { func (c Contact) MarshalCompact() ([]byte, error) {

View file

@ -40,7 +40,6 @@ const (
headerPayloadField = "3" headerPayloadField = "3"
headerArgsField = "4" headerArgsField = "4"
contactsField = "contacts" contactsField = "contacts"
pageField = "p"
tokenField = "token" tokenField = "token"
protocolVersionField = "protocolVersion" protocolVersionField = "protocolVersion"
) )
@ -271,7 +270,6 @@ type Response struct {
FindValueKey string FindValueKey string
Token string Token string
ProtocolVersion int ProtocolVersion int
Page uint8
} }
func (r Response) argsDebug() string { func (r Response) argsDebug() string {
@ -392,34 +390,27 @@ func (r *Response) UnmarshalBencode(b []byte) error {
if contacts, ok := rawData[contactsField]; ok { if contacts, ok := rawData[contactsField]; ok {
err = bencode.DecodeBytes(contacts, &r.Contacts) err = bencode.DecodeBytes(contacts, &r.Contacts)
delete(rawData, contactsField) // so it doesnt mess up findValue key finding below
if err != nil { if err != nil {
return err return err
} }
} } else {
if page, ok := rawData[pageField]; ok { for k, v := range rawData {
err = bencode.DecodeBytes(page, &r.Page) r.FindValueKey = k
delete(rawData, pageField) // so it doesnt mess up findValue key finding below var compactContacts [][]byte
if err != nil { err = bencode.DecodeBytes(v, &compactContacts)
return err
}
}
for k, v := range rawData {
r.FindValueKey = k
var compactContacts [][]byte
err = bencode.DecodeBytes(v, &compactContacts)
if err != nil {
return err
}
for _, compact := range compactContacts {
var c Contact
err = c.UnmarshalCompact(compact)
if err != nil { if err != nil {
return err return err
} }
r.Contacts = append(r.Contacts, c) for _, compact := range compactContacts {
var c Contact
err = c.UnmarshalCompact(compact)
if err != nil {
return err
}
r.Contacts = append(r.Contacts, c)
}
break
} }
break
} }
return nil return nil

View file

@ -10,7 +10,7 @@ import (
"github.com/lbryio/lbry.go/v2/extras/stop" "github.com/lbryio/lbry.go/v2/extras/stop"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"go.uber.org/atomic" "github.com/uber-go/atomic"
) )
// TODO: iterativeFindValue may be stopping early. if it gets a response with one peer, it should keep going because other nodes may know about more peers that have that blob // TODO: iterativeFindValue may be stopping early. if it gets a response with one peer, it should keep going because other nodes may know about more peers that have that blob

View file

@ -102,7 +102,6 @@ type RpcIterativeFindValueArgs struct {
type RpcIterativeFindValueResult struct { type RpcIterativeFindValueResult struct {
Contacts []Contact Contacts []Contact
FoundValue bool FoundValue bool
Values []Contact
} }
func (rpc *rpcReceiver) IterativeFindValue(r *http.Request, args *RpcIterativeFindValueArgs, result *RpcIterativeFindValueResult) error { func (rpc *rpcReceiver) IterativeFindValue(r *http.Request, args *RpcIterativeFindValueArgs, result *RpcIterativeFindValueResult) error {
@ -110,19 +109,12 @@ func (rpc *rpcReceiver) IterativeFindValue(r *http.Request, args *RpcIterativeFi
if err != nil { if err != nil {
return err return err
} }
foundContacts, found, err := FindContacts(rpc.dht.node, key, true, nil) foundContacts, found, err := FindContacts(rpc.dht.node, key, false, nil)
if err != nil { if err != nil {
return err return err
} }
result.Contacts = foundContacts result.Contacts = foundContacts
result.FoundValue = found result.FoundValue = found
if found {
for _, contact := range foundContacts {
if contact.PeerPort > 0 {
result.Values = append(result.Values, contact)
}
}
}
return nil return nil
} }

View file

@ -17,12 +17,6 @@ import (
// ResponseHeaders are returned with each response // ResponseHeaders are returned with each response
var ResponseHeaders map[string]string var ResponseHeaders map[string]string
// CorsDomains Allowed domains for CORS Policy
var CorsDomains []string
// CorsAllowLocalhost if true localhost connections are always allowed
var CorsAllowLocalhost bool
// Log allows logging of events and errors // Log allows logging of events and errors
var Log = func(*http.Request, *Response, error) {} var Log = func(*http.Request, *Response, error) {}
@ -83,32 +77,6 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set(key, value) w.Header().Set(key, value)
} }
} }
origin := r.Header.Get("origin")
for _, d := range CorsDomains {
if d == origin {
w.Header().Set("Access-Control-Allow-Origin", d)
vary := w.Header().Get("Vary")
if vary != "*" {
if vary != "" {
vary += ", "
}
vary += "Origin"
}
w.Header().Set("Vary", vary)
}
}
if CorsAllowLocalhost && strings.HasPrefix(origin, "http://localhost:") {
w.Header().Set("Access-Control-Allow-Origin", origin)
vary := w.Header().Get("Vary")
if vary != "*" {
if vary != "" {
vary += ", "
}
vary += "Origin"
}
w.Header().Set("Vary", vary)
}
// Stop here if its a preflighted OPTIONS request // Stop here if its a preflighted OPTIONS request
if r.Method == "OPTIONS" { if r.Method == "OPTIONS" {
@ -121,9 +89,6 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if rsp.Error != nil { if rsp.Error != nil {
ogErr := errors.Unwrap(rsp.Error) ogErr := errors.Unwrap(rsp.Error)
if statusError, ok := ogErr.(StatusError); ok { if statusError, ok := ogErr.(StatusError); ok {
if statusError.Status == 0 {
statusError.Status = http.StatusInternalServerError
}
rsp.Status = statusError.Status rsp.Status = statusError.Status
} else { } else {
rsp.Status = http.StatusInternalServerError rsp.Status = http.StatusInternalServerError

View file

@ -15,29 +15,16 @@ import (
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/ybbus/jsonrpc/v2" "github.com/ybbus/jsonrpc"
) )
const DefaultPort = 5279 const DefaultPort = 5279
const (
ErrorWalletNotLoaded = "WalletNotLoadedError"
ErrorWalletAlreadyLoaded = "WalletAlreadyLoadedError"
ErrorWalletNotFound = "WalletNotFoundError"
ErrorWalletAlreadyExists = "WalletAlreadyExistsError"
)
type Client struct { type Client struct {
conn jsonrpc.RPCClient conn jsonrpc.RPCClient
address string address string
} }
type Error struct {
Code int
Name string
Message string
}
func NewClient(address string) *Client { func NewClient(address string) *Client {
d := Client{} d := Client{}
@ -83,15 +70,6 @@ func Decode(data interface{}, targetStruct interface{}) error {
return nil return nil
} }
// WrapError adds error metadata from JSONRPC error response for clients to access
func WrapError(rpcError *jsonrpc.RPCError) Error {
e := Error{Code: rpcError.Code, Message: rpcError.Message}
if d, ok := rpcError.Data.(map[string]interface{}); ok {
e.Name = d["name"].(string)
}
return e
}
func decodeNumber(data interface{}) (decimal.Decimal, error) { func decodeNumber(data interface{}) (decimal.Decimal, error) {
var number string var number string
@ -128,10 +106,6 @@ func debugParams(params map[string]interface{}) string {
return strings.Join(s, " ") return strings.Join(s, " ")
} }
func (e Error) Error() string {
return fmt.Sprintf("Error in daemon: %s", e.Message)
}
func (d *Client) callNoDecode(command string, params map[string]interface{}) (interface{}, error) { func (d *Client) callNoDecode(command string, params map[string]interface{}) (interface{}, error) {
log.Debugln("jsonrpc: " + command + " " + debugParams(params)) log.Debugln("jsonrpc: " + command + " " + debugParams(params))
r, err := d.conn.Call(command, params) r, err := d.conn.Call(command, params)
@ -140,7 +114,7 @@ func (d *Client) callNoDecode(command string, params map[string]interface{}) (in
} }
if r.Error != nil { if r.Error != nil {
return nil, WrapError(r.Error) return nil, errors.Err("Error in daemon: " + r.Error.Message)
} }
return r.Result, nil return r.Result, nil
@ -164,21 +138,6 @@ func (d *Client) SetRPCTimeout(timeout time.Duration) {
// NEW SDK // NEW SDK
//============================================ //============================================
func (d *Client) AccountSend(accountID *string, amount, toAddress string) (*TransactionSummary, error) {
response := new(TransactionSummary)
args := struct {
AccountID *string `json:"account_id"`
Amount string `json:"amount"`
Addresses string `json:"addresses"`
}{
AccountID: accountID,
Amount: amount,
Addresses: toAddress,
}
structs.DefaultTagName = "json"
return response, d.call(response, "account_send", structs.Map(args))
}
func (d *Client) AccountList(page uint64, pageSize uint64) (*AccountListResponse, error) { func (d *Client) AccountList(page uint64, pageSize uint64) (*AccountListResponse, error) {
response := new(AccountListResponse) response := new(AccountListResponse)
return response, d.call(response, "account_list", map[string]interface{}{ return response, d.call(response, "account_list", map[string]interface{}{
@ -261,13 +220,6 @@ func (d *Client) AddressUnused(account *string) (*AddressUnusedResponse, error)
}) })
} }
func (d *Client) TransactionShow(txid string) (*TransactionSummary, error) {
response := new(TransactionSummary)
return response, d.call(response, "transaction_show", map[string]interface{}{
"txid": txid,
})
}
func (d *Client) ChannelList(account *string, page uint64, pageSize uint64, wid *string) (*ChannelListResponse, error) { func (d *Client) ChannelList(account *string, page uint64, pageSize uint64, wid *string) (*ChannelListResponse, error) {
if page == 0 { if page == 0 {
return nil, errors.Err("pages start from 1") return nil, errors.Err("pages start from 1")
@ -318,7 +270,6 @@ type ChannelCreateOptions struct {
CoverURL *string `json:"cover_url,omitempty"` CoverURL *string `json:"cover_url,omitempty"`
Featured []string `json:"featured,omitempty"` Featured []string `json:"featured,omitempty"`
AccountID *string `json:"account_id,omitempty"` AccountID *string `json:"account_id,omitempty"`
FundingAccountIDs []string `json:"funding_account_ids,omitempty"`
} }
func (d *Client) ChannelCreate(name string, bid float64, options ChannelCreateOptions) (*TransactionSummary, error) { func (d *Client) ChannelCreate(name string, bid float64, options ChannelCreateOptions) (*TransactionSummary, error) {
@ -522,11 +473,10 @@ func (d *Client) Status() (*StatusResponse, error) {
return response, d.call(response, "status", map[string]interface{}{}) return response, d.call(response, "status", map[string]interface{}{})
} }
func (d *Client) TransactionList(account *string, wallet *string, page uint64, pageSize uint64) (*TransactionListResponse, error) { func (d *Client) TransactionList(account *string, page uint64, pageSize uint64) (*TransactionListResponse, error) {
response := new(TransactionListResponse) response := new(TransactionListResponse)
return response, d.call(response, "transaction_list", map[string]interface{}{ return response, d.call(response, "transaction_list", map[string]interface{}{
"account_id": account, "account_id": account,
"wallet_id": wallet,
"page": page, "page": page,
"page_size": pageSize, "page_size": pageSize,
}) })
@ -578,35 +528,24 @@ func (d *Client) Resolve(urls string) (*ResolveResponse, error) {
}) })
} }
type ClaimSearchArgs struct { func (d *Client) ClaimSearch(claimName, claimID, txid *string, nout *uint, page uint64, pageSize uint64) (*ClaimSearchResponse, error) {
ClaimID *string `json:"claim_id,omitempty"`
TXID *string `json:"txid,omitempty"`
Nout *uint `json:"nout,omitempty"`
Name *string `json:"name,omitempty"`
ClaimType []string `json:"claim_type,omitempty"`
OrderBy []string `json:"order_by,omitempty"`
LimitClaimsPerChannel *int `json:"limit_claims_per_channel,omitempty"`
HasNoSource *bool `json:"has_no_source,omitempty"`
ReleaseTime string `json:"release_time,omitempty"`
ChannelIDs []string `json:"channel_ids,omitempty"`
NoTotals *bool `json:"no_totals,omitempty"`
IncludeProtobuf *bool `json:"include_protobuf,omitempty"`
AnyTags []string `json:"any_tags,omitempty"`
Page uint64 `json:"page"`
PageSize uint64 `json:"page_size"`
}
func (d *Client) ClaimSearch(args ClaimSearchArgs) (*ClaimSearchResponse, error) {
response := new(ClaimSearchResponse) response := new(ClaimSearchResponse)
if args.NoTotals == nil { args := struct {
nototals := true ClaimID *string `json:"claim_id,omitempty"`
args.NoTotals = &nototals TXID *string `json:"txid,omitempty"`
} Nout *uint `json:"nout,omitempty"`
Name *string `json:"name,omitempty"`
if args.IncludeProtobuf == nil { IncludeProtobuf bool `json:"include_protobuf"`
include := true Page uint64 `json:"page"`
args.IncludeProtobuf = &include PageSize uint64 `json:"page_size"`
}{
ClaimID: claimID,
TXID: txid,
Nout: nout,
Name: claimName,
IncludeProtobuf: true,
Page: page,
PageSize: pageSize,
} }
structs.DefaultTagName = "json" structs.DefaultTagName = "json"
return response, d.call(response, "claim_search", structs.Map(args)) return response, d.call(response, "claim_search", structs.Map(args))
@ -686,36 +625,6 @@ func (d *Client) SupportAbandon(claimID *string, txid *string, nout *uint, keep
return response, d.call(response, "support_abandon", structs.Map(args)) return response, d.call(response, "support_abandon", structs.Map(args))
} }
func (d *Client) TxoSpend(txoType, claimID, txid, channelID, name, accountID *string) (*[]TransactionSummary, error) {
if txoType == nil && claimID == nil && txid == nil && channelID == nil && name == nil {
return nil, errors.Err("either txoType or claimID or channelID or name or txid must be supplied")
}
response := new([]TransactionSummary)
args := struct {
ClaimID *string `json:"claim_id,omitempty"`
ChannelID *string `json:"channel_id,omitempty"`
Name *string `json:"name,omitempty"`
TxID *string `json:"claim_id,omitempty"`
Type *string `json:"type,omitempty"`
AccountID *string `json:"account_id,omitempty"`
Preview bool `json:"preview,omitempty"`
Blocking bool `json:"blocking,omitempty"`
IncludeFullTx bool `json:"include_full_tx,omitempty"`
}{
ClaimID: claimID,
ChannelID: channelID,
Name: name,
Type: txoType,
AccountID: accountID,
TxID: txid,
Blocking: true,
Preview: false,
IncludeFullTx: true,
}
structs.DefaultTagName = "json"
return response, d.call(response, "txo_spend", structs.Map(args))
}
func (d *Client) AccountAdd(accountName string, seed *string, privateKey *string, publicKey *string, singleKey *bool, walletID *string) (*Account, error) { func (d *Client) AccountAdd(accountName string, seed *string, privateKey *string, publicKey *string, singleKey *bool, walletID *string) (*Account, error) {
response := new(Account) response := new(Account)

View file

@ -11,7 +11,6 @@ import (
"time" "time"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
"github.com/stretchr/testify/assert"
"github.com/lbryio/lbry.go/v2/extras/errors" "github.com/lbryio/lbry.go/v2/extras/errors"
@ -55,32 +54,6 @@ func TestClient_AccountFund(t *testing.T) {
prettyPrint(*got) prettyPrint(*got)
} }
func TestClient_AccountSend(t *testing.T) {
d := NewClient("")
accounts, err := d.AccountList(1, 20)
if !assert.NoError(t, err) {
return
}
if !assert.NotEmpty(t, accounts.Items[1].ID) {
return
}
account := (accounts.Items)[1].ID
addressess, err := d.AddressList(&account, nil, 1, 20)
if !assert.NoError(t, err) {
return
}
if !assert.NotEmpty(t, addressess.Items) {
return
}
got, err := d.AccountSend(&account, "0.01", string(addressess.Items[0].Address))
if !assert.NoError(t, err) {
return
}
prettyPrint(*got)
}
func TestClient_AccountList(t *testing.T) { func TestClient_AccountList(t *testing.T) {
d := NewClient("") d := NewClient("")
got, err := d.AccountList(1, 20) got, err := d.AccountList(1, 20)
@ -157,11 +130,11 @@ func TestClient_ChannelCreate(t *testing.T) {
State: util.PtrToString("Ticino"), State: util.PtrToString("Ticino"),
City: util.PtrToString("Lugano"), City: util.PtrToString("Lugano"),
}}, }},
ThumbnailURL: util.PtrToString("https://scrn.storni.info/2022-06-10_17-18-29-409175881.png"), ThumbnailURL: util.PtrToString("https://scrn.storni.info/2019-04-12_15-43-25-001592625.png"),
}, },
Email: util.PtrToString("niko@lbry.com"), Email: util.PtrToString("niko@lbry.com"),
WebsiteURL: util.PtrToString("https://lbry.com"), WebsiteURL: util.PtrToString("https://lbry.com"),
CoverURL: util.PtrToString("https://scrn.storni.info/2022-06-10_17-18-29-409175881.png"), CoverURL: util.PtrToString("https://scrn.storni.info/2019-04-12_15-43-25-001592625.png"),
}) })
if err != nil { if err != nil {
t.Error(err) t.Error(err)
@ -331,7 +304,7 @@ func TestClient_StreamList(t *testing.T) {
func TestClient_TransactionList(t *testing.T) { func TestClient_TransactionList(t *testing.T) {
_ = os.Setenv("BLOCKCHAIN_NAME", "lbrycrd_regtest") _ = os.Setenv("BLOCKCHAIN_NAME", "lbrycrd_regtest")
d := NewClient("") d := NewClient("")
got, err := d.TransactionList(nil, nil, 1, 20) got, err := d.TransactionList(nil, 1, 20)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
@ -395,109 +368,9 @@ func TestClient_SupportTest(t *testing.T) {
prettyPrint(*got4) prettyPrint(*got4)
} }
func TestClient_TxoSpendTest(t *testing.T) {
_ = os.Setenv("BLOCKCHAIN_NAME", "lbrycrd_regtest")
d := NewClient("")
got, err := d.ChannelCreate("@Test"+fmt.Sprintf("%d", time.Now().Unix()), 13.37, ChannelCreateOptions{
ClaimCreateOptions: ClaimCreateOptions{
Title: util.PtrToString("Mess with the channels"),
Description: util.PtrToString("And you'll get what you deserve"),
Tags: []string{"we", "got", "tags"},
Languages: []string{"en-US"},
Locations: []Location{{
Country: util.PtrToString("CH"),
State: util.PtrToString("Ticino"),
City: util.PtrToString("Lugano"),
}},
ThumbnailURL: util.PtrToString("https://scrn.storni.info/2019-04-12_15-43-25-001592625.png"),
},
Email: util.PtrToString("niko@lbry.com"),
WebsiteURL: util.PtrToString("https://lbry.com"),
CoverURL: util.PtrToString("https://scrn.storni.info/2019-04-12_15-43-25-001592625.png"),
})
if err != nil {
t.Error(err)
return
}
time.Sleep(10 * time.Second)
got2, err := d.SupportCreate(got.Outputs[0].ClaimID, "1.0", util.PtrToBool(true), nil, nil, nil)
if err != nil {
t.Error(err)
return
}
prettyPrint(*got2)
got3, err := d.SupportList(nil, 1, 10)
if err != nil {
t.Error(err)
return
}
found := false
for _, support := range got3.Items {
if support.ClaimID == got.Outputs[0].ClaimID {
found = true
}
}
if !found {
t.Error(errors.Err("support not found"))
return
}
prettyPrint(*got3)
got4, err := d.TxoSpend(util.PtrToString("support"), util.PtrToString(got.Outputs[0].ClaimID), nil, nil, nil, nil)
if err != nil {
t.Error(err)
return
}
prettyPrint(*got4)
time.Sleep(10 * time.Second)
got3, err = d.SupportList(nil, 1, 10)
if err != nil {
t.Error(err)
return
}
found = false
for _, support := range got3.Items {
if support.ClaimID == got.Outputs[0].ClaimID {
found = true
}
}
if found {
t.Error(errors.Err("support found even though it should have been abandoned"))
return
}
prettyPrint(*got3)
got4, err = d.TxoSpend(util.PtrToString("channel"), util.PtrToString(got.Outputs[0].ClaimID), nil, nil, nil, nil)
if err != nil {
t.Error(err)
return
}
prettyPrint(*got4)
time.Sleep(10 * time.Second)
got5, err := d.ClaimList(nil, 1, 50)
if err != nil {
t.Error(err)
return
}
for _, claim := range got5.Claims {
if claim.ClaimID == got.Outputs[0].ClaimID {
t.Error(errors.Err("claim found even though it should have been abandoned"))
return
}
}
prettyPrint(*got5)
}
func TestClient_ClaimSearch(t *testing.T) { func TestClient_ClaimSearch(t *testing.T) {
d := NewClient("") d := NewClient("")
got, err := d.ClaimSearch(ClaimSearchArgs{ got, err := d.ClaimSearch(nil, util.PtrToString(channelID), nil, nil, 1, 20)
ChannelIDs: []string{channelID},
ReleaseTime: ">1633350820",
HasNoSource: util.PtrToBool(true),
OrderBy: []string{"^release_time"},
Page: 1,
PageSize: 20,
})
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
@ -777,15 +650,11 @@ func TestClient_WalletList(t *testing.T) {
d := NewClient("") d := NewClient("")
id := "lbry#wallet#id:" + fmt.Sprintf("%d", rand.Int()) id := "lbry#wallet#id:" + fmt.Sprintf("%d", rand.Int())
_, err := d.WalletList(id, 1, 20) wList, err := d.WalletList(id, 1, 20)
if err == nil { if err == nil {
t.Fatalf("wallet %v was unexpectedly found", id) t.Fatalf("wallet %v was unexpectedly found", id)
} }
derr, ok := err.(Error) if !strings.Contains(err.Error(), fmt.Sprintf("Couldn't find wallet: %v.", id)) {
if !ok {
t.Fatalf("unknown error returned: %s", err)
}
if derr.Name != ErrorWalletNotLoaded {
t.Fatal(err) t.Fatal(err)
} }
@ -794,7 +663,7 @@ func TestClient_WalletList(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
wList, err := d.WalletList(id, 1, 20) wList, err = d.WalletList(id, 1, 20)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -829,17 +698,3 @@ func TestClient_WalletRemoveWalletAdd(t *testing.T) {
t.Fatalf("wallet ID mismatch, expected %q, got %q", wallet.ID, addedWallet.Name) t.Fatalf("wallet ID mismatch, expected %q, got %q", wallet.ID, addedWallet.Name)
} }
} }
func TestClient_TransactionSummary(t *testing.T) {
d := NewClient("https://api.na-backend.odysee.com/api/v1/proxy")
r, err := d.TransactionShow("d104a1616c6af581e2046819de678f370d624e97cf176f95acaec4b183a42db6")
if err != nil {
t.Error(err)
}
if len(r.Outputs) != 2 {
t.Fatal("found wrong transaction")
}
if r.Outputs[0].Amount != "5.0" {
t.Error("found wrong lbc amount for transaction.")
}
}

View file

@ -7,12 +7,11 @@ import (
"net/http" "net/http"
"os" "os"
"reflect" "reflect"
"strings"
"github.com/lbryio/lbry.go/v2/extras/errors" "github.com/lbryio/lbry.go/v2/extras/errors"
"github.com/lbryio/lbry.go/v2/stream" "github.com/lbryio/lbry.go/v2/stream"
schema "github.com/lbryio/lbry.go/v2/schema/stake" schema "github.com/lbryio/lbryschema.go/claim"
lbryschema "github.com/lbryio/types/v2/go" lbryschema "github.com/lbryio/types/v2/go"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
@ -33,7 +32,6 @@ type Fee struct {
} }
type File struct { type File struct {
AddedOn int64 `json:"added_on"`
BlobsCompleted uint64 `json:"blobs_completed"` BlobsCompleted uint64 `json:"blobs_completed"`
BlobsInStream uint64 `json:"blobs_in_stream"` BlobsInStream uint64 `json:"blobs_in_stream"`
BlobsRemaining uint64 `json:"blobs_remaining"` BlobsRemaining uint64 `json:"blobs_remaining"`
@ -48,14 +46,12 @@ type File struct {
DownloadPath string `json:"download_path"` DownloadPath string `json:"download_path"`
FileName string `json:"file_name"` FileName string `json:"file_name"`
Height int `json:"height"` Height int `json:"height"`
IsFullyReflected bool `json:"is_fully_reflected"`
Key string `json:"key"` Key string `json:"key"`
Value *lbryschema.Claim `json:"protobuf"` Metadata *lbryschema.Claim `json:"protobuf"`
MimeType string `json:"mime_type"` MimeType string `json:"mime_type"`
Nout int `json:"nout"` Nout int `json:"nout"`
Outpoint string `json:"outpoint"` Outpoint string `json:"outpoint"`
PurchaseReceipt interface{} `json:"purchase_receipt"` PointsPaid decimal.Decimal `json:"points_paid"`
ReflectorProgress int `json:"reflector_progress"`
SdHash string `json:"sd_hash"` SdHash string `json:"sd_hash"`
Status string `json:"status"` Status string `json:"status"`
Stopped bool `json:"stopped"` Stopped bool `json:"stopped"`
@ -67,7 +63,6 @@ type File struct {
TotalBytes uint64 `json:"total_bytes"` TotalBytes uint64 `json:"total_bytes"`
TotalBytesLowerBound uint64 `json:"total_bytes_lower_bound"` TotalBytesLowerBound uint64 `json:"total_bytes_lower_bound"`
Txid string `json:"txid"` Txid string `json:"txid"`
UploadingToReflector bool `json:"uploading_to_reflector"`
WrittenBytes uint64 `json:"written_bytes"` WrittenBytes uint64 `json:"written_bytes"`
} }
@ -188,6 +183,20 @@ type FileListResponse struct {
TotalPages uint64 `json:"total_pages"` TotalPages uint64 `json:"total_pages"`
} }
type WalletListResponse []string
type BlobAnnounceResponse bool
type WalletPrefillAddressesResponse struct {
Broadcast bool `json:"broadcast"`
Complete bool `json:"complete"`
Hex string `json:"hex"`
}
type WalletNewAddressResponse string
type WalletUnusedAddressResponse string
type Account struct { type Account struct {
AddressGenerator struct { AddressGenerator struct {
Change struct { Change struct {
@ -211,8 +220,8 @@ type Account struct {
Preferences *struct { Preferences *struct {
Theme string `json:"theme"` Theme string `json:"theme"`
} `json:"preferences,omitempty"` } `json:"preferences,omitempty"`
PrivateKey *string `json:"private_key,omitempty"`
PublicKey string `json:"public_key"` PublicKey string `json:"public_key"`
PrivateKey *string `json:"private_key,omitempty"`
Seed *string `json:"seed,omitempty"` Seed *string `json:"seed,omitempty"`
Satoshis uint64 `json:"satoshis"` Satoshis uint64 `json:"satoshis"`
} }
@ -225,37 +234,27 @@ type AccountListResponse struct {
} }
type AccountBalanceResponse struct { type AccountBalanceResponse struct {
Available decimal.Decimal `json:"available"` Available decimal.Decimal `json:"available"`
Reserved decimal.Decimal `json:"reserved"` Reserved decimal.Decimal `json:"reserved"`
ReservedSubtotals struct { ReservedSubtotals *decimal.Decimal `json:"reserved_subtotals"`
Claims decimal.Decimal `json:"claims"` Total decimal.Decimal `json:"total"`
Supports decimal.Decimal `json:"supports"`
Tips decimal.Decimal `json:"tips"`
} `json:"reserved_subtotals"`
Total decimal.Decimal `json:"total"`
} }
type Transaction struct { type Transaction struct {
Address string `json:"address"` Address string `json:"address"`
Amount string `json:"amount"` Amount string `json:"amount"`
ClaimID string `json:"claim_id"` ClaimID string `json:"claim_id"`
ClaimOp string `json:"claim_op"` Confirmations int `json:"confirmations"`
Confirmations int `json:"confirmations"` Height int `json:"height"`
HasSigningKey bool `json:"has_signing_key"` IsChange bool `json:"is_change"`
Height int `json:"height"` IsMine bool `json:"is_mine"`
IsInternalTransfer bool `json:"is_internal_transfer"` Name string `json:"name"`
IsMyInput bool `json:"is_my_input"` Nout uint64 `json:"nout"`
IsMyOutput bool `json:"is_my_output"` PermanentUrl string `json:"permanent_url"`
IsSpent bool `json:"is_spent"` Protobuf string `json:"protobuf,omitempty"`
Name string `json:"name"` Txid string `json:"txid"`
NormalizedName string `json:"normalized_name"` Type string `json:"type"`
Nout uint64 `json:"nout"` Value *lbryschema.Claim `json:"protobuf"`
PermanentUrl string `json:"permanent_url"`
SigningChannel *Claim `json:"signing_channel,omitempty"`
TimeStamp uint64 `json:"time_stamp"`
Txid string `json:"txid"`
Type string `json:"type"`
Value *lbryschema.Claim `json:"protobuf,omitempty"`
} }
type TransactionSummary struct { type TransactionSummary struct {
@ -264,7 +263,6 @@ type TransactionSummary struct {
Inputs []Transaction `json:"inputs"` Inputs []Transaction `json:"inputs"`
Outputs []Transaction `json:"outputs"` Outputs []Transaction `json:"outputs"`
TotalFee string `json:"total_fee"` TotalFee string `json:"total_fee"`
TotalInput string `json:"total_input"`
TotalOutput string `json:"total_output"` TotalOutput string `json:"total_output"`
Txid string `json:"txid"` Txid string `json:"txid"`
} }
@ -305,39 +303,21 @@ type Support struct {
Txid string `json:"txid"` Txid string `json:"txid"`
} }
type PurchaseReceipt struct {
Address string `json:"file_name"`
Amount string `json:"amount"`
ClaimID string `json:"claim_id"`
Confirmations int `json:"confirmations"`
Height int `json:"height"`
Nout uint64 `json:"nout"`
Timestamp uint64 `json:"timestamp"`
Txid string `json:"txid"`
Type string `json:"purchase"`
}
type Claim struct { type Claim struct {
Address string `json:"address"` Address string `json:"address"`
Amount string `json:"amount"` Amount string `json:"amount"`
CanonicalURL string `json:"canonical_url"` CanonicalURL string `json:"canonical_url"`
ChannelID string `json:"channel_id"`
ClaimID string `json:"claim_id"` ClaimID string `json:"claim_id"`
ClaimOp string `json:"claim_op,omitempty"` ClaimOp string `json:"claim_op,omitempty"`
Confirmations int `json:"confirmations"` Confirmations int `json:"confirmations"`
Height int `json:"height"` Height int `json:"height"`
IsChange bool `json:"is_change,omitempty"` IsChange bool `json:"is_change,omitempty"`
IsChannelSignatureValid bool `json:"is_channel_signature_valid,omitempty"` IsChannelSignatureValid bool `json:"is_channel_signature_valid,omitempty"`
IsInternalTransfer bool `json:"is_internal_transfer"`
IsMyInput bool `json:"is_my_input"`
IsMyOutput bool `json:"is_my_output"`
IsSpent bool `json:"is_spent"`
Meta Meta `json:"meta,omitempty"` Meta Meta `json:"meta,omitempty"`
Name string `json:"name"` Name string `json:"name"`
NormalizedName string `json:"normalized_name"` NormalizedName string `json:"normalized_name"`
Nout uint64 `json:"nout"` Nout uint64 `json:"nout"`
PermanentURL string `json:"permanent_url"` PermanentURL string `json:"permanent_url"`
PurchaseReceipt *PurchaseReceipt `json:"purchase_receipt,omitempty"`
ShortURL string `json:"short_url"` ShortURL string `json:"short_url"`
SigningChannel *Claim `json:"signing_channel,omitempty"` SigningChannel *Claim `json:"signing_channel,omitempty"`
Timestamp int `json:"timestamp"` Timestamp int `json:"timestamp"`
@ -370,7 +350,7 @@ type Meta struct {
TrendingMixed float64 `json:"trending_mixed,omitempty"` TrendingMixed float64 `json:"trending_mixed,omitempty"`
} }
const coldStorageURL = "https://s3.wasabisys.com/blobs.lbry.com/" const reflectorURL = "http://blobs.lbry.io/"
// GetStreamSizeByMagic uses "magic" to not just estimate, but actually return the exact size of a stream // GetStreamSizeByMagic uses "magic" to not just estimate, but actually return the exact size of a stream
// It does so by fetching the sd blob and the last blob from our S3 bucket, decrypting and unpadding the last blob // It does so by fetching the sd blob and the last blob from our S3 bucket, decrypting and unpadding the last blob
@ -380,7 +360,7 @@ func (c *Claim) GetStreamSizeByMagic() (streamSize uint64, e error) {
if c.Value.GetStream() == nil { if c.Value.GetStream() == nil {
return 0, errors.Err("this claim is not a stream") return 0, errors.Err("this claim is not a stream")
} }
resp, err := http.Get(coldStorageURL + hex.EncodeToString(c.Value.GetStream().Source.SdHash)) resp, err := http.Get(reflectorURL + hex.EncodeToString(c.Value.GetStream().Source.SdHash))
if err != nil { if err != nil {
return 0, errors.Err(err) return 0, errors.Err(err)
} }
@ -403,7 +383,7 @@ func (c *Claim) GetStreamSizeByMagic() (streamSize uint64, e error) {
streamSize = uint64(stream.MaxBlobSize-1) * uint64(len(sdb.BlobInfos)-2) streamSize = uint64(stream.MaxBlobSize-1) * uint64(len(sdb.BlobInfos)-2)
} }
resp2, err := http.Get(coldStorageURL + hex.EncodeToString(lastBlobHash)) resp2, err := http.Get(reflectorURL + hex.EncodeToString(lastBlobHash))
if err != nil { if err != nil {
return 0, errors.Err(err) return 0, errors.Err(err)
} }
@ -427,33 +407,6 @@ func (c *Claim) GetStreamSizeByMagic() (streamSize uint64, e error) {
return streamSize, nil return streamSize, nil
} }
const (
ProtectedContentTag = SpecialContentType("c:members-only")
PurchaseContentTag = SpecialContentType("c:purchase:")
RentalContentTag = SpecialContentType("c:rental:")
PreorderContentTag = SpecialContentType("c:preorder:")
LegacyPurchaseContentTag = SpecialContentType("purchase:")
LegacyRentalContentTag = SpecialContentType("rental:")
LegacyPreorderContentTag = SpecialContentType("preorder:")
ScheduledShowContentTag = SpecialContentType("c:scheduled:show")
ScheduledHideContentTag = SpecialContentType("c:scheduled:hide")
UnlistedContentTag = SpecialContentType("c:unlisted")
)
type SpecialContentType string
//IsContentSpecial returns true if the claim is of a special content type
func (c *Claim) IsContentSpecial(specialTags ...SpecialContentType) bool {
for _, t := range c.Value.GetTags() {
for _, ct := range specialTags {
if strings.Contains(t, string(ct)) {
return true
}
}
}
return false
}
type StreamListResponse struct { type StreamListResponse struct {
Items []Claim `json:"items"` Items []Claim `json:"items"`
Page uint64 `json:"page"` Page uint64 `json:"page"`
@ -475,18 +428,9 @@ type SupportListResponse struct {
PageSize uint64 `json:"page_size"` PageSize uint64 `json:"page_size"`
TotalPages uint64 `json:"total_pages"` TotalPages uint64 `json:"total_pages"`
} }
type StatusResponse struct { type StatusResponse struct {
BlobManager struct { BlobManager struct {
Connections struct { FinishedBlobs uint64 `json:"finished_blobs"`
MaxIncomingMbs float64 `json:"max_incoming_mbs"`
MaxOutgoingMbs float64 `json:"max_outgoing_mbs"`
TotalIncomingMbs float64 `json:"total_incoming_mbs"`
TotalOutgoingMbs float64 `json:"total_outgoing_mbs"`
TotalReceived int64 `json:"total_received"`
TotalSent int64 `json:"total_sent"`
} `json:"connections"`
FinishedBlobs int64 `json:"finished_blobs"`
} `json:"blob_manager"` } `json:"blob_manager"`
ConnectionStatus struct { ConnectionStatus struct {
Code string `json:"code"` Code string `json:"code"`
@ -496,33 +440,28 @@ type StatusResponse struct {
NodeID string `json:"node_id"` NodeID string `json:"node_id"`
PeersInRoutingTable uint64 `json:"peers_in_routing_table"` PeersInRoutingTable uint64 `json:"peers_in_routing_table"`
} `json:"dht"` } `json:"dht"`
FfmpegStatus struct {
AnalyzeAudioVolume bool `json:"analyze_audio_volume"`
Available bool `json:"available"`
Which string `json:"which"`
} `json:"ffmpeg_status"`
FileManager struct {
ManagedFiles int64 `json:"managed_files"`
} `json:"file_manager"`
HashAnnouncer struct { HashAnnouncer struct {
AnnounceQueueSize uint64 `json:"announce_queue_size"` AnnounceQueueSize uint64 `json:"announce_queue_size"`
} `json:"hash_announcer"` } `json:"hash_announcer"`
InstallationID string `json:"installation_id"` InstallationID string `json:"installation_id"`
IsFirstRun bool `json:"is_first_run"`
IsRunning bool `json:"is_running"` IsRunning bool `json:"is_running"`
SkippedComponents []string `json:"skipped_components"` SkippedComponents []string `json:"skipped_components"`
StartupStatus struct { StartupStatus struct {
BlobManager bool `json:"blob_manager"` BlobManager bool `json:"blob_manager"`
Database bool `json:"database"` BlockchainHeaders bool `json:"blockchain_headers"`
Dht bool `json:"dht"` Database bool `json:"database"`
ExchangeRateManager bool `json:"exchange_rate_manager"` Dht bool `json:"dht"`
FileManager bool `json:"file_manager"` ExchangeRateManager bool `json:"exchange_rate_manager"`
HashAnnouncer bool `json:"hash_announcer"` HashAnnouncer bool `json:"hash_announcer"`
LibtorrentComponent bool `json:"libtorrent_component"` PeerProtocolServer bool `json:"peer_protocol_server"`
PeerProtocolServer bool `json:"peer_protocol_server"` StreamManager bool `json:"stream_manager"`
Upnp bool `json:"upnp"` Upnp bool `json:"upnp"`
Wallet bool `json:"wallet"` Wallet bool `json:"wallet"`
WalletServerPayments bool `json:"wallet_server_payments"`
} `json:"startup_status"` } `json:"startup_status"`
StreamManager struct {
ManagedFiles int64 `json:"managed_files"`
} `json:"stream_manager"`
Upnp struct { Upnp struct {
AioupnpVersion string `json:"aioupnp_version"` AioupnpVersion string `json:"aioupnp_version"`
DhtRedirectSet bool `json:"dht_redirect_set"` DhtRedirectSet bool `json:"dht_redirect_set"`
@ -530,57 +469,27 @@ type StatusResponse struct {
Gateway string `json:"gateway"` Gateway string `json:"gateway"`
PeerRedirectSet bool `json:"peer_redirect_set"` PeerRedirectSet bool `json:"peer_redirect_set"`
Redirects struct{} `json:"redirects"` Redirects struct{} `json:"redirects"`
} `json:"upnp"` }
Wallet struct { Wallet struct {
AvailableServers int `json:"available_servers"` BestBlochash string `json:"best_blockhash"`
BestBlockhash string `json:"best_blockhash"` Blocks int `json:"blocks"`
Blocks int `json:"blocks"` BlocksBehind int `json:"blocks_behind"`
BlocksBehind int `json:"blocks_behind"` IsEncrypted bool `json:"is_encrypted"`
Connected string `json:"connected"` IsLocked bool `json:"is_locked"`
ConnectedFeatures struct {
DailyFee string `json:"daily_fee"`
Description string `json:"description"`
DonationAddress string `json:"donation_address"`
GenesisHash string `json:"genesis_hash"`
HashFunction string `json:"hash_function"`
Hosts struct {
} `json:"hosts"`
PaymentAddress string `json:"payment_address"`
ProtocolMax string `json:"protocol_max"`
ProtocolMin string `json:"protocol_min"`
Pruning interface{} `json:"pruning"`
ServerVersion string `json:"server_version"`
TrendingAlgorithm string `json:"trending_algorithm"`
} `json:"connected_features"`
HeadersSynchronizationProgress int `json:"headers_synchronization_progress"`
KnownServers int `json:"known_servers"`
Servers []struct {
Availability bool `json:"availability"`
Host string `json:"host"`
Latency float64 `json:"latency"`
Port int `json:"port"`
} `json:"servers"`
} `json:"wallet"` } `json:"wallet"`
WalletServerPayments struct {
MaxFee string `json:"max_fee"`
Running bool `json:"running"`
} `json:"wallet_server_payments"`
} }
type UTXOListResponse struct { type UTXOListResponse struct {
Items []struct { Items []struct {
Address string `json:"address"` Address string `json:"address"`
Amount string `json:"amount"` Amount string `json:"amount"`
Confirmations int `json:"confirmations"` Confirmations int `json:"confirmations"`
Height int `json:"height"` Height int `json:"height"`
IsInternalTransfer bool `json:"is_internal_transfer"` IsChange bool `json:"is_change"`
IsMyInput bool `json:"is_my_input"` IsMine bool `json:"is_mine"`
IsMyOutput bool `json:"is_my_output"` Nout int `json:"nout"`
IsSpent bool `json:"is_spent"` Txid string `json:"txid"`
Nout int `json:"nout"` Type string `json:"type"`
Timestamp int64 `json:"timestamp"`
Txid string `json:"txid"`
Type string `json:"type"`
} `json:"items"` } `json:"items"`
Page uint64 `json:"page"` Page uint64 `json:"page"`
PageSize uint64 `json:"page_size"` PageSize uint64 `json:"page_size"`
@ -595,7 +504,6 @@ type transactionListBlob struct {
BalanceDelta string `json:"balance_delta"` BalanceDelta string `json:"balance_delta"`
ClaimId string `json:"claim_id"` ClaimId string `json:"claim_id"`
ClaimName string `json:"claim_name"` ClaimName string `json:"claim_name"`
IsSpent bool `json:"is_spent"`
Nout int `json:"nout"` Nout int `json:"nout"`
} }
@ -607,9 +515,8 @@ type supportBlob struct {
BalanceDelta string `json:"balance_delta"` BalanceDelta string `json:"balance_delta"`
ClaimId string `json:"claim_id"` ClaimId string `json:"claim_id"`
ClaimName string `json:"claim_name"` ClaimName string `json:"claim_name"`
IsSpent bool `json:"is_spent"`
IsTip bool `json:"is_tip"`
Nout int `json:"nout"` Nout int `json:"nout"`
IsTip bool `json:"is_tip"`
} }
type TransactionListResponse struct { type TransactionListResponse struct {
@ -644,17 +551,22 @@ type VersionResponse struct {
Minor string `json:"minor"` Minor string `json:"minor"`
} `json:"version_parts"` } `json:"version_parts"`
} `json:"distro"` } `json:"distro"`
LbrynetVersion string `json:"lbrynet_version"` LbrynetVersion string `json:"lbrynet_version"`
OsRelease string `json:"os_release"` LbryschemaVersion string `json:"lbryschema_version"`
OsSystem string `json:"os_system"` OsRelease string `json:"os_release"`
Platform string `json:"platform"` OsSystem string `json:"os_system"`
Processor string `json:"processor"` Platform string `json:"platform"`
PythonVersion string `json:"python_version"` Processor string `json:"processor"`
Version string `json:"version"` PythonVersion string `json:"python_version"`
} }
type ResolveResponse map[string]Claim type ResolveResponse map[string]Claim
type NumClaimsInChannelResponse map[string]struct {
ClaimsInChannel *uint64 `json:"claims_in_channel,omitempty"`
Error *string `json:"error,omitempty"`
}
type ClaimShowResponse *Claim type ClaimShowResponse *Claim
type Wallet struct { type Wallet struct {

View file

@ -10,13 +10,11 @@ import (
"net/url" "net/url"
"time" "time"
"golang.org/x/oauth2"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
const ( const (
defaultServerAddress = "https://api.odysee.tv" defaultServerAddress = "https://api.lbry.com"
timeout = 5 * time.Second timeout = 5 * time.Second
headerForwardedFor = "X-Forwarded-For" headerForwardedFor = "X-Forwarded-For"
@ -28,7 +26,6 @@ const (
// Client stores data about internal-apis call it is about to make. // Client stores data about internal-apis call it is about to make.
type Client struct { type Client struct {
AuthToken string AuthToken string
OAuthToken oauth2.TokenSource
Logger *log.Logger Logger *log.Logger
serverAddress string serverAddress string
extraHeaders map[string]string extraHeaders map[string]string
@ -44,54 +41,13 @@ type ClientOpts struct {
// APIResponse reflects internal-apis JSON response format. // APIResponse reflects internal-apis JSON response format.
type APIResponse struct { type APIResponse struct {
Success bool `json:"success"` Success bool `json:"success"`
Error *string `json:"error"` Error *string `json:"error"`
Data interface{} `json:"data"` Data *ResponseData `json:"data"`
}
type data struct {
obj map[string]interface{}
array []interface{}
}
func (d data) IsObject() bool {
return d.obj != nil
}
func (d data) IsArray() bool {
return d.array != nil
}
func (d data) Object() (map[string]interface{}, error) {
if d.obj == nil {
return nil, errors.New("no object data found")
}
return d.obj, nil
}
func (d data) Array() ([]interface{}, error) {
if d.array == nil {
return nil, errors.New("no array data found")
}
return d.array, nil
}
// APIError wraps errors returned by LBRY API server to discern them from other kinds (like http errors).
type APIError struct {
Err error
}
func (e APIError) Error() string {
return fmt.Sprintf("api error: %v", e.Err)
} }
// ResponseData is a map containing parsed json response. // ResponseData is a map containing parsed json response.
type ResponseData interface { type ResponseData map[string]interface{}
IsObject() bool
IsArray() bool
Object() (map[string]interface{}, error)
Array() ([]interface{}, error)
}
func makeMethodPath(obj, method string) string { func makeMethodPath(obj, method string) string {
return fmt.Sprintf("/%s/%s", obj, method) return fmt.Sprintf("/%s/%s", obj, method)
@ -118,42 +74,13 @@ func NewClient(authToken string, opts *ClientOpts) Client {
return c return c
} }
// NewOauthClient returns a client instance for internal-apis. It requires Oauth Token Source to be provided
// for authentication.
func NewOauthClient(token oauth2.TokenSource, opts *ClientOpts) Client {
c := Client{
serverAddress: defaultServerAddress,
extraHeaders: make(map[string]string),
OAuthToken: token,
Logger: log.StandardLogger(),
}
if opts != nil {
if opts.ServerAddress != "" {
c.serverAddress = opts.ServerAddress
}
if opts.RemoteIP != "" {
c.extraHeaders[headerForwardedFor] = opts.RemoteIP
}
}
return c
}
func (c Client) getEndpointURL(object, method string) string { func (c Client) getEndpointURL(object, method string) string {
return fmt.Sprintf("%s%s", c.serverAddress, makeMethodPath(object, method)) return fmt.Sprintf("%s%s", c.serverAddress, makeMethodPath(object, method))
} }
func (c Client) getEndpointURLFromPath(path string) string {
return fmt.Sprintf("%s%s", c.serverAddress, path)
}
func (c Client) prepareParams(params map[string]interface{}) (string, error) { func (c Client) prepareParams(params map[string]interface{}) (string, error) {
form := url.Values{} form := url.Values{}
if c.AuthToken != "" { form.Add("auth_token", c.AuthToken)
form.Add("auth_token", c.AuthToken)
} else if c.OAuthToken == nil {
return "", errors.New("oauth token source must be supplied")
}
for k, v := range params { for k, v := range params {
if k == "auth_token" { if k == "auth_token" {
return "", errors.New("extra auth_token supplied in request params") return "", errors.New("extra auth_token supplied in request params")
@ -173,16 +100,6 @@ func (c Client) doCall(url string, payload string) ([]byte, error) {
req.Header.Add("Accept", "application/json") req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/x-www-form-urlencoded") req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
if c.OAuthToken != nil {
t, err := c.OAuthToken.Token()
if err != nil {
return nil, err
}
if t.Type() != "Bearer" {
return nil, errors.New("internal-apis requires an oAuth token of type 'Bearer'")
}
t.SetAuthHeader(req)
}
for k, v := range c.extraHeaders { for k, v := range c.extraHeaders {
req.Header.Set(k, v) req.Header.Set(k, v)
@ -193,77 +110,39 @@ func (c Client) doCall(url string, payload string) ([]byte, error) {
if err != nil { if err != nil {
return body, err return body, err
} }
if r.StatusCode >= 500 {
return body, fmt.Errorf("server returned non-OK status: %v", r.StatusCode)
}
defer r.Body.Close() defer r.Body.Close()
return ioutil.ReadAll(r.Body) return ioutil.ReadAll(r.Body)
} }
// CallResource calls a remote internal-apis server resource, returning a response, // Call calls a remote internal-apis server, returning a response,
// wrapped into standardized API Response struct. // wrapped into standardized API Response struct.
func (c Client) CallResource(object, method string, params map[string]interface{}) (ResponseData, error) { func (c Client) Call(object, method string, params map[string]interface{}) (ResponseData, error) {
var d data var rd ResponseData
payload, err := c.prepareParams(params) payload, err := c.prepareParams(params)
if err != nil { if err != nil {
return d, err return rd, err
} }
body, err := c.doCall(c.getEndpointURL(object, method), payload) body, err := c.doCall(c.getEndpointURL(object, method), payload)
if err != nil { if err != nil {
return d, err return rd, err
} }
var ar APIResponse var ar APIResponse
err = json.Unmarshal(body, &ar) err = json.Unmarshal(body, &ar)
if err != nil { if err != nil {
return d, err return rd, err
} }
if !ar.Success { if !ar.Success {
return d, APIError{errors.New(*ar.Error)} return rd, errors.New(*ar.Error)
} }
if v, ok := ar.Data.([]interface{}); ok { return *ar.Data, err
d.array = v
} else if v, ok := ar.Data.(map[string]interface{}); ok {
d.obj = v
}
return d, err
} }
// Call calls a remote internal-apis server, returning a response, // UserMe returns user details for the user associated with the current auth_token
// wrapped into standardized API Response struct.
func (c Client) Call(path string, params map[string]interface{}) (ResponseData, error) {
var d data
payload, err := c.prepareParams(params)
if err != nil {
return d, err
}
body, err := c.doCall(c.getEndpointURLFromPath(path), payload)
if err != nil {
return d, err
}
var ar APIResponse
err = json.Unmarshal(body, &ar)
if err != nil {
return d, err
}
if !ar.Success {
return d, APIError{errors.New(*ar.Error)}
}
if v, ok := ar.Data.([]interface{}); ok {
d.array = v
} else if v, ok := ar.Data.(map[string]interface{}); ok {
d.obj = v
}
return d, err
}
// UserMe returns user details for the user associated with the current auth_token.
func (c Client) UserMe() (ResponseData, error) { func (c Client) UserMe() (ResponseData, error) {
return c.CallResource(userObjectPath, userMeMethod, map[string]interface{}{}) return c.Call(userObjectPath, userMeMethod, map[string]interface{}{})
} }
// UserHasVerifiedEmail calls has_verified_email method.
func (c Client) UserHasVerifiedEmail() (ResponseData, error) { func (c Client) UserHasVerifiedEmail() (ResponseData, error) {
return c.CallResource(userObjectPath, userHasVerifiedEmailMethod, map[string]interface{}{}) return c.Call(userObjectPath, userHasVerifiedEmailMethod, map[string]interface{}{})
} }

View file

@ -6,96 +6,66 @@ import (
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"golang.org/x/oauth2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func launchDummyServer(lastReq **http.Request, path, response string, status int) *httptest.Server { func TestUserMeWrongToken(t *testing.T) {
c := NewClient("abc", nil)
r, err := c.UserMe()
require.NotNil(t, err)
assert.Equal(t, "could not authenticate user", err.Error())
assert.Nil(t, r)
}
func TestUserHasVerifiedEmailWrongToken(t *testing.T) {
c := NewClient("abc", nil)
r, err := c.UserHasVerifiedEmail()
require.NotNil(t, err)
assert.Equal(t, "could not authenticate user", err.Error())
assert.Nil(t, r)
}
func launchDummyServer(lastReq **http.Request, path, response string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if lastReq != nil { *lastReq = &*r
*lastReq = &*r
}
authT := r.FormValue("auth_token")
if authT == "" {
accessT := r.Header.Get("Authorization")
if accessT == "" {
w.WriteHeader(http.StatusUnauthorized)
return
}
}
if r.URL.Path != path { if r.URL.Path != path {
fmt.Printf("path doesn't match: %v != %v", r.URL.Path, path) fmt.Printf("path doesn't match: %v != %v", r.URL.Path, path)
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
} else { } else {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status) w.WriteHeader(http.StatusOK)
w.Write([]byte(response)) w.Write([]byte(response))
} }
})) }))
} }
func TestUserMe(t *testing.T) { func TestUserMe(t *testing.T) {
ts := launchDummyServer(nil, makeMethodPath(userObjectPath, userMeMethod), userMeResponse, http.StatusOK) var req *http.Request
ts := launchDummyServer(&req, makeMethodPath(userObjectPath, userMeMethod), userMeResponse)
defer ts.Close() defer ts.Close()
c := NewClient("realToken", &ClientOpts{ServerAddress: ts.URL}) c := NewClient("realToken", &ClientOpts{ServerAddress: ts.URL})
r, err := c.UserMe() r, err := c.UserMe()
assert.Nil(t, err) assert.Nil(t, err)
robj, err := r.Object() assert.Equal(t, "user@lbry.tv", r["primary_email"])
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "user@lbry.tv", robj["primary_email"])
}
func TestListFiltered(t *testing.T) {
ts := launchDummyServer(nil, "/file/list_filtered", listFilteredResponse, http.StatusOK)
defer ts.Close()
c := NewClient("realToken", &ClientOpts{ServerAddress: ts.URL})
r, err := c.CallResource("file", "list_filtered", map[string]interface{}{"with_claim_id": "true"})
assert.Nil(t, err)
assert.True(t, r.IsArray())
_, err = r.Array()
if err != nil {
t.Fatal(err)
}
} }
func TestUserHasVerifiedEmail(t *testing.T) { func TestUserHasVerifiedEmail(t *testing.T) {
ts := launchDummyServer(nil, makeMethodPath(userObjectPath, userHasVerifiedEmailMethod), userHasVerifiedEmailResponse, http.StatusOK) var req *http.Request
ts := launchDummyServer(&req, makeMethodPath(userObjectPath, userHasVerifiedEmailMethod), userHasVerifiedEmailResponse)
defer ts.Close() defer ts.Close()
c := NewClient("realToken", &ClientOpts{ServerAddress: ts.URL}) c := NewClient("realToken", &ClientOpts{ServerAddress: ts.URL})
r, err := c.UserHasVerifiedEmail() r, err := c.UserHasVerifiedEmail()
assert.Nil(t, err) assert.Nil(t, err)
robj, err := r.Object() assert.EqualValues(t, 12345, r["user_id"])
if err != nil { assert.Equal(t, true, r["has_verified_email"])
t.Error(err)
}
assert.EqualValues(t, 12345, robj["user_id"])
assert.Equal(t, true, robj["has_verified_email"])
}
func TestUserHasVerifiedEmailOAuth(t *testing.T) {
ts := launchDummyServer(nil, makeMethodPath(userObjectPath, userHasVerifiedEmailMethod), userHasVerifiedEmailResponse, http.StatusOK)
defer ts.Close()
c := NewOauthClient(oauth2.StaticTokenSource(&oauth2.Token{AccessToken: "Test-Access-Token"}), &ClientOpts{ServerAddress: ts.URL})
r, err := c.UserHasVerifiedEmail()
assert.Nil(t, err)
robj, err := r.Object()
if err != nil {
t.Error(err)
}
assert.EqualValues(t, 12345, robj["user_id"])
assert.Equal(t, true, robj["has_verified_email"])
} }
func TestRemoteIP(t *testing.T) { func TestRemoteIP(t *testing.T) {
var req *http.Request var req *http.Request
ts := launchDummyServer(&req, makeMethodPath(userObjectPath, userMeMethod), userMeResponse, http.StatusOK) ts := launchDummyServer(&req, makeMethodPath(userObjectPath, userMeMethod), userMeResponse)
defer ts.Close() defer ts.Close()
c := NewClient("realToken", &ClientOpts{ServerAddress: ts.URL, RemoteIP: "8.8.8.8"}) c := NewClient("realToken", &ClientOpts{ServerAddress: ts.URL, RemoteIP: "8.8.8.8"})
@ -104,34 +74,6 @@ func TestRemoteIP(t *testing.T) {
assert.Equal(t, []string{"8.8.8.8"}, req.Header["X-Forwarded-For"]) assert.Equal(t, []string{"8.8.8.8"}, req.Header["X-Forwarded-For"])
} }
func TestWrongToken(t *testing.T) {
c := NewClient("zcasdasc", nil)
r, err := c.UserHasVerifiedEmail()
assert.False(t, r.IsObject())
assert.EqualError(t, err, "api error: could not authenticate user")
assert.ErrorAs(t, err, &APIError{})
}
func TestHTTPError(t *testing.T) {
c := NewClient("zcasdasc", &ClientOpts{ServerAddress: "http://lolcathost"})
r, err := c.UserHasVerifiedEmail()
assert.False(t, r.IsObject())
assert.EqualError(t, err, `Post "http://lolcathost/user/has_verified_email": dial tcp: lookup lolcathost: no such host`)
}
func TestGatewayError(t *testing.T) {
var req *http.Request
ts := launchDummyServer(&req, makeMethodPath(userObjectPath, userHasVerifiedEmailMethod), "", http.StatusBadGateway)
defer ts.Close()
c := NewClient("zcasdasc", &ClientOpts{ServerAddress: ts.URL})
r, err := c.UserHasVerifiedEmail()
assert.False(t, r.IsObject())
assert.EqualError(t, err, `server returned non-OK status: 502`)
}
const userMeResponse = `{ const userMeResponse = `{
"success": true, "success": true,
"error": null, "error": null,
@ -165,18 +107,3 @@ const userHasVerifiedEmailResponse = `{
"has_verified_email": true "has_verified_email": true
} }
}` }`
const listFilteredResponse = `{
"success": true,
"error": null,
"data": [
{
"claim_id": "322ce77e9085d9da42279c790f7c9755b4916fca",
"outpoint": "20e04af21a569061ced7aa1801a43b4ed4839dfeb79919ea49a4059c7fe114c5:0"
},
{
"claim_id": "61496c567badcd98b82d9a700a8d56fd8a5fa8fb",
"outpoint": "657e4ec774524b326f9d3ecb9f468ea085bd1f3d450565f0330feca02e8fd25b:0"
}
]
}`

View file

@ -9,7 +9,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/lbryio/lbry.go/v2/extras/errors" "github.com/lbryio/lbry.go/extras/errors"
) )
type keyIndex struct { type keyIndex struct {

View file

@ -87,11 +87,11 @@ func InterpolateParams(query string, args ...interface{}) (string, error) {
// Qs is a shortcut for one group of positional placeholders // Qs is a shortcut for one group of positional placeholders
func Qs(count int) string { func Qs(count int) string {
return Placeholders(false, count, 1, 1) return placeholders(false, count, 1, 1)
} }
// placeholders creates indexed or positional placeholders, in groups, with different starts // placeholders creates indexed or positional placeholders, in groups, with different starts
func Placeholders(indexPlaceholders bool, count int, start int, group int) string { func placeholders(indexPlaceholders bool, count int, start int, group int) string {
buf := bytes.Buffer{} buf := bytes.Buffer{}
if start == 0 || group == 0 { if start == 0 || group == 0 {

View file

@ -2,15 +2,12 @@ package util
import ( import (
"fmt" "fmt"
"net"
"net/http"
"strings" "strings"
"time"
"github.com/lbryio/lbry.go/v2/extras/errors" "github.com/lbryio/lbry.go/v2/extras/errors"
"github.com/nlopes/slack"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/slack-go/slack"
) )
var defaultChannel string var defaultChannel string
@ -19,18 +16,7 @@ var slackApi *slack.Client
// InitSlack Initializes a slack client with the given token and sets the default channel. // InitSlack Initializes a slack client with the given token and sets the default channel.
func InitSlack(token string, channel string, username string) { func InitSlack(token string, channel string, username string) {
c := &http.Client{ slackApi = slack.New(token)
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
},
}
slackApi = slack.New(token, slack.OptionHTTPClient(c))
defaultChannel = channel defaultChannel = channel
defaultUsername = username defaultUsername = username
} }
@ -79,13 +65,7 @@ func sendToSlack(channel, username, message string) error {
err = errors.Err("no slack token provided") err = errors.Err("no slack token provided")
} else { } else {
log.Debugln("slack: " + channel + ": " + message) log.Debugln("slack: " + channel + ": " + message)
for { _, _, err = slackApi.PostMessage(channel, slack.MsgOptionText(message, false), slack.MsgOptionUsername(username))
_, _, err = slackApi.PostMessage(channel, slack.MsgOptionText(message, false), slack.MsgOptionUsername(username))
if err != nil && strings.Contains(err.Error(), "timeout awaiting response headers") {
continue
}
break
}
} }
if err != nil { if err != nil {

View file

@ -1,53 +0,0 @@
package util
import (
"encoding/hex"
"golang.org/x/text/cases"
"golang.org/x/text/unicode/norm"
"strings"
)
func StringSplitArg(stringToSplit, separator string) []interface{} {
split := strings.Split(stringToSplit, separator)
splitInterface := make([]interface{}, len(split))
for i, s := range split {
splitInterface[i] = s
}
return splitInterface
}
// NormalizeName Normalize names to remove weird characters and account to capitalization
func NormalizeName(s string) string {
c := cases.Fold()
return c.String(norm.NFD.String(s))
}
// ReverseBytesInPlace reverse the bytes. thanks, Satoshi 😒
func ReverseBytesInPlace(s []byte) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}
// TxIdToTxHash convert the txid to a hash for returning from the hub
func TxIdToTxHash(txid string) []byte {
t, err := hex.DecodeString(txid)
if err != nil {
return nil
}
ReverseBytesInPlace(t)
return t
}
// TxHashToTxId convert the txHash from the response format back to an id
func TxHashToTxId(txHash []byte) string {
t := make([]byte, len(txHash))
copy(t, txHash)
ReverseBytesInPlace(t)
return hex.EncodeToString(t)
}

82
go.mod
View file

@ -1,55 +1,55 @@
go 1.18
module github.com/lbryio/lbry.go/v2 module github.com/lbryio/lbry.go/v2
replace github.com/btcsuite/btcd => github.com/lbryio/lbrycrd.go v0.0.0-20200203050410-e1076f12bf19 replace github.com/btcsuite/btcd => github.com/lbryio/lbrycrd.go v0.0.0-20200203050410-e1076f12bf19
require ( require (
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32 github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/fatih/structs v1.1.0 github.com/fatih/structs v1.1.0
github.com/go-errors/errors v1.4.2 github.com/go-errors/errors v1.0.1
github.com/go-ini/ini v1.67.0 github.com/go-ini/ini v1.48.0
github.com/golang/protobuf v1.5.2 github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect
github.com/gorilla/mux v1.8.0 github.com/golang/protobuf v1.3.2
github.com/google/go-cmp v0.3.1 // indirect
github.com/gopherjs/gopherjs v0.0.0-20190915194858-d3ddacdb130f // indirect
github.com/gorilla/mux v1.7.3
github.com/gorilla/rpc v1.2.0 github.com/gorilla/rpc v1.2.0
github.com/lbryio/ozzo-validation v3.0.3-0.20170512160344-202201e212ec+incompatible github.com/gorilla/websocket v1.4.1 // indirect
github.com/lbryio/types v0.0.0-20220224142228-73610f6654a6 github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/lbryio/lbry.go v1.1.2
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-20191009145016-1bb8107e04f8
github.com/lyoshenka/bencode v0.0.0-20180323155644-b7abd7672df5 github.com/lyoshenka/bencode v0.0.0-20180323155644-b7abd7672df5
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.1.2
github.com/sebdah/goldie v1.0.0 github.com/nlopes/slack v0.6.0
github.com/sergi/go-diff v1.3.1 github.com/onsi/ginkgo v1.10.2 // indirect
github.com/shopspring/decimal v1.3.1 github.com/onsi/gomega v1.7.0 // indirect
github.com/sirupsen/logrus v1.9.0 github.com/sebdah/goldie v0.0.0-20190531093107-d313ffb52c77
github.com/slack-go/slack v0.12.1 github.com/sergi/go-diff v1.0.0
github.com/spf13/cast v1.5.0 github.com/shopspring/decimal v0.0.0-20191009025716-f1972eb1d1f5
github.com/stretchr/testify v1.8.2 github.com/sirupsen/logrus v1.4.2
github.com/ybbus/jsonrpc/v2 v2.1.7 github.com/smartystreets/assertions v1.0.1 // indirect
go.uber.org/atomic v1.10.0 github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect
golang.org/x/crypto v0.7.0 github.com/spf13/cast v1.3.0
golang.org/x/net v0.8.0 github.com/stretchr/testify v1.4.0
golang.org/x/oauth2 v0.6.0 github.com/uber-go/atomic v1.4.0
golang.org/x/text v0.8.0 github.com/ybbus/jsonrpc v0.0.0-20180411222309-2a548b7d822d
golang.org/x/time v0.3.0 go.uber.org/atomic v1.4.0 // indirect
google.golang.org/grpc v1.53.0 golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc
golang.org/x/net v0.0.0-20191009170851-d66e71096ffb
golang.org/x/sys v0.0.0-20191009170203-06d7bd2c5f4f // indirect
golang.org/x/text v0.3.2 // indirect
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03 // indirect
google.golang.org/grpc v1.24.0
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/ini.v1 v1.48.0 // indirect
gopkg.in/nullbio/null.v6 v6.0.0-20161116030900-40264a2e6b79 gopkg.in/nullbio/null.v6 v6.0.0-20161116030900-40264a2e6b79
gotest.tools v2.2.0+incompatible gopkg.in/yaml.v2 v2.2.4 // indirect
) )
require ( go 1.13
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/onsi/gomega v1.7.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.6.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

247
go.sum
View file

@ -1,150 +1,229 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
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-20190207003914-4c204d697803 h1:j3AgPKKZtZStM2nyhrDSLSYgT7YHrZKdSkq1OYeLjvM=
github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
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/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=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd h1:qdGvebPBDuYDPGi1WCPjy1tGyMpmDK8IEapSsszn7HE=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723 h1:ZA/jbKoGcVAnER6pCHPEkGdZOV7U1oLUedErBHCUMs0=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.38.2/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ini/ini v1.48.0 h1:TvO60hO/2xgaaTWp2P0wUe4CFxwdMzfbkv3+343Xzqw=
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-ini/ini v1.48.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE=
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gopherjs/gopherjs v0.0.0-20190915194858-d3ddacdb130f h1:TyqzGm2z1h3AGhjOoRYyeLcW4WlW81MDQkWa+rx/000=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gopherjs/gopherjs v0.0.0-20190915194858-d3ddacdb130f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/rpc v1.1.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk= github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk=
github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89 h1:12K8AlpT0/6QUXSfV0yi4Q0jkbq8NDtIKFtF61AoqV0=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/lbryio/errors.go v0.0.0-20180223142025-ad03d3cc6a5c/go.mod h1:muH7wpUqE8hRA3OrYYosw9+Sl681BF9cwcjzE+OCNK8=
github.com/lbryio/lbry.go v1.1.2 h1:Dyxc+glT/rVWJwHfIf7vjlPYYbjzrQz5ARmJd5Hp69c=
github.com/lbryio/lbry.go v1.1.2/go.mod h1:JtyI30bU51rm0LZ/po3mQuzf++14OWb6kR/6mMRAmKU=
github.com/lbryio/lbrycrd.go v0.0.0-20200203050410-e1076f12bf19 h1:/zWD8dVIl7bV1TdJWqPqy9tpqixzX2Qxgit48h3hQcY= github.com/lbryio/lbrycrd.go v0.0.0-20200203050410-e1076f12bf19 h1:/zWD8dVIl7bV1TdJWqPqy9tpqixzX2Qxgit48h3hQcY=
github.com/lbryio/lbrycrd.go v0.0.0-20200203050410-e1076f12bf19/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/lbryio/lbrycrd.go v0.0.0-20200203050410-e1076f12bf19/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/lbryio/ozzo-validation v3.0.3-0.20170512160344-202201e212ec+incompatible h1:OH/jgRO/2lQ73n7PgtK/CvLZ0dwAVr5G5s635+YfUA4= github.com/lbryio/lbryschema.go v0.0.0-20190428231007-c54836bca002/go.mod h1:dAzPCBj3CKKWBGYBZxK6tKBP5SCgY2tqd9SnQd/OyKo=
github.com/lbryio/ozzo-validation v3.0.3-0.20170512160344-202201e212ec+incompatible/go.mod h1:fbG/dzobG8r95KzMwckXiLMHfFjZaBRQqC9hPs2XAQ4= github.com/lbryio/lbryschema.go v0.0.0-20190602173230-6d2f69a36f46 h1:LemfR+rMxhf7nnOrzy2HqS7Me7SZ5gEwOcNFzKC8ySQ=
github.com/lbryio/types v0.0.0-20220224142228-73610f6654a6 h1:IhL9D2QfDWhLNDQpZ3Uiiw0gZEUYeLBS6uDqOd59G5o= github.com/lbryio/lbryschema.go v0.0.0-20190602173230-6d2f69a36f46/go.mod h1:dAzPCBj3CKKWBGYBZxK6tKBP5SCgY2tqd9SnQd/OyKo=
github.com/lbryio/types v0.0.0-20220224142228-73610f6654a6/go.mod h1:CG3wsDv5BiVYQd5i1Jp7wGsaVyjZTJshqXeWMVKsISE= 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/go.mod h1:CG3wsDv5BiVYQd5i1Jp7wGsaVyjZTJshqXeWMVKsISE=
github.com/lbryio/types v0.0.0-20191009145016-1bb8107e04f8 h1:jSNW/rK6DQsz7Zh+iv1zR384PeQdHt0gS4hKY17tkuM=
github.com/lbryio/types v0.0.0-20191009145016-1bb8107e04f8/go.mod h1:CG3wsDv5BiVYQd5i1Jp7wGsaVyjZTJshqXeWMVKsISE=
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5/go.mod h1:c2mYKRyMb1BPkO5St0c/ps62L4S0W2NAkaTXj9qEI+0=
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg=
github.com/lyoshenka/bencode v0.0.0-20180323155644-b7abd7672df5 h1:mG83tLXWSRdcXMWfkoumVwhcCbf3jHF9QKv/m37BkM0= github.com/lyoshenka/bencode v0.0.0-20180323155644-b7abd7672df5 h1:mG83tLXWSRdcXMWfkoumVwhcCbf3jHF9QKv/m37BkM0=
github.com/lyoshenka/bencode v0.0.0-20180323155644-b7abd7672df5/go.mod h1:H0aPCWffGOaDcjkw1iB7W9DVLp6GXmfcJY/7YZCWPA4= github.com/lyoshenka/bencode v0.0.0-20180323155644-b7abd7672df5/go.mod h1:H0aPCWffGOaDcjkw1iB7W9DVLp6GXmfcJY/7YZCWPA4=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v0.0.0-20180511142126-bb74f1db0675/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/nlopes/slack v0.5.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM=
github.com/nlopes/slack v0.6.0 h1:jt0jxVQGhssx1Ib7naAOZEZcGdtIhTzkP0nopK0AsRA=
github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.2 h1:uqH7bpe+ERSiDa34FDOF7RikN6RzXgduUF8yarlZp94=
github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/sebdah/goldie v0.0.0-20180424091453-8784dd1ab561/go.mod h1:lvjGftC8oe7XPtyrOidaMi0rp5B9+XY/ZRUynGnuaxQ=
github.com/sebdah/goldie v1.0.0 h1:9GNhIat69MSlz/ndaBg48vl9dF5fI+NBB6kfOxgfkMc= github.com/sebdah/goldie v0.0.0-20190531093107-d313ffb52c77 h1:Msb6XRY62jQOueNNlB5LGin1rljK7c49NLniGwJG2bg=
github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= github.com/sebdah/goldie v0.0.0-20190531093107-d313ffb52c77/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v0.0.0-20180607144847-19e3cb6c2930/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v0.0.0-20191009025716-f1972eb1d1f5 h1:Gojs/hac/DoYEM7WEICT45+hNWczIeuL5D21e5/HPAw=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/shopspring/decimal v0.0.0-20191009025716-f1972eb1d1f5/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/slack-go/slack v0.12.1 h1:X97b9g2hnITDtNsNe5GkGx6O2/Sz/uC20ejRZN6QxOw= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/slack-go/slack v0.12.1/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/uber-go/atomic v1.4.0 h1:yOuPqEq4ovnhEjpHmfFwsqBXDYbQeT6Nb0bwD6XnD5o=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/ybbus/jsonrpc v0.0.0-20180411222309-2a548b7d822d h1:tQo6hjclyv3RHUgZOl6iWb2Y44A/sN9bf9LAYfuioEg=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/ybbus/jsonrpc v0.0.0-20180411222309-2a548b7d822d/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE=
github.com/ybbus/jsonrpc/v2 v2.1.7 h1:QjoXuZhkXZ3oLBkrONBe2avzFkYeYLorpeA+d8175XQ= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
github.com/ybbus/jsonrpc/v2 v2.1.7/go.mod h1:rIuG1+ORoiqocf9xs/v+ecaAVeo3zcZHQgInyKFMeg0= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/net v0.0.0-20191009170851-d66e71096ffb h1:TR699M2v0qoKTOHxeLgp6zPqaQNs74f01a/ob9W0qko=
golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20190520201301-c432e742b0af/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191009170203-06d7bd2c5f4f h1:hjzMYz/7Ea1mNKfOnFOfktR0mlA5jqhvywClCMHM/qw=
golang.org/x/sys v0.0.0-20191009170203-06d7bd2c5f4f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0 h1:xQwXv67TxFo9nC1GJFyab5eq/5B590r6RlnL/G8Sz7w=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/genproto v0.0.0-20181004005441-af9cb2a35e7f/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03 h1:4HYDjxeNXAOTv3o1N2tjo8UUSlhQgAD52FVkwxnWgM8=
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s=
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.41.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.48.0 h1:URjZc+8ugRY5mL5uUeQH/a63JcHwdX9xZaWvmNWD7z8=
gopkg.in/ini.v1 v1.48.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/nullbio/null.v6 v6.0.0-20161116030900-40264a2e6b79 h1:FpCr9V8wuOei4BAen+93HtVJ+XSi+KPbaPKm0Vj5R64= gopkg.in/nullbio/null.v6 v6.0.0-20161116030900-40264a2e6b79 h1:FpCr9V8wuOei4BAen+93HtVJ+XSi+KPbaPKm0Vj5R64=
gopkg.in/nullbio/null.v6 v6.0.0-20161116030900-40264a2e6b79/go.mod h1:gWkaRU7CoXpezCBWfWjm3999QqS+1pYPXGbqQCTMzo8= gopkg.in/nullbio/null.v6 v6.0.0-20161116030900-40264a2e6b79/go.mod h1:gWkaRU7CoXpezCBWfWjm3999QqS+1pYPXGbqQCTMzo8=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View file

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

View file

@ -4,14 +4,14 @@ import (
"encoding/hex" "encoding/hex"
"github.com/lbryio/lbry.go/v2/extras/errors" "github.com/lbryio/lbry.go/v2/extras/errors"
c "github.com/lbryio/lbry.go/v2/schema/stake" c "github.com/lbryio/lbryschema.go/claim"
pb "github.com/lbryio/types/v2/go" pb "github.com/lbryio/types/v2/go"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
) )
func NewImageStreamClaim() (*c.StakeHelper, error) { func NewImageStreamClaim() (*c.ClaimHelper, error) {
streamClaim := new(pb.Claim_Stream) streamClaim := new(pb.Claim_Stream)
stream := new(pb.Stream) stream := new(pb.Stream)
image := new(pb.Stream_Image) image := new(pb.Stream_Image)
@ -23,12 +23,12 @@ func NewImageStreamClaim() (*c.StakeHelper, error) {
pbClaim := new(pb.Claim) pbClaim := new(pb.Claim)
pbClaim.Type = streamClaim pbClaim.Type = streamClaim
helper := c.StakeHelper{Claim: pbClaim} helper := c.ClaimHelper{Claim: pbClaim}
return &helper, nil return &helper, nil
} }
func NewVideoStreamClaim() (*c.StakeHelper, error) { func NewVideoStreamClaim() (*c.ClaimHelper, error) {
streamClaim := new(pb.Claim_Stream) streamClaim := new(pb.Claim_Stream)
stream := new(pb.Stream) stream := new(pb.Stream)
video := new(pb.Stream_Video) video := new(pb.Stream_Video)
@ -39,12 +39,12 @@ func NewVideoStreamClaim() (*c.StakeHelper, error) {
pbClaim := new(pb.Claim) pbClaim := new(pb.Claim)
pbClaim.Type = streamClaim pbClaim.Type = streamClaim
helper := c.StakeHelper{Claim: pbClaim} helper := c.ClaimHelper{Claim: pbClaim}
return &helper, nil return &helper, nil
} }
func NewStreamClaim(title, description string) (*c.StakeHelper, error) { func NewStreamClaim(title, description string) (*c.ClaimHelper, error) {
streamClaim := new(pb.Claim_Stream) streamClaim := new(pb.Claim_Stream)
stream := new(pb.Stream) stream := new(pb.Stream)
streamClaim.Stream = stream streamClaim.Stream = stream
@ -52,14 +52,14 @@ func NewStreamClaim(title, description string) (*c.StakeHelper, error) {
pbClaim := new(pb.Claim) pbClaim := new(pb.Claim)
pbClaim.Type = streamClaim pbClaim.Type = streamClaim
helper := c.StakeHelper{Claim: pbClaim} helper := c.ClaimHelper{Claim: pbClaim}
helper.Claim.Title = title helper.Title = title
helper.Claim.Description = description helper.Description = description
return &helper, nil return &helper, nil
} }
func SignClaim(rawTx *wire.MsgTx, privKey btcec.PrivateKey, claim, channel *c.StakeHelper, channelClaimID string) error { func SignClaim(rawTx *wire.MsgTx, privKey btcec.PrivateKey, claim, channel *c.ClaimHelper, channelClaimID string) error {
claimIDHexBytes, err := hex.DecodeString(channelClaimID) claimIDHexBytes, err := hex.DecodeString(channelClaimID)
if err != nil { if err != nil {
return errors.Err(err) return errors.Err(err)

View file

@ -7,7 +7,7 @@ import (
"strconv" "strconv"
"github.com/lbryio/lbry.go/v2/extras/errors" "github.com/lbryio/lbry.go/v2/extras/errors"
c "github.com/lbryio/lbry.go/v2/schema/stake" c "github.com/lbryio/lbryschema.go/claim"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/btcjson"
@ -158,7 +158,7 @@ func (c *Client) SimpleSend(toAddress string, amount float64) (*chainhash.Hash,
return nil, errors.Err(err) return nil, errors.Err(err)
} }
hash, err := c.Client.SendToAddress(decodedAddress, lbcAmount) hash, err := c.Client.SendFromMinConf("", decodedAddress, lbcAmount, 0)
if err != nil { if err != nil {
if err.Error() == "-6: Insufficient funds" { if err.Error() == "-6: Insufficient funds" {
err = errors.Err(errInsufficientFunds) err = errors.Err(errInsufficientFunds)
@ -271,7 +271,7 @@ const (
ClaimSupport ClaimSupport
) )
func (c *Client) AddStakeToTx(rawTx *wire.MsgTx, claim *c.StakeHelper, name string, claimAmount float64, scriptType ScriptType) error { func (c *Client) AddStakeToTx(rawTx *wire.MsgTx, claim *c.ClaimHelper, name string, claimAmount float64, scriptType ScriptType) error {
address, err := c.GetNewAddress("") address, err := c.GetNewAddress("")
if err != nil { if err != nil {
@ -314,7 +314,7 @@ func (c *Client) AddStakeToTx(rawTx *wire.MsgTx, claim *c.StakeHelper, name stri
return nil return nil
} }
func (c *Client) CreateChannel(name string, amount float64) (*c.StakeHelper, *btcec.PrivateKey, error) { func (c *Client) CreateChannel(name string, amount float64) (*c.ClaimHelper, *btcec.PrivateKey, error) {
channel, key, err := NewChannel() channel, key, err := NewChannel()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err

View file

@ -2,8 +2,6 @@
lbry.go is a set of tools and projects implemented in Golang. See each subfolder for more details lbry.go is a set of tools and projects implemented in Golang. See each subfolder for more details
**there are significant updates in the [v3 branch](https://github.com/lbryio/lbry.go/tree/v3). if you're starting a new project, strongly consider using that version instead**
[![Build Status](https://travis-ci.org/lbryio/lbry.go.svg?branch=master)](https://travis-ci.org/lbryio/lbry.go) [![Build Status](https://travis-ci.org/lbryio/lbry.go.svg?branch=master)](https://travis-ci.org/lbryio/lbry.go)
This project uses Go modules. Make sure you have Go 1.11+ installed. This project uses Go modules. Make sure you have Go 1.11+ installed.
@ -36,7 +34,7 @@ This project is MIT licensed. For the full license, see [LICENSE](LICENSE).
## Security ## Security
We take security seriously. Please contact security@lbry.com regarding any issues you may encounter. We take security seriously. Please contact security@lbry.com regarding any issues you may encounter.
Our PGP key is [here](https://lbry.com/faq/pgp-key) if you need it. Our PGP key is [here](https://keybase.io/lbry/key.asc) if you need it.
## Contact ## Contact

4
schema/.gitignore vendored
View file

@ -1,4 +0,0 @@
.idea/
lbryschema-cli
lbryschema-python-binding.h
lbryschema-python-binding.so

View file

@ -1,9 +0,0 @@
os: linux
dist: trusty
language: go
go:
- 1.15.x
script:
- ./build_and_test.sh

View file

@ -1,29 +0,0 @@
package address
import "testing"
func TestDecodeAddressLBRYCrdMain(t *testing.T) {
addr := "bUc9gyCJPKu2CBYpTvJ98MdmsLb68utjP6"
correct := [25]byte{85, 174, 41, 64, 245, 110, 91, 239, 43, 208, 32, 73, 115, 20, 70, 204, 83, 199, 3,
206, 210, 176, 194, 188, 193}
result, err := DecodeAddress(addr, "lbrycrd_main")
if err != nil {
t.Error(err)
}
if result != correct {
t.Error("Mismatch")
}
}
func TestEncodeAddressLBRYCrdMain(t *testing.T) {
addr := [25]byte{85, 174, 41, 64, 245, 110, 91, 239, 43, 208, 32, 73, 115, 20, 70, 204, 83, 199, 3,
206, 210, 176, 194, 188, 193}
correct := "bUc9gyCJPKu2CBYpTvJ98MdmsLb68utjP6"
result, err := EncodeAddress(addr, "lbrycrd_main")
if err != nil {
t.Error(err)
}
if result != correct {
t.Error("Mismatch")
}
}

View file

@ -1 +0,0 @@
package base58

View file

@ -1,21 +0,0 @@
package base58
import (
"errors"
"math/big"
)
var b58Characters = [58]byte{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45,
0x46, 0x47, 0x48, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6a, 0x6b, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
0x79, 0x7a}
func CharacterIndex(character byte) (*big.Int, error) {
for i := 0; i < 58; i++ {
if b58Characters[i] == character {
return big.NewInt(int64(i)), nil
}
}
return nil, errors.New("invalid character")
}

View file

@ -1,20 +0,0 @@
package base58
import "crypto/sha256"
const checksumLength = 4
func VerifyBase58Checksum(v []byte) bool {
checksum := [checksumLength]byte{}
for i := range checksum {
checksum[i] = v[len(v)-checksumLength+i]
}
real_checksum := sha256.Sum256(v[:len(v)-checksumLength])
real_checksum = sha256.Sum256(real_checksum[:])
for i, c := range checksum {
if c != real_checksum[i] {
return false
}
}
return true
}

View file

@ -1,39 +0,0 @@
package base58
import (
"errors"
"math/big"
)
func DecodeBase58(value string, size int64) ([]byte, error) {
buf := []byte(value)
longValue := big.NewInt(0)
result := make([]byte, size)
for i := int64(len(buf) - 1); i >= 0; i-- {
to_add := big.NewInt(0)
to_add = to_add.Exp(big.NewInt(58), big.NewInt(i), to_add)
c, err := CharacterIndex(buf[int64(len(buf))-i-1])
if err != nil {
return result, err
}
to_add = to_add.Mul(c, to_add)
longValue = longValue.Add(to_add, longValue)
}
for i := size - 1; i >= 0; i-- {
m := big.NewInt(0)
longValue, m = longValue.DivMod(longValue, big.NewInt(256), m)
bs := m.Bytes()
if len(bs) == 0 {
bs = append(bs, 0x00)
}
b := byte(bs[0])
result[i] = b
}
if longValue.Int64() != 0 {
return result, errors.New("cannot decode to the given size")
}
if size != int64(len(result)) {
return result, errors.New("length mismatch")
}
return result, nil
}

View file

@ -1,32 +0,0 @@
package base58
import (
"math/big"
)
func EncodeBase58(data []byte) string {
longValue := big.NewInt(0)
result := ""
for i := 0; i < len(data); i++ {
to_add := big.NewInt(0)
to_add = to_add.Exp(big.NewInt(256), big.NewInt(int64(i)), to_add)
to_add = to_add.Mul(big.NewInt(int64(data[24-i])), to_add)
longValue = longValue.Add(to_add, longValue)
}
i := 0
for {
m := big.NewInt(0)
longValue, m = longValue.DivMod(longValue, big.NewInt(58), m)
bs := m.Bytes()
if len(bs) == 0 {
bs = append(bs, 0x00)
}
b := b58Characters[bs[0]]
result = string(b) + result
if longValue.Int64() == 0 {
break
}
i += 1
}
return result
}

View file

@ -1,19 +0,0 @@
package address
import (
"github.com/lbryio/lbry.go/v2/extras/errors"
"github.com/lbryio/lbry.go/v2/schema/address/base58"
)
func DecodeAddress(address string, blockchainName string) ([addressLength]byte, error) {
decoded, err := base58.DecodeBase58(address, addressLength)
if err != nil {
return [addressLength]byte{}, errors.Err("failed to decode")
}
buf := [addressLength]byte{}
for i, b := range decoded {
buf[i] = b
}
return ValidateAddress(buf, blockchainName)
}

View file

@ -1,13 +0,0 @@
package address
import (
"github.com/lbryio/lbry.go/v2/schema/address/base58"
)
func EncodeAddress(address [addressLength]byte, blockchainName string) (string, error) {
buf, err := ValidateAddress(address, blockchainName)
if err != nil {
return "", err
}
return base58.EncodeBase58(buf[:]), nil
}

View file

@ -1,69 +0,0 @@
package address
import (
"github.com/lbryio/lbry.go/v2/extras/errors"
"github.com/lbryio/lbry.go/v2/schema/address/base58"
)
const lbrycrdMainPubkeyPrefix = byte(85)
const lbrycrdMainScriptPrefix = byte(122)
const lbrycrdTestnetPubkeyPrefix = byte(111)
const lbrycrdTestnetScriptPrefix = byte(196)
const lbrycrdRegtestPubkeyPrefix = byte(111)
const lbrycrdRegtestScriptPrefix = byte(196)
const prefixLength = 1
const pubkeyLength = 20
const checksumLength = 4
const addressLength = prefixLength + pubkeyLength + checksumLength
const lbrycrdMain = "lbrycrd_main"
const lbrycrdTestnet = "lbrycrd_testnet"
const lbrycrdRegtest = "lbrycrd_regtest"
var addressPrefixes = map[string][2]byte{
lbrycrdMain: {lbrycrdMainPubkeyPrefix, lbrycrdMainScriptPrefix},
lbrycrdTestnet: {lbrycrdTestnetPubkeyPrefix, lbrycrdTestnetScriptPrefix},
lbrycrdRegtest: {lbrycrdRegtestPubkeyPrefix, lbrycrdRegtestScriptPrefix},
}
func PrefixIsValid(address [addressLength]byte, blockchainName string) bool {
prefix := address[0]
for _, addrPrefix := range addressPrefixes[blockchainName] {
if addrPrefix == prefix {
return true
}
}
return false
}
func PubKeyIsValid(address [addressLength]byte) bool {
pubkey := address[prefixLength : pubkeyLength+prefixLength]
// TODO: validate this for real
if len(pubkey) != pubkeyLength {
return false
}
return true
}
func ChecksumIsValid(address [addressLength]byte) bool {
return base58.VerifyBase58Checksum(address[:])
}
func ValidateAddress(address [addressLength]byte, blockchainName string) ([addressLength]byte, error) {
if blockchainName != lbrycrdMain && blockchainName != lbrycrdTestnet && blockchainName != lbrycrdRegtest {
return address, errors.Err("invalid blockchain name")
}
if !PrefixIsValid(address, blockchainName) {
return address, errors.Err("invalid prefix")
}
if !PubKeyIsValid(address) {
return address, errors.Err("invalid pubkey")
}
if !ChecksumIsValid(address) {
return address, errors.Err("invalid address checksum")
}
return address, nil
}

View file

@ -1,5 +0,0 @@
#!/bin/bash
set -euxo pipefail
go build ./...
go build ./cli/lbryschema-cli.go

View file

@ -1,5 +0,0 @@
#!/bin/bash
set -euxo pipefail
./build.sh
./test.sh

View file

@ -1,44 +0,0 @@
package main
import (
"fmt"
"os"
"github.com/lbryio/lbry.go/v2/schema/stake"
)
func main() {
args := os.Args[1:]
if len(args) == 1 {
claimBytes := []byte(args[0])
decoded, err := stake.DecodeClaimBytes(claimBytes, "lbrycrd_main")
if err != nil {
fmt.Println("Decoding error:", err)
return
}
text, err := decoded.RenderJSON()
if err != nil {
fmt.Println("Decoding error:", err)
return
}
fmt.Println(text)
return
} else if (len(args) == 2) && (args[1] == "--decode_hex") {
claimHex := args[0]
decoded, err := stake.DecodeClaimHex(claimHex, "lbrycrd_main")
if err != nil {
fmt.Println("Decoding error:", err)
return
}
text, err := decoded.RenderJSON()
if err != nil {
fmt.Println("Decoding error:", err)
return
}
fmt.Println(text)
return
} else {
fmt.Println("encountered an error\nusage: \n\tlbryschema-cli <value to decode> [--decode_hex]")
return
}
}

View file

@ -1,108 +0,0 @@
package keys
import (
"crypto/elliptic"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"github.com/lbryio/lbry.go/v2/extras/errors"
"github.com/btcsuite/btcd/btcec"
)
type publicKeyInfo struct {
Raw asn1.RawContent
Algorithm pkix.AlgorithmIdentifier
PublicKey asn1.BitString
}
func PublicKeyToDER(publicKey *btcec.PublicKey) ([]byte, error) {
var publicKeyBytes []byte
var publicKeyAlgorithm pkix.AlgorithmIdentifier
var err error
pub := publicKey.ToECDSA()
publicKeyBytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y)
//ans1 encoding oid for ecdsa public key https://github.com/golang/go/blob/release-branch.go1.12/src/crypto/x509/x509.go#L457
publicKeyAlgorithm.Algorithm = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
//asn1 encoding oid for secp256k1 https://github.com/bitpay/bitpay-go/blob/v2.2.2/key_utils/key_utils.go#L30
paramBytes, err := asn1.Marshal(asn1.ObjectIdentifier{1, 3, 132, 0, 10})
if err != nil {
return nil, errors.Err(err)
}
publicKeyAlgorithm.Parameters.FullBytes = paramBytes
return asn1.Marshal(publicKeyInfo{
Algorithm: publicKeyAlgorithm,
PublicKey: asn1.BitString{
Bytes: publicKeyBytes,
BitLength: 8 * len(publicKeyBytes),
},
})
}
//This type provides compatibility with the btcec package
type ecPrivateKey struct {
Version int
PrivateKey []byte
NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"`
PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"`
}
func PrivateKeyToDER(key *btcec.PrivateKey) ([]byte, error) {
privateKey := make([]byte, (key.Curve.Params().N.BitLen()+7)/8)
oid := asn1.ObjectIdentifier{1, 3, 132, 0, 10}
return asn1.Marshal(ecPrivateKey{
Version: 1,
PrivateKey: key.D.FillBytes(privateKey),
NamedCurveOID: oid,
PublicKey: asn1.BitString{Bytes: elliptic.Marshal(key.Curve, key.X, key.Y)},
})
}
func GetPublicKeyFromBytes(pubKeyBytes []byte) (*btcec.PublicKey, error) {
if len(pubKeyBytes) == 33 {
return btcec.ParsePubKey(pubKeyBytes, btcec.S256())
}
PKInfo := publicKeyInfo{}
_, err := asn1.Unmarshal(pubKeyBytes, &PKInfo)
if err != nil {
return nil, errors.Err(err)
}
pubkeyBytes1 := PKInfo.PublicKey.Bytes
return btcec.ParsePubKey(pubkeyBytes1, btcec.S256())
}
func GetPrivateKeyFromBytes(privKeyBytes []byte) (*btcec.PrivateKey, *btcec.PublicKey, error) {
ecPK := ecPrivateKey{}
_, err := asn1.Unmarshal(privKeyBytes, &ecPK)
if err != nil {
return nil, nil, errors.Err(err)
}
priv, publ := btcec.PrivKeyFromBytes(btcec.S256(), ecPK.PrivateKey)
return priv, publ, nil
}
//Returns a btec.Private key object if provided a correct secp256k1 encoded pem.
func ExtractKeyFromPem(pm string) (*btcec.PrivateKey, *btcec.PublicKey) {
byta := []byte(pm)
blck, _ := pem.Decode(byta)
var ecp ecPrivateKey
asn1.Unmarshal(blck.Bytes, &ecp)
return btcec.PrivKeyFromBytes(btcec.S256(), ecp.PrivateKey)
}
type Signature struct {
btcec.Signature
}
func (s *Signature) LBRYSDKEncode() ([]byte, error) {
if s.R == nil || s.S == nil {
return nil, errors.Err("invalid signature, both S & R are nil")
}
rBytes := s.R.Bytes()
sBytes := s.S.Bytes()
return append(rBytes, sBytes...), nil
}

View file

@ -1,95 +0,0 @@
package keys
import (
"bytes"
"encoding/hex"
"encoding/pem"
"testing"
"github.com/btcsuite/btcd/btcec"
"gotest.tools/assert"
)
// The purpose of this test, is to make sure the function converts btcec.PublicKey to DER format the same way
// lbry SDK does as this is the bytes that are put into protobuf and the same bytes are used for verify signatures.
// Making sure these
func TestPublicKeyToDER(t *testing.T) {
publicKeyHex := "3056301006072a8648ce3d020106052b8104000a03420004d015365a40f3e5c03c87227168e5851f44659837bcf6a3398ae633bc37d04ee19baeb26dc888003bd728146dbea39f5344bf8c52cedaf1a3a1623a0166f4a367"
pubKeyBytes, err := hex.DecodeString(publicKeyHex)
if err != nil {
t.Error(err)
}
p1, err := GetPublicKeyFromBytes(pubKeyBytes)
if err != nil {
t.Error(err)
}
pubkeyBytes2, err := PublicKeyToDER(p1)
if err != nil {
t.Error(err)
}
for i, b := range pubKeyBytes {
assert.Assert(t, b == pubkeyBytes2[i], "DER format in bytes must match!")
}
p2, err := GetPublicKeyFromBytes(pubkeyBytes2)
if err != nil {
t.Error(err)
}
assert.Assert(t, p1.IsEqual(p2), "The keys produced must be the same key!")
}
func TestPrivateKeyToDER(t *testing.T) {
private1, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
t.Fatal(err)
}
bytes, err := PrivateKeyToDER(private1)
if err != nil {
t.Fatal(err)
}
private2, _, err := GetPrivateKeyFromBytes(bytes)
if err != nil {
t.Error(err)
}
if !private1.ToECDSA().Equal(private2.ToECDSA()) {
t.Error("private keys dont match")
}
}
func TestGetPrivateKeyFromBytes(t *testing.T) {
private, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
t.Fatal(err)
}
bytes, err := PrivateKeyToDER(private)
private2, _, err := GetPrivateKeyFromBytes(bytes)
if !private.ToECDSA().Equal(private2.ToECDSA()) {
t.Error("private keys dont match")
}
}
func TestEncodePEMAndBack(t *testing.T) {
private1, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
t.Fatal(err)
}
b := bytes.NewBuffer(nil)
derBytes, err := PrivateKeyToDER(private1)
if err != nil {
t.Fatal(err)
}
err = pem.Encode(b, &pem.Block{Type: "PRIVATE KEY", Bytes: derBytes})
if err != nil {
t.Fatal(err)
}
println(string(b.Bytes()))
private2, _ := ExtractKeyFromPem(string(b.Bytes()))
if err != nil {
t.Fatal(err)
}
if !private1.ToECDSA().Equal(private2.ToECDSA()) {
t.Error("private keys dont match")
}
}

View file

@ -1,105 +0,0 @@
package stake
import (
"encoding/hex"
"testing"
"github.com/lbryio/lbry.go/v2/schema/keys"
pb "github.com/lbryio/types/v2/go"
"github.com/btcsuite/btcd/btcec"
)
type rawClaim struct {
Hex string
ClaimID string
}
var raw_claims = []string{
"08011002225e0801100322583056301006072a8648ce3d020106052b8104000a03420004d015365a40f3e5c03c87227168e5851f44659837bcf6a3398ae633bc37d04ee19baeb26dc888003bd728146dbea39f5344bf8c52cedaf1a3a1623a0166f4a367",
"080110011ad7010801128f01080410011a0c47616d65206f66206c696665221047616d65206f66206c696665206769662a0b4a6f686e20436f6e776179322e437265617469766520436f6d6d6f6e73204174747269627574696f6e20342e3020496e7465726e6174696f6e616c38004224080110011a195569c917f18bf5d2d67f1346aa467b218ba90cdbf2795676da250000803f4a0052005a001a41080110011a30b6adf6e2a62950407ea9fb045a96127b67d39088678d2f738c359894c88d95698075ee6203533d3c204330713aa7acaf2209696d6167652f6769662a5c080110031a40c73fe1be4f1743c2996102eec6ce0509e03744ab940c97d19ddb3b25596206367ab1a3d2583b16c04d2717eeb983ae8f84fee2a46621ffa5c4726b30174c6ff82214251305ca93d4dbedb50dceb282ebcb7b07b7ac65",
"080110011ad7010801128f01080410011a0c47616d65206f66206c696665221047616d65206f66206c696665206769662a0b4a6f686e20436f6e776179322e437265617469766520436f6d6d6f6e73204174747269627574696f6e20342e3020496e7465726e6174696f6e616c38004224080110011a195569c917f18bf5d2d67f1346aa467b218ba90cdbf2795676da250000803f4a0052005a001a41080110011a30b6adf6e2a62950407ea9fb045a96127b67d39088678d2f738c359894c88d95698075ee6203533d3c204330713aa7acaf2209696d6167652f676966",
"080110011af901080112b101080410011a1c43414e47474948207c204b412044494c554152204e4547455249207c222c0a68747470733a2f2f7777772e796f75747562652e636f6d2f77617463683f763d5f5470313577746e7753732a1042616d62616e6720536574796177616e321c436f7079726967687465642028636f6e7461637420617574686f722938004a2968747470733a2f2f6265726b2e6e696e6a612f7468756d626e61696c732f5f5470313577746e77537352005a001a41080110011a304616bfdbb6fcb870d4c235443f9261d289ee8edbd4a51b8c6e3e95a34baeebbbb08978a7c5f9bf9a36245513b450943b2209766964656f2f6d70342a5c080110031a40f94a9db9c70217e4f17f9d38f08770096e7ce94a86b742b972e07c62c9606459c6ad735cd517175cf76ad6ea9eb16ca8198a17e2d31dc3ac53413005b5ca2a3a221402b1839207e2a706f0ba73dec0ce6b719043293d",
"080110011aa510080112dd0f080410011a4b4953545249204d4142554b204e41494b204b412021207b547269707d205369646f61726a6f202d2042616e797577616e67692042617275204e61696b204b41205372692054616e6a756e6722a80e4b657265746120617069205372692054616e6a756e67206164616c61682072616e676b6169616e206b657265746120617069206b656c617320656b6f6e6f6d69204143206a6172616b206a617568206d696c696b205054204b65726574612041706920496e646f6e6573696120285065727365726f292079616e67206d656c6179616e6920727574652042616e797577616e676920426172752d4c656d707579616e67616e2c2070702e204b65726574612061706920696e692064696f7065726173696b616e206f6c656820446165726168204f706572617369204958204a656d6265722c2079616e67206469616d62696c2064617269205372692054616e6a756e672c206e616d6120746f6b6f682064616c616d206365726974612072616b7961742042616e797577616e67692e0a0a4b65726574612061706920696e6920626572616e676b617420646172692042616e797577616e67692070756b756c2030362e3330205749422074696261206469204c656d707579616e67616e2070756b756c2031392e3330205749422c20736564616e676b616e20626572616e676b61742064617269204c656d707579616e67616e2070756b756c2030372e31352057494220746962612064692042616e797577616e67692070756b756c2032312e3135205749422e204b65726574612061706920696e69206d656d62617761207361747520676572626f6e6720616c696e672d616c696e6720626572757061206b65726574612070656d62616e676b6974202862696173616e7961204b5033292c20656e616d206b65726574612070656e756d70616e67206b656c617320656b6f6e6f6d692c2073617475206b6572657461206d616b616e2070656d62616e676b6974206b656c617320656b6f6e6f6d692c2064616e2068616d7069722073656c616c75206d656d626177612073617475206b65726574612062616761736920756e696b2079616e67206265727761726e61206269727520706f6c6f732e204e616d756e2073656972696e672064656e67616e2070656d62617275616e20696d616765205054204b41492c207365636172612062657274616861702073656d756120676572626f6e67206d656e6767756e616b616e206c697665727920224b65736570616b6174616e22206d656e67696b757469206b657265746120617069206c61696e6e79612e0a0a44616c616d207065726a616c616e616e6e79612c206b65726574612061706920696e692062657268656e7469206469205374617369756e2042616e797577616e676920426172752c204b6172616e676173656d2c20526f676f6a616d70692c2054656d7567757275682c204b616c6973657461696c2c2053756d626572776164756e672c20476c656e6d6f72652c204b616c69626172752c204b616c697361742c204a656d6265722c2052616d626970756a692c2054616e6767756c2c2050726f626f6c696e67676f2c20506173757275616e2c2042616e67696c2c205369646f61726a6f2c20537572616261796120477562656e672c20576f6e6f6b726f6d6f2c204d6f6a6f6b6572746f2c204a6f6d62616e672c204b6572746f736f6e6f2c204e67616e6a756b2c204361727562616e2c204d616469756e2c2042617261742c205061726f6e2c2057616c696b756b756e2c2053726167656e2c20507572776f736172692c204b6c6174656e2c2064616e204c656d707579616e67616e2c2064656e67616e20746f74616c2077616b74752074656d7075682073656b697461722031332d3134206a616d2e0a0a4b687573757320756e74756b206b652061726168205374617369756e2042616e797577616e6769204261727520284b41203139342f313935292c206b657265746120696e692064617061742062657268656e7469206469205374617369756e205361726164616e2028756e74756b2070657273696c616e67616e292c204261726f6e2c2053756d6f6269746f2028756e74756b2070657273696c616e67616e292c2064616e20536570616e6a616e672e204469205374617369756e20537572616261796120477562656e672c2064696c616b756b616e2070656d696e646168616e20706f73697369206c6f6b6f6d6f7469662e0a0a5061646120746168756e2032303136207461726966204b41205372692054616e6a756e67206b656d62616c69206469737562736964692070656d6572696e7461682e20506164612074616e6767616c2031204a616e756172692068696e676761203331204d617265742074617269666e7961206164616c6168205270203130302e3030302c30302c206d756c6169203120417072696c2074617269666e7961206164616c61682052702039362e3030302c30302c2064616e206d756c61692031204a756c692074617269666e7961206164616c61682052702039342e3030302c30302e202857696b697065646961290a68747470733a2f2f7777772e796f75747562652e636f6d2f77617463683f763d754750344b5857614536512a1042616d62616e6720536574796177616e321c436f7079726967687465642028636f6e7461637420617574686f722938004a2968747470733a2f2f6265726b2e6e696e6a612f7468756d626e61696c732f754750344b58576145365152005a001a41080110011a30d3d1d49ce3268e3dcf318ebbb6f4bfd454995d6b772bd5e27630743c0fb1d66387bf84b51afe28733812c5495b837b8f2209766964656f2f6d70342a5c080110031a40a47aa2d45ec15d1e578b91e5c8c76ee8a82e55af37da4873a7703795889ee7400967cf41e903788bcf0510d7c06976c99983fa01e702e1fb6d518b0646b0d565221402b1839207e2a706f0ba73dec0ce6b719043293d",
"080110011aa30d080112db0c080410011a3b5354415349554e2042414e595557414e47492042415255207c2050656e6767616e7469205374617369756e2042616e797577616e6769204c616d6122b60b5374617369756e2042616e797577616e676920426172752028425729206164616c6168207374617369756e206b657265746120617069206b656c61732062657361722079616e6720626572616461206469204b65746170616e672c204b616c697075726f2c2042616e797577616e67692e205374617369756e2079616e67207465726c6574616b2070616461206b6574696e676769616e202b37206d20696e69206d65727570616b616e207374617369756e2079616e67206c6574616b6e79612070616c696e672074696d757220646920446165726168204f706572617369204958204a656d6265722e205374617369756e20696e692062657261646120646920756a756e672070616c696e672074696d75722050756c6175204a6177612064616e2068616e7961206265726a6172616b20313030206d6574657220646172692050656c61627568616e2046657269204b65746170616e6720736568696e676761207374617369756e20696e69206a75676120736572696e672064697365627574205374617369756e204b65746170616e672e205374617369756e20696e69206a756761206d65727570616b616e207374617369756e206b65726574612061706920616b7469662079616e67206265726c6f6b6173692070616c696e672074696d75722064692042616e797577616e67692c204a6177612054696d75722c2064616e20496e646f6e657369612e205374617369756e20696e6920646962616e67756e2062657273616d61616e2064656e67616e2070656d62616e67756e616e206a616c757220626172752064617269207374617369756e206e6f6e20616b746966204b61626174206d656e756a752070656c61627568616e207465727365627574207061646120746168756e20313938353b20646966756e6773696b616e20756e74756b206d656e6767616e74696b616e205374617369756e2042616e797577616e6769204c616d612079616e67206164612064692077696c61796168206b6f74612042616e797577616e67692e205374617369756e2042616e797577616e67692042617275207465726c6574616b203130206b6d20646172692077696c61796168206b6f7461206b6520617261682075746172613b20646962616e67756e20756e74756b206d656d656e756869206b656275747568616e207472616e73706f72746173692079616e672073696e657267697320616e74617261206b6572657461206170692064656e67616e206b6170616c20666572692064692070656e7965626572616e67616e204b65746170616e672e205374617369756e20696e69206d656d696c696b6920656e616d206a616c75722064656e67616e206a616c757220322073656261676169207365707572206c757275732e0a5374617369756e20696e692064696c656e676b6170692064656e67616e20737562206469706f206c6f6b6f6d6f7469662064616e206469706f206b65726574612020756e74756b206d656e79696d70616e2c206d6572617761742061726d616461206b657265746120617069206b68757375736e7961206d696c696b2044616f70204958206974752073656e646972692c206a756761206d656d70756e79616920207475726e7461626c652042616c6c6f6f6e204c6f6f702079616e67207465726c6574616b20646920736562656c61682075746172612e0a5374617369756e20696e69206a756761206d656c6179616e6920616e676b7574616e20626172616e672c207961697475206b6572657461206170692053656d656e205469676120526f64612079616e67206469626572616e676b61746b616e2064617269205374617369756e204e616d626f2064616e206d656e6a616469204b412079616e67206d656e656d707568206a6172616b2070616c696e67206a61756820646920496e646f6e657369612e5b77696b6970656469615d0a68747470733a2f2f7777772e796f75747562652e636f6d2f77617463683f763d475473535a5a30794a53452a1042616d62616e6720536574796177616e321c436f7079726967687465642028636f6e7461637420617574686f722938004a2968747470733a2f2f6265726b2e6e696e6a612f7468756d626e61696c732f475473535a5a30794a534552005a001a41080110011a3048b3efe92661810e11118c9f8c0b4b4d1bca195eb6f74c8325070d97c699f4fb7ecc9ac90b3decc4feeb2ea0431e65922209766964656f2f6d70342a5c080110031a408e9fc836cad00c52ec7cdc95c11fc5369874948891df2187faaee212ca0925fc1058df5339c153dc00f055a8a21b853fb449a8ccb25ea52a98ba5645b22bdbfb221402b1839207e2a706f0ba73dec0ce6b719043293d",
"080110011aa40b080112dc0a080410011a465354415349554e2054454d504548202854504529207c504155442d4b422053454b4152204152554d7c204a616c7572204d617469204c756d616a616e672d506173697269616e22ac095374617369756e2054656d706568202854504529206b6574696e676769616e202b3933206d206d65727570616b616e207374617369756e206b657265746120617069206d61746920286e6f6e20616b746966292079616e67207465726c6574616b20646920447573756e2054756c757372656a6f20492054656d706568204c6f72204b6563616d6174616e2054656d7065682c204b616275706174656e204c756d616a616e672064656e67616e204b6f6f7264696e6174203a203038c2b03131e280b235372e32e280b34c532c313133c2b03130e280b232392e38e280b342542e205374617369756e20696e69206d65727570616b616e2073616c61682073617475207374617369756e2070616461206a616c7572206b657265746120617069204c756d616a616e672d506173697269616e2079616e67206d756c616920646967756e616b616e2074616e6767616c203136204d65692031383936202064616e2074656c616820646974757475702073656d656e6a616b203120466562727561726920313938382e2050616461206d6173612070656e6a616a6168616e2042656c616e64612c206a616c75722d6a616c757220696e69206265726164612064692062617761682070656e67656c6f6c61616e2053746161747373706f6f722d20656e205472616d776567656e20284f6f737465726c696a6e656e29202853532d4f4c292e0a5361617420696e69206b6f6e64697369205374617369756e2054656d7065682074696e6767616c2062616e67756e616e207574616d612c2079616e672064696a6164696b616e20736562616761692074656d706174206265726d61696e206b616e616b2d6b616e616b205041554420e28093204b422053454b4152204152554d2e0a4a616c7572206b65726574612061706920696e692070616461206d617361206c616c75206d65727570616b616e206a616c75722079616e672063756b757020736962756b2c2064656e67616e205374617369756e204c756d616a616e67202d79616e67207465726265736172206469206a616c757220696e69206d656c6179616e692068616d706972203330302e3030302070656e756d70616e6720706572746168756e2064616e20626172616e672068696e676761203233207269627520746f6e206c6562696820646920616e7461726120746168756e20313935302d31393533202e204a756d6c61682070656e756d70616e672079616e67206e61696b2064617269205374617369756e2054656d7065682068616d70697220736574656e6761682062616e79616b6e79612079616e67206e61696b2064617269205374617369756e204c756d616a616e672e0a53656d656e74617261206a6172696e67616e2072656c2062657365727461206b656c656e676b6170616e20776573656c2064616e2070657273696e79616c616e6e79612074656c61682068616269732074616b20626572736973612e2042656b6173206a616c75722072656c6e7961206b696e69206d656e6a616469206a616c616e206b6563696c202867616e672920616e746172206b616d70756e672e202857696b697065646961290a68747470733a2f2f7777772e796f75747562652e636f6d2f77617463683f763d43704e33485f6f67695f6f2a1042616d62616e6720536574796177616e321c436f7079726967687465642028636f6e7461637420617574686f722938004a2968747470733a2f2f6265726b2e6e696e6a612f7468756d626e61696c732f43704e33485f6f67695f6f52005a001a41080110011a30c11a6c72dc5cf5bb9b80bb58e760893984010a219702062234ef6eb9ec9572353a3c1b5b4da91a57057ee671b454f3c22209766964656f2f6d70342a5c080110031a40a8134e7e6e123c0b9ca568d95d8804da8a70877bdc47ca9b1c536db3a0e35a0de213ee66e3df77d42fb0c47cbd32c901b344b3e017f355169d7f85722a124dc9221402b1839207e2a706f0ba73dec0ce6b719043293d",
"080110011a8307080112bb06080410011a484b4120534552415955202032313520444154414e47204449205354415349554e2042414e4a415220204d454d424157412050554c414e47207c4a75727573616e205057542d505345228905536161742073656c657361692070656e656c75737572616e206a616c7572206e6f6e20616b7469662042616e6a61722c2050616e67616e646172616e2073616d7061692043696a756c616e67206d61756e79612074656d75616e206469207374617369756e2042616e6a617220756e74756b206d656e67756361706b616e2073616c616d207065727069736168616e2064656e67616e204f6d204d6179626920507261626f776f2073616e67206d617374657220626c7573756b616e206a616c7572206e6f6e20616b7469662c207465726e79617461204b412079616e6720616b616e206d656d626177612070756c616e672062616c696b206b65204a616b6172746120646174616e672064756c75616e206461726920507572776f6b6572746f2064616e20736179612073656e64697269206d617369682062657261646120646961746173206a656d626174616e2f4f7665727061732079616e672062657261646120646920736562656c6168207374617369756e2042616e6a61722061726168204a616b617274612c20616b6869726e79612073616c616d207065727069736168616e2068616e79612064656e67616e206d656d766964696f6b616e206b65726574612079616e67206d656d626177616e79612070756c616e672062616c696b206b65204a616b6172746120646172692061746173204a656d626174616e2f4f7665727061732e0a536179612073656e646972692062616c696b206b652053757261626179612064656e67616e204b4120506173756e64616e2c20476f6f64627965206d7920667269656e6420746f206d65657420616761696e2e0a68747470733a2f2f7777772e796f75747562652e636f6d2f77617463683f763d596c6f594d3353447430452a1042616d62616e6720536574796177616e321c436f7079726967687465642028636f6e7461637420617574686f722938004a2968747470733a2f2f6265726b2e6e696e6a612f7468756d626e61696c732f596c6f594d33534474304552005a001a41080110011a308c591efe76bd6d31b39c553996f925b3002b6fc150116f0e8d6bf7654e6674c5b3a59baef24c50fa908580a02dd90ded2209766964656f2f6d70342a5c080110031a40cbef89584d26bbf2695e039a10f2b34749843d827323c530e63b0472407fc7b184d174634d91f05efee9b90c1706e319bd9641226728524952e2b9004400684d221402b1839207e2a706f0ba73dec0ce6b719043293d",
"080110011aed03080112a503080410011a3b4b41205345524159552020323136204449204a504c203432362d41205354415349554e2042414e4a4152207c4a75727573616e205053452d505754228002496e696c6168204b4120536572617975203231362079616e67206d656d62617761206d617374657220626c7573756b616e206a616c7572206e6f6e20616b746966204f6d204d6179626920507261626f776f202068747470733a2f2f7777772e796f75747562652e636f6d2f6368616e6e656c2f55435076355953496f59716f38364a525f4871626b437251202e0a5361617420696e6920616b616e206d656e656c7573757269206a616c7572206e6f6e20616b7469662042616e6a61722d50616e67616e646172616e2d43696a756c616e672e0a68747470733a2f2f7777772e796f75747562652e636f6d2f77617463683f763d4a6b4347615473774c35632a1042616d62616e6720536574796177616e321c436f7079726967687465642028636f6e7461637420617574686f722938004a2968747470733a2f2f6265726b2e6e696e6a612f7468756d626e61696c732f4a6b4347615473774c356352005a001a41080110011a302ed97c79df5eccb145f8f8e1e866be1a392004a6794347c08c7e851c5f00b1504092a9f3c0674c78805a73a33c8b1bf32209766964656f2f6d70342a5c080110031a40cbcec20908e60b5f6198aecc192d2a9e4b069aa58d9238cb7154e37c4d04f268feefe92c2705c14009acf32e7e876df180cff3afdea6c989e75b4861150d1644221402b1839207e2a706f0ba73dec0ce6b719043293d",
}
func TestDecodeClaim(t *testing.T) {
claimHex := "000aa4010a8a010a30f1303989f58396694b0c5982c97f7e9d9435841d92aa13f4b80f671c27110c469babc4fbf4bd764155eaac089cfc49e8121454554d205045204d45524e45204c41472e6d703418cad0c8012209766964656f2f6d70343230c2c9389731e2a9568f66c78d703736a8c341015ada2e46f5dcc87aa6f08ab17c02df2121d9f6ef74055827a29dfc75801a044e6f6e6532040803180a5a0908b001109001188102421054554d205045204d45524e45204c41474a0944657369206c6f636b62020801"
claim, err := DecodeClaimHex(claimHex, "lbrycrd_main")
if err != nil {
t.Error(err, claim.ClaimID)
}
}
func TestDecodeClaims(t *testing.T) {
for _, claim_hex := range raw_claims {
claim, err := DecodeClaimHex(claim_hex, "lbrycrd_main")
if err != nil {
t.Error(err)
}
serializedHex, err := claim.serializedHexString()
if err != nil {
t.Error(err)
}
if serializedHex != claim_hex {
t.Error("failed to re-serialize")
}
}
}
func TestStripSignature(t *testing.T) {
claimHex := raw_claims[1]
claim, err := DecodeClaimHex(claimHex, "lbrycrd_main")
if err != nil {
t.Error(err)
}
noSig, err := claim.serializedNoSignature()
if err != nil {
t.Error(err)
}
if hex.EncodeToString(noSig) != raw_claims[2] {
t.Error("failed to remove signature")
}
}
func TestCreateChannelClaim(t *testing.T) {
private, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
t.Error(err)
}
pubKeyBytes, err := keys.PublicKeyToDER(private.PubKey())
if err != nil {
t.Error(err)
}
claim := &StakeHelper{Claim: newChannelClaim(), Version: NoSig}
claim.Claim.GetChannel().PublicKey = pubKeyBytes
claim.Claim.Title = "Test Channel Title"
claim.Claim.Description = "Test Channel Description"
claim.Claim.GetChannel().Cover = &pb.Source{Url: "http://testcoverurl.com"}
claim.Claim.Tags = []string{"TagA", "TagB", "TagC"}
claim.Claim.Languages = []*pb.Language{{Language: pb.Language_en}, {Language: pb.Language_es}}
claim.Claim.Thumbnail = &pb.Source{Url: "http://thumbnailurl.com"}
claim.Claim.GetChannel().WebsiteUrl = "http://homepageurl.com"
claim.Claim.Locations = []*pb.Location{{Country: pb.Location_AD}, {Country: pb.Location_US, State: "NJ", City: "some city"}}
rawClaim, err := claim.CompileValue()
if err != nil {
t.Error(err)
}
claim, err = DecodeClaimBytes(rawClaim, "lbrycrd_main")
if err != nil {
t.Error(err)
}
if bytes, err := claim.CompileValue(); err != nil || len(bytes) != len(rawClaim) {
t.Error("decoded claim does not match original")
}
}

View file

@ -1,197 +0,0 @@
package stake
import (
"encoding/hex"
"github.com/lbryio/lbry.go/v2/extras/errors"
v1pb "github.com/lbryio/types/v1/go"
pb "github.com/lbryio/types/v2/go"
"github.com/btcsuite/btcutil/base58"
)
const lbrySDHash = "lbry_sd_hash"
func newStreamClaim() *pb.Claim {
claimStream := new(pb.Claim_Stream)
stream := new(pb.Stream)
pbClaim := new(pb.Claim)
pbClaim.Type = claimStream
claimStream.Stream = stream
return pbClaim
}
func newChannelClaim() *pb.Claim {
claimChannel := new(pb.Claim_Channel)
channel := new(pb.Channel)
pbClaim := new(pb.Claim)
pbClaim.Type = claimChannel
claimChannel.Channel = channel
return pbClaim
}
func setMetaData(claim *pb.Claim, author string, description string, language pb.Language_Language, license string,
licenseURL *string, title string, thumbnail *string, nsfw bool) {
claim.Title = title
claim.Description = description
claim.GetStream().Author = author
claim.Languages = []*pb.Language{{Language: language}}
if thumbnail != nil {
source := new(pb.Source)
source.Url = *thumbnail
claim.Thumbnail = source
}
if nsfw {
claim.Tags = []string{"mature"}
}
claim.GetStream().License = license
if licenseURL != nil {
claim.GetStream().LicenseUrl = *licenseURL
}
}
func migrateV1PBClaim(vClaim v1pb.Claim) (*pb.Claim, error) {
if *vClaim.ClaimType == v1pb.Claim_streamType {
return migrateV1PBStream(vClaim)
}
if *vClaim.ClaimType == v1pb.Claim_certificateType {
return migrateV1PBChannel(vClaim)
}
return nil, errors.Err("Could not migrate v1 protobuf claim due to unknown type '%s'.", vClaim.ClaimType.String())
}
func migrateV1PBStream(vClaim v1pb.Claim) (*pb.Claim, error) {
claim := newStreamClaim()
source := new(pb.Source)
source.MediaType = vClaim.GetStream().GetSource().GetContentType()
source.SdHash = vClaim.GetStream().GetSource().GetSource()
claim.GetStream().Source = source
md := vClaim.GetStream().GetMetadata()
if md.GetFee() != nil {
claim.GetStream().Fee = new(pb.Fee)
claim.GetStream().GetFee().Amount = uint64(*md.GetFee().Amount * 100000000)
claim.GetStream().GetFee().Address = md.GetFee().GetAddress()
claim.GetStream().GetFee().Currency = pb.Fee_Currency(pb.Fee_Currency_value[md.GetFee().GetCurrency().String()])
}
if vClaim.GetStream().GetMetadata().GetNsfw() {
claim.Tags = []string{"mature"}
}
thumbnailSource := new(pb.Source)
thumbnailSource.Url = md.GetThumbnail()
claim.Thumbnail = thumbnailSource
language := pb.Language_Language(pb.Language_Language_value[md.GetLanguage().String()])
claim.Languages = []*pb.Language{{Language: language}}
claim.GetStream().LicenseUrl = md.GetLicenseUrl()
claim.GetStream().License = md.GetLicense()
claim.Title = md.GetTitle()
claim.Description = md.GetDescription()
claim.GetStream().Author = md.GetAuthor()
return claim, nil
}
func migrateV1PBChannel(vClaim v1pb.Claim) (*pb.Claim, error) {
claim := newChannelClaim()
claim.GetChannel().PublicKey = vClaim.GetCertificate().PublicKey
return claim, nil
}
func migrateV1Claim(vClaim V1Claim) (*pb.Claim, error) {
pbClaim := newStreamClaim()
//Stream
// -->Universal
setFee(vClaim.Fee, pbClaim)
// -->MetaData
language := pb.Language_Language(pb.Language_Language_value[vClaim.Language])
setMetaData(pbClaim, vClaim.Author, vClaim.Description, language,
vClaim.License, nil, vClaim.Title, vClaim.Thumbnail, false)
// -->Source
source := new(pb.Source)
source.MediaType = vClaim.ContentType
src, err := hex.DecodeString(vClaim.Sources.LbrySDHash)
if err != nil {
return nil, errors.Err(err)
}
source.SdHash = src
pbClaim.GetStream().Source = source
return pbClaim, nil
}
func migrateV2Claim(vClaim V2Claim) (*pb.Claim, error) {
pbClaim := newStreamClaim()
//Stream
// -->Fee
setFee(vClaim.Fee, pbClaim)
// -->MetaData
language := pb.Language_Language(pb.Language_Language_value[vClaim.Language])
setMetaData(pbClaim, vClaim.Author, vClaim.Description, language,
vClaim.License, vClaim.LicenseURL, vClaim.Title, vClaim.Thumbnail, vClaim.NSFW)
// -->Source
source := new(pb.Source)
source.MediaType = vClaim.ContentType
src, err := hex.DecodeString(vClaim.Sources.LbrySDHash)
if err != nil {
return nil, errors.Err(err)
}
source.SdHash = src
pbClaim.GetStream().Source = source
return pbClaim, nil
}
func migrateV3Claim(vClaim V3Claim) (*pb.Claim, error) {
pbClaim := newStreamClaim()
//Stream
// -->Fee
setFee(vClaim.Fee, pbClaim)
// -->MetaData
language := pb.Language_Language(pb.Language_Language_value[vClaim.Language])
setMetaData(pbClaim, vClaim.Author, vClaim.Description, language,
vClaim.License, vClaim.LicenseURL, vClaim.Title, vClaim.Thumbnail, vClaim.NSFW)
// -->Source
source := new(pb.Source)
source.MediaType = vClaim.ContentType
src, err := hex.DecodeString(vClaim.Sources.LbrySDHash)
if err != nil {
return nil, errors.Err(err)
}
source.SdHash = src
pbClaim.GetStream().Source = source
return pbClaim, nil
}
func setFee(fee *Fee, pbClaim *pb.Claim) {
if fee != nil {
amount := float32(0.0)
currency := pb.Fee_LBC
address := ""
if fee.BTC != nil {
amount = fee.BTC.Amount
currency = pb.Fee_BTC
address = fee.BTC.Address
} else if fee.LBC != nil {
amount = fee.LBC.Amount
currency = pb.Fee_LBC
address = fee.LBC.Address
} else if fee.USD != nil {
amount = fee.USD.Amount
currency = pb.Fee_USD
address = fee.USD.Address
}
pbClaim.GetStream().Fee = new(pb.Fee)
//Fee Settings
pbClaim.GetStream().GetFee().Amount = uint64(amount * 100000000)
pbClaim.GetStream().GetFee().Currency = currency
pbClaim.GetStream().GetFee().Address = base58.Decode(address)
}
}

View file

@ -1,282 +0,0 @@
package stake
import (
"bytes"
"encoding/hex"
"fmt"
"testing"
"github.com/lbryio/lbry.go/v2/schema/address"
"github.com/btcsuite/btcutil/base58"
"gotest.tools/assert"
)
type valueTestPair struct {
ValueAsHex string
Claim claimResult
}
type claimResult struct {
Version string
Author string
Title string
Description string
License string
LicenseURL string
FeeAmount float32
FeeCurrency string
FeeAddress string
ContentType string
Language string
LbrySDHash string
Thumbnail string
NSFW bool
}
var badJsonVersionTests = []string{
"7b22666565223a20352c2022766572223a2022302e302e33222c20226c6963656e7365223a202247504c20332e30222c20226c616e6775616765223a2022656e222c2022617574686f72223a2022576f6e64657220576f6d616e2031393933222c20227469746c65223a2022576f6e64657220576f6d616e2031393933222c2022736f7572636573223a207b226c6272795f73645f68617368223a2022323338663832363932356462323235353863646335636430373436303637626666393964636263333834333832666661303236646463373230373261366362323461393332346461373961626136643232373437313164653035353063396534227d2c20226e736677223a20747275652c2022636f6e74656e745f74797065223a2022696d6167652f6a706567222c20227468756d626e61696c223a2022687474703a2f2f692e696d6775722e636f6d2f4568344a4658732e6a7067222c20226465736372697074696f6e223a2022466f7220746865206c6f766521227d",
"7b22666565223a2022302e303031222c2022766572223a2022302e302e33222c20226465736372697074696f6e223a20224120717569636b206c6f6f6b2061742074686520536f6e79204c44502033363030204c617365726469736320706c61796572222c20226c6963656e7365223a20224c42525920696e63222c20227469746c65223a2022536f6e79204c44502033363030204c617365726469736320506c61796572222c2022617574686f72223a20225061756c204b6176616e616768222c20226c616e6775616765223a2022656e222c2022736f7572636573223a207b226c6272795f73645f68617368223a2022393962383766363064643136643730316538613562666238353130633938343239623866633563656538623764663333643665666135386464313133313261333665616437303638643133636364636331383563376465613730643930393261227d2c2022636f6e74656e745f74797065223a2022766964656f2f6d7034222c20226e736677223a2066616c73657d",
"080110011ac3bd01080112c2b501080410011a0b57696e205465737420313122065465737420322a0444617665322e437265617469766520436f6d6d6f6e73204174747269627574696f6e20332e3020556e697465642053746174657338004224080110031a19556bc2a9c28bc2b057c3aec2901d5445c3b17cc384c3bd29c3b72b5f74101cc2aec2aac2a1250000c2b2424a0052005a3868747470733a2f2f6372656174697665636f6d6d6f6e732e6f72672f6c6963656e7365732f62792f332e302f75732f6c6567616c636f64651a41080110011a302a1ec2a42cc383223f7cc2b4c291353d0f687675c289c3a34cc3a73942c383c3abc3b7330dc3912d162ac388c3bec3a5c2aec293c2b1c38f68c3a2c2bfc2907a04c3b8c39dc2b1742209696d6167652f706e67",
"7b22666565223a2022302e303031222c2022766572223a2022302e302e33222c20226465736372697074696f6e223a20224120717569636b206c6f6f6b2061742074686520536f6e79204c44502033363030204c617365726469736320706c61796572222c20226c6963656e7365223a20224c42525920696e63222c20227469746c65223a2022536f6e79204c44502033363030204c617365726469736320506c61796572222c2022617574686f72223a20225061756c204b6176616e616768222c20226c616e6775616765223a2022656e222c2022736f7572636573223a207b226c6272795f73645f68617368223a2022393962383766363064643136643730316538613562666238353130633938343239623866633563656538623764663333643665666135386464313133313261333665616437303638643133636364636331383563376465613730643930393261227d2c2022636f6e74656e745f74797065223a2022766964656f2f6d7034222c20226e736677223a2066616c73657d",
"7b22666565223a2022302e303031222c2022766572223a2022302e302e33222c20226465736372697074696f6e223a20224120717569636b206c6f6f6b2061742074686520536f6e79204c44502033363030204c617365726469736320706c61796572222c20226c6963656e7365223a20224c42525920696e63222c2022617574686f72223a20225061756c204b6176616e616768222c20227469746c65223a2022536f6e79204c44502033363030204c617365726469736320506c61796572222c20226c616e6775616765223a2022656e222c2022736f7572636573223a207b226c6272795f73645f68617368223a2022626639663033333464623237393634303730663035636637356262376131626664663431363864356565376130633866373563663661623536653965633363643039633534636636383262353366316531353030363930616165353530663134227d2c2022636f6e74656e745f74797065223a2022766964656f2f6d7034222c20226e736677223a2066616c73657d",
"30383031313030313161356330383031313231343038303431303030316130303232303032613030333230303338303034613030353230303561303031613432303830313130303131613330333933333030616561386262623636613238613966633031316231666238373634636364386338323633656233343532623733303832363233656534356431363866646530343836346435303563376535396539353430636263643766623336323230613639366436313637363532663661373036353637",
}
var jsonVersionTests = []valueTestPair{
{"7b22666565223a207b224c4243223a207b22616d6f756e74223a20312e302c202261646472657373223a2022625077474139683775696a6f79357541767a565051773951794c6f595a6568484a6f227d7d2c20226465736372697074696f6e223a2022313030304d4220746573742066696c6520746f206d65617375726520646f776e6c6f6164207370656564206f6e204c627279207032702d6e6574776f726b2e222c20226c6963656e7365223a20224e6f6e65222c2022617574686f72223a2022726f6f74222c20226c616e6775616765223a2022456e676c697368222c20227469746c65223a2022313030304d4220737065656420746573742066696c65222c2022736f7572636573223a207b226c6272795f73645f68617368223a2022626439343033336431336634663339303837303837303163616635363562666130396366616466326633346661646634613733666238366232393564316232316137653634383035393934653435623566626336353066333062616334383734227d2c2022636f6e74656e742d74797065223a20226170706c69636174696f6e2f6f637465742d73747265616d222c20227468756d626e61696c223a20222f686f6d65726f626572742f6c6272792f73706565642e6a7067227d",
claimResult{"0.0.1",
"root",
"1000MB speed test file",
"1000MB test file to measure download speed on Lbry p2p-network.",
"None",
"",
1,
"LBC",
"bPwGA9h7uijoy5uAvzVPQw9QyLoYZehHJo",
"application/octet-stream",
"", //"English" is not supported for conversion.
"bd94033d13f4f3908708701caf565bfa09cfadf2f34fadf4a73fb86b295d1b21a7e64805994e45b5fbc650f30bac4874",
"/homerobert/lbry/speed.jpg",
false,
},
},
{"7b22666565223a207b224c4243223a207b22616d6f756e74223a2035302e302c202261646472657373223a2022624c5673336966507275795a6e70596d46665432544c416d68715a76676a70514461227d7d2c20226465736372697074696f6e223a2022466f757220636f75706c6573206d65657420666f722053756e646179206272756e6368206f6e6c7920746f20646973636f76657220746865792061726520737475636b20696e206120686f75736520746f6765746865722061732074686520776f726c64206d61792062652061626f757420746f20656e642e222c20226c6963656e7365223a20224f7363696c6c6f73636f7065204c61626f7261746f72696573222c2022617574686f72223a20225772697474656e20616e6420646972656374656420627920546f646420426572676572222c20226c616e6775616765223a2022656e222c20227469746c65223a2022497427732061204469736173746572222c2022736f7572636573223a207b226c6272795f73645f68617368223a2022646363316266323838393361353033376561623965346139636437613462666536663736616436633231393730656134636565653031323266353032656630373936353764346463613435366234626533323439383439633465313836386238227d2c2022636f6e74656e742d74797065223a2022766964656f2f717569636b74696d65222c20227468756d626e61696c223a2022687474703a2f2f69612e6d656469612d696d64622e636f6d2f696d616765732f4d2f4d5635424d5451774e6a597a4d5451304d6c35424d6c3542616e426e586b46745a5463774e44557a4f444d354e7740402e5f56315f5359313030305f4352302c302c3637332c313030305f414c5f2e6a7067227d",
claimResult{"0.0.1",
"Written and directed by Todd Berger",
"It's a Disaster",
"Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.",
"Oscilloscope Laboratories",
"",
50,
"LBC",
"bLVs3ifPruyZnpYmFfT2TLAmhqZvgjpQDa",
"video/quicktime",
"language:en ",
"dcc1bf28893a5037eab9e4a9cd7a4bfe6f76ad6c21970ea4ceee0122f502ef079657d4dca456b4be3249849c4e1868b8",
"http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg",
false,
},
},
{"7b226465736372697074696f6e223a202241647669736f7220416c6578205461626172726f6b206769766573206869732074616b65206f6e204c4252592e222c20226c6963656e7365223a20224c4252592c20496e632e222c2022617574686f72223a202253616d75656c20427279616e222c20226c616e6775616765223a2022656e222c20227469746c65223a20224d65657420746865205465616d20457069736f64652031222c2022736f7572636573223a207b226c6272795f73645f68617368223a2022373939613164653933623865353536643766363638313033613666666334386163356664363830316464346438396361653637373363383064383163323833373130666434666432356564363864306462656565323638663832393134313435227d2c2022636f6e74656e742d74797065223a2022766964656f2f6d7034227d",
claimResult{"0.0.1",
"Samuel Bryan",
"Meet the Team Episode 1",
"Advisor Alex Tabarrok gives his take on LBRY.",
"LBRY, Inc.",
"",
0,
"UNKNOWN_CURRENCY",
"",
"video/mp4",
"language:en ",
"799a1de93b8e556d7f668103a6ffc48ac5fd6801dd4d89cae6773c80d81c283710fd4fd25ed68d0dbeee268f82914145",
"http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg",
false,
},
},
{"7b226c616e6775616765223a2022656e222c2022666565223a207b22555344223a207b22616d6f756e74223a20302e30312c202261646472657373223a2022624d486d5a4b5a6250713662504245514663384d5870694468463966374d56784d52227d7d2c2022736f7572636573223a207b226c6272795f73645f68617368223a2022326264386439646431613231386337663536373137653533666135313065666435613863303839656431663236373561306638643062356238626233633165643338336362396633616562396238393137383937363133303532393339373961227d2c20226465736372697074696f6e223a2022636c6f756473222c20226c6963656e7365223a2022637265617469766520636f6d6d6f6e73222c2022617574686f72223a202268747470733a2f2f7777772e76696465657a792e636f6d2f636c6f7564732f323637362d6461726b2d73746f726d2d636c6f7564732d726f79616c74792d667265652d68642d73746f636b2d766964656f222c20226e736677223a2066616c73652c20227469746c65223a2022636c6f756473222c2022636f6e74656e742d74797065223a2022766964656f2f6d7034222c2022766572223a2022302e302e32227d",
claimResult{"0.0.2",
"https://www.videezy.com/clouds/2676-dark-storm-clouds-royalty-free-hd-stock-video",
"clouds",
"clouds",
"creative commons",
"",
0.01,
"USD",
"bMHmZKZbPq6bPBEQFc8MXpiDhF9f7MVxMR",
"video/mp4",
"language:en ",
"2bd8d9dd1a218c7f56717e53fa510efd5a8c089ed1f2675a0f8d0b5b8bb3c1ed383cb9f3aeb9b891789761305293979a",
"",
false,
},
},
{"7b226c6963656e7365223a2022437265617469766520436f6d6d6f6e73204174747269627574696f6e20332e3020556e6974656420537461746573222c2022666565223a207b224c4243223a207b22616d6f756e74223a20312c202261646472657373223a20226256507157775966766a424859426f7575766b6e62514d58555a46764c644573354d227d7d2c2022766572223a2022302e302e32222c20226465736372697074696f6e223a20227a222c20226c616e6775616765223a2022656e222c2022617574686f72223a202279222c20227469746c65223a202278222c2022736f7572636573223a207b226c6272795f73645f68617368223a2022376332316565323337333234653561353061333432353632306665366363343030643363636363303535313938363763646131623963313061393737313934653331323030343134643837313436626666343730626162376637643735343738227d2c20226e736677223a2066616c73652c20226c6963656e73655f75726c223a202268747470733a2f2f6372656174697665636f6d6d6f6e732e6f72672f6c6963656e7365732f62792f332e302f75732f6c6567616c636f6465222c2022636f6e74656e742d74797065223a2022746578742f706c61696e227d",
claimResult{"0.0.2",
"y",
"x",
"z",
"Creative Commons Attribution 3.0 United States",
"https://creativecommons.org/licenses/by/3.0/us/legalcode",
1,
"LBC",
"bVPqWwYfvjBHYBouuvknbQMXUZFvLdEs5M",
"text/plain",
"language:en ",
"7c21ee237324e5a50a3425620fe6cc400d3cccc05519867cda1b9c10a977194e31200414d87146bff470bab7f7d75478",
"",
false,
},
},
{"7b22666565223a207b22555344223a207b22616d6f756e74223a20302e342c202261646472657373223a202262485365334b417674565352346d365331317a6475754639584874777363446a6f45227d7d2c2022766572223a2022302e302e33222c20226c6963656e7365223a2022437265617469766520436f6d6d6f6e73204174747269627574696f6e20332e3020556e6974656420537461746573222c20226c616e6775616765223a2022656e222c20227469746c65223a2022526561647920506c61796572204f6e652028417564696f626f6f6b2031206f66203229222c2022617574686f72223a202245726e65737420436c696e65222c2022736f7572636573223a207b226c6272795f73645f68617368223a2022333430653164646130653834313463323166616662623166323866326338623338343832316665306431653261373134336134383130353435653634653236613431303530343264346434376463393735346236313865636466653064313931227d2c20226e736677223a2066616c73652c2022636f6e74656e745f74797065223a2022617564696f2f6d706567222c20226c6963656e73655f75726c223a202268747470733a2f2f6372656174697665636f6d6d6f6e732e6f72672f6c6963656e7365732f62792f332e302f75732f6c6567616c636f6465222c20227468756d626e61696c223a2022687474703a2f2f692e696d6775722e636f6d2f6c794b45485a632e6a7067222c20226465736372697074696f6e223a2022496e20746865207965617220323034342c2074686520776f726c64206973206772697070656420627920616e20656e65726779206372697369732063617573696e67207769646573707265616420736f6369616c2070726f626c656d7320616e642065636f6e6f6d696320737461676e6174696f6e2e20546865207072696d6172792065736361706520666f72206d6f73742070656f706c6520697320746865204f415349532c2061207669727475616c20756e6976657273652c20616363657373656420776974682061207669736f7220616e642068617074696320676c6f7665732e2049742066756e6374696f6e7320626f746820617320616e204d4d4f52504720616e642061732061207669727475616c20736f63696574792c2077697468206974732063757272656e6379206265696e6720746865206d6f737420737461626c652063757272656e637920696e2074686520776f726c642e204974207761732063726561746564206279204a616d65732048616c6c696461792c2077686f73652077696c6c206c656674206120736572696573206f6620636c75657320746f776172647320616e20456173746572204567672077697468696e20746865204f41534953207468617420776f756c64206772616e742077686f6576657220666f756e6420697420626f74682068697320666f7274756e6520616e6420636f6e74726f6c206f6620746865204f4153495320697473656c662e205468697320686173206c656420746f20616e20696e74656e736520696e74657265737420696e20616c6c2061737065637473206f662038307320706f702063756c747572652c2077686963682048616c6c69646179206d61646520636c65617220776f756c6420626520657373656e7469616c20746f2066696e64696e6720686973206567672e227d",
claimResult{"0.0.3",
"Ernest Cline",
"Ready Player One (Audiobook 1 of 2)",
"In the year 2044, the world is gripped by an energy crisis causing widespread social problems and economic stagnation. The primary escape for most people is the OASIS, a virtual universe, accessed with a visor and haptic gloves. It functions both as an MMORPG and as a virtual society, with its currency being the most stable currency in the world. It was created by James Halliday, whose will left a series of clues towards an Easter Egg within the OASIS that would grant whoever found it both his fortune and control of the OASIS itself. This has led to an intense interest in all aspects of 80s pop culture, which Halliday made clear would be essential to finding his egg.",
"Creative Commons Attribution 3.0 United States",
"https://creativecommons.org/licenses/by/3.0/us/legalcode",
0.4,
"USD",
"bHSe3KAvtVSR4m6S11zduuF9XHtwscDjoE",
"audio/mpeg",
"language:en ",
"340e1dda0e8414c21fafbb1f28f2c8b384821fe0d1e2a7143a4810545e64e26a4105042d4d47dc9754b618ecdfe0d191",
"http://i.imgur.com/lyKEHZc.jpg",
false,
},
},
{"7b22666565223a207b224c4243223a207b22616d6f756e74223a20312c202261646472657373223a202262525478744355706a3654764a48675763527347634861467972524c6b6b69586747227d7d2c2022766572223a2022302e302e33222c20226c6963656e7365223a2022436f707972696768742032303136206272617a7a657273222c20226c616e6775616765223a2022656e222c20227469746c65223a2022686f7420706f726e222c2022617574686f72223a20226272617a7a657273222c2022736f7572636573223a207b226c6272795f73645f68617368223a2022666330646163356366633532363335343936336666313736396636653733396334653432613037393034323066666162396661336236343031653933616535613035313565666639363066386339633237323930376564613666636261323534227d2c20226e736677223a20747275652c2022636f6e74656e745f74797065223a2022766964656f2f6d7034222c20226465736372697074696f6e223a2022686f7420706f726e5c6e6272617a7a657273227d",
claimResult{"0.0.3",
"brazzers",
"hot porn",
"hot porn\nbrazzers",
"Copyright 2016 brazzers",
"",
1,
"LBC",
"bRTxtCUpj6TvJHgWcRsGcHaFyrRLkkiXgG",
"video/mp4",
"language:en ",
"fc0dac5cfc526354963ff1769f6e739c4e42a0790420ffab9fa3b6401e93ae5a0515eff960f8c9c272907eda6fcba254",
"",
true,
},
},
}
func TestBadMigrationFromJSON(t *testing.T) {
for _, hexStr := range badJsonVersionTests {
valueBytes, err := hex.DecodeString(hexStr)
if err != nil {
t.Error(err)
}
_, err = DecodeClaimBytes(valueBytes, "lbrycrd_main")
if err == nil {
t.Error(fmt.Sprintf("Decode error: The raw claim '%s' should have failed decoding.", hexStr))
}
}
}
func TestMigrationFromJSON(t *testing.T) {
for _, pair := range jsonVersionTests {
valueBytes, err := hex.DecodeString(pair.ValueAsHex)
if err != nil {
t.Error(err)
}
helper, err := DecodeClaimBytes(valueBytes, "lbrycrd_main")
if err != nil {
t.Error("Decode error: ", err)
}
if helper.Claim.GetStream().GetAuthor() != pair.Claim.Author {
t.Error("Author mismatch: expected", pair.Claim.Author, "got", helper.Claim.GetStream().GetAuthor())
}
if helper.Claim.GetTitle() != pair.Claim.Title {
t.Error("Title mismatch: expected", pair.Claim.Title, "got '", helper.Claim.GetTitle(), "'")
}
if helper.Claim.GetDescription() != pair.Claim.Description {
t.Error("Description mismatch: expected", pair.Claim.Description, "got '", helper.Claim.GetDescription(), "'")
}
if helper.Claim.GetStream().GetLicense() != pair.Claim.License {
t.Error("License mismatch: expected", pair.Claim.License, "got", helper.Claim.GetStream().GetLicense())
}
if helper.Claim.GetStream().GetLicenseUrl() != pair.Claim.LicenseURL {
t.Error("LicenseURL mismatch: expected", pair.Claim.LicenseURL, "got", helper.Claim.GetStream().GetLicenseUrl())
}
if helper.Claim.GetStream().GetFee().GetAmount() != uint64(pair.Claim.FeeAmount*100000000) {
t.Error("Fee Amount mismatch: expected", pair.Claim.FeeAmount, "got", helper.Claim.GetStream().GetFee().GetAmount())
}
if helper.Claim.GetStream().GetFee().GetCurrency().String() != pair.Claim.FeeCurrency {
t.Error("Fee Currency mismatch: expected", pair.Claim.FeeCurrency, "got", helper.Claim.GetStream().GetFee().GetCurrency())
}
hexaddress := base58.Encode(helper.Claim.GetStream().GetFee().GetAddress())
if hexaddress != pair.Claim.FeeAddress {
t.Error("Fee Address mismatch: expected", pair.Claim.FeeAddress, "got", hexaddress)
}
if helper.Claim.GetStream().GetSource().GetMediaType() != pair.Claim.ContentType {
t.Error("ContentType mismatch: expected", pair.Claim.ContentType, "got", helper.Claim.GetStream().GetSource().GetMediaType())
}
if helper.Claim.GetLanguages()[0].String() != pair.Claim.Language {
t.Error("Language mismatch: expected ", pair.Claim.Language, " got ", helper.Claim.GetLanguages()[0].String())
}
content := hex.EncodeToString(helper.Claim.GetStream().GetSource().GetSdHash())
if content != pair.Claim.LbrySDHash {
t.Error("Source mismatch: expected", pair.Claim.LbrySDHash, "got", content)
}
}
}
func TestMigrationFromV1YTSync(t *testing.T) {
claimHex := "080110011aee04080112a604080410011a2b4865726520617265203520526561736f6e73204920e29da4efb88f204e657874636c6f7564207c20544c4722920346696e64206f7574206d6f72652061626f7574204e657874636c6f75643a2068747470733a2f2f6e657874636c6f75642e636f6d2f0a0a596f752063616e2066696e64206d65206f6e20746865736520736f6369616c733a0a202a20466f72756d733a2068747470733a2f2f666f72756d2e6865617679656c656d656e742e696f2f0a202a20506f64636173743a2068747470733a2f2f6f6666746f706963616c2e6e65740a202a2050617472656f6e3a2068747470733a2f2f70617472656f6e2e636f6d2f7468656c696e757867616d65720a202a204d657263683a2068747470733a2f2f746565737072696e672e636f6d2f73746f7265732f6f6666696369616c2d6c696e75782d67616d65720a202a205477697463683a2068747470733a2f2f7477697463682e74762f786f6e64616b0a202a20547769747465723a2068747470733a2f2f747769747465722e636f6d2f7468656c696e757867616d65720a0a2e2e2e0a68747470733a2f2f7777772e796f75747562652e636f6d2f77617463683f763d4672546442434f535f66632a0f546865204c696e75782047616d6572321c436f7079726967687465642028636f6e7461637420617574686f722938004a2968747470733a2f2f6265726b2e6e696e6a612f7468756d626e61696c732f4672546442434f535f666352005a001a41080110011a30040e8ac6e89c061f982528c23ad33829fd7146435bf7a4cc22f0bff70c4fe0b91fd36da9a375e3e1c171db825bf5d1f32209766964656f2f6d70342a5c080110031a4062b2dd4c45e364030fbfad1a6fefff695ebf20ea33a5381b947753e2a0ca359989a5cc7d15e5392a0d354c0b68498382b2701b22c03beb8dcb91089031b871e72214feb61536c007cdf4faeeaab4876cb397feaf6b51"
claim, err := DecodeClaimHex(claimHex, "lbrycrd_main")
if err != nil {
t.Error(err)
}
assert.Assert(t, claim.Claim.GetTitle() == "Here are 5 Reasons I ❤️ Nextcloud | TLG")
assert.Assert(t, claim.Claim.GetDescription() == "Find out more about Nextcloud: https://nextcloud.com/\n\nYou can find me on these socials:\n * Forums: https://forum.heavyelement.io/\n * Podcast: https://offtopical.net\n * Patreon: https://patreon.com/thelinuxgamer\n * Merch: https://teespring.com/stores/official-linux-gamer\n * Twitch: https://twitch.tv/xondak\n * Twitter: https://twitter.com/thelinuxgamer\n\n...\nhttps://www.youtube.com/watch?v=FrTdBCOS_fc")
assert.Assert(t, claim.GetStream().GetLicense() == "Copyrighted (contact author)")
assert.Assert(t, claim.GetStream().GetAuthor() == "The Linux Gamer")
//?assert.Assert(t, claim.GetStream().GetLanguages()[0])
assert.Assert(t, claim.GetStream().GetSource().GetMediaType() == "video/mp4")
assert.Assert(t, claim.Claim.GetThumbnail().GetUrl() == "https://berk.ninja/thumbnails/FrTdBCOS_fc")
sdHashBytes, err := hex.DecodeString("040e8ac6e89c061f982528c23ad33829fd7146435bf7a4cc22f0bff70c4fe0b91fd36da9a375e3e1c171db825bf5d1f3")
if err != nil {
t.Error(err)
}
assert.Assert(t, bytes.Equal(claim.GetStream().GetSource().GetSdHash(), sdHashBytes))
channelHex := "08011002225e0801100322583056301006072a8648ce3d020106052b8104000a034200043878b1edd4a1373149909ef03f4339f6da9c2bd2214c040fd2e530463ffe66098eca14fc70b50ff3aefd106049a815f595ed5a13eda7419ad78d9ed7ae473f17"
channel, err := DecodeClaimHex(channelHex, "lbrycrd_main")
if err != nil {
t.Error(err)
}
pubKeyBytes, err := hex.DecodeString("3056301006072a8648ce3d020106052b8104000a034200043878b1edd4a1373149909ef03f4339f6da9c2bd2214c040fd2e530463ffe66098eca14fc70b50ff3aefd106049a815f595ed5a13eda7419ad78d9ed7ae473f17")
if err != nil {
t.Error(err)
}
assert.Assert(t, bytes.Equal(pubKeyBytes, channel.Claim.GetChannel().GetPublicKey()))
}
func TestMigrationFromV1UnsignedWithFee(t *testing.T) {
claimHex := "080110011ad6010801127c080410011a08727067206d69646922046d6964692a08727067206d696469322e437265617469766520436f6d6d6f6e73204174747269627574696f6e20342e3020496e7465726e6174696f6e616c38004224080110011a19553f00bc139bbf40de425f94d51fffb34c1bea6d9171cd374c25000070414a0052005a001a54080110011a301f41eb0312aa7e8a5ce49349bc77d811da975833719d751523b19f123fc3d528d6a94e3446ccddb7b9329f27a9cad7e3221c6170706c69636174696f6e2f782d7a69702d636f6d70726573736564"
claim, err := DecodeClaimHex(claimHex, "lbrycrd_main")
if err != nil {
t.Error(err)
}
assert.Assert(t, claim.Claim.GetTitle() == "rpg midi")
assert.Assert(t, claim.Claim.GetDescription() == "midi")
assert.Assert(t, claim.GetStream().GetLicense() == "Creative Commons Attribution 4.0 International")
assert.Assert(t, claim.GetStream().GetAuthor() == "rpg midi")
//assert.Assert(t, claim.GetStream().GetLanguage() == "en")
assert.Assert(t, claim.GetStream().GetSource().GetMediaType() == "application/x-zip-compressed")
sdHashBytes, err := hex.DecodeString("1f41eb0312aa7e8a5ce49349bc77d811da975833719d751523b19f123fc3d528d6a94e3446ccddb7b9329f27a9cad7e3")
if err != nil {
t.Error(err)
}
assert.Assert(t, bytes.Equal(claim.GetStream().GetSource().GetSdHash(), sdHashBytes))
feeAddressBytes, err := address.DecodeAddress("bJUQ9MxS9N6M29zsA5GTpVSDzsnPjMBBX9", "lbrycrd_main")
assert.Assert(t, bytes.Equal(claim.GetStream().GetFee().GetAddress(), feeAddressBytes[:]))
assert.Assert(t, claim.GetStream().GetFee().GetAmount() == 1500000000)
assert.Assert(t, claim.GetStream().GetFee().GetCurrency().String() == "LBC")
}

View file

@ -1,32 +0,0 @@
package stake
import (
"encoding/json"
"fmt"
"github.com/golang/protobuf/jsonpb"
)
func marshalToString(c *StakeHelper) (string, error) {
m_pb := &jsonpb.Marshaler{}
if c.IsSupport() {
return m_pb.MarshalToString(c.Support)
}
return m_pb.MarshalToString(c.Claim)
}
func (c *StakeHelper) RenderJSON() (string, error) {
r, err := marshalToString(c)
if err != nil {
fmt.Println("err")
return "", err
}
var dat map[string]interface{}
err = json.Unmarshal([]byte(r), &dat)
if err != nil {
return "", err
}
return r, nil
}
//TODO: encode byte arrays with b58 for addresses and b16 for source hashes instead of the default of b64

View file

@ -1,125 +0,0 @@
package stake
import (
"encoding/json"
"github.com/lbryio/lbry.go/v2/extras/errors"
)
// V1Claim is the first version of claim metadata used by lbry.
type V1Claim struct {
Version string `json:"ver,omitempty"`
Title string `json:"title"` //Required
Description string `json:"description"` //Required
Author string `json:"author"` //Required
Language string `json:"language"` //Required
License string `json:"license"` //Required
Sources Sources `json:"sources"` //Required
ContentType string `json:"content-type"` //Required
Thumbnail *string `json:"thumbnail,omitempty"`
Fee *Fee `json:"fee,omitempty"`
Contact *int `json:"contact,omitempty"`
PubKey *string `json:"pubkey,omitempty"`
}
// V2Claim is the second version of claim metadata used by lbry.
type V2Claim struct {
Version string `json:"ver"` //Required
Title string `json:"title"` //Required
Description string `json:"description"` //Required
Author string `json:"author"` //Required
Language string `json:"language"` //Required
License string `json:"license"` //Required
Sources Sources `json:"sources"` //Required
ContentType string `json:"content-type"` //Required
Thumbnail *string `json:"thumbnail,omitempty"`
Fee *Fee `json:"fee,omitempty"`
Contact *int `json:"contact,omitempty"`
PubKey *string `json:"pubkey,omitempty"`
LicenseURL *string `json:"license_url,omitempty"`
NSFW bool `json:"nsfw"` //Required
}
// V3Claim is the third version of claim metadata used by lbry.
type V3Claim struct {
Version string `json:"ver"` //Required
Title string `json:"title"` //Required
Description string `json:"description"` //Required
Author string `json:"author"` //Required
Language string `json:"language"` //Required
License string `json:"license"` //Required
Sources Sources `json:"sources"` //Required
ContentType string `json:"content_type"` //Required
Thumbnail *string `json:"thumbnail,omitempty"`
Fee *Fee `json:"fee,omitempty"`
Contact *int `json:"contact,omitempty"`
PubKey *string `json:"pubkey,omitempty"`
LicenseURL *string `json:"license_url,omitempty"`
NSFW bool `json:"nsfw"` //Required
Sig *string `json:"sig"`
}
// FeeInfo is the structure of fee information used by lbry.
type FeeInfo struct {
Amount float32 `json:"amount"` //Required
Address string `json:"address"` //Required
}
// Sources is the structure of Sources that can be used for a claim. Sources mainly include lbrysdhash but could be from
// elsewhere in the future.
type Sources struct {
LbrySDHash string `json:"lbry_sd_hash"` //Required
BTIH string `json:"btih"` //Required
URL string `json:"url"` //Required
}
// Fee is the structure used for different currencies allowed for claims.
type Fee struct {
LBC *FeeInfo `json:"LBC,omitempty"`
BTC *FeeInfo `json:"BTC,omitempty"`
USD *FeeInfo `json:"USD,omitempty"`
}
// Unmarshal is an implementation to unmarshal the V1 claim from json. Main addition is to check the version.
func (c *V1Claim) Unmarshal(value []byte) error {
err := json.Unmarshal(value, c)
if err != nil {
return err
} //Version can be blank for version 1
if c.Version != "" && c.Version != "0.0.1" {
err = errors.Base("Incorrect version, expected 0.0.1 found " + c.Version)
return err
}
//ToDo - restrict to required fields?
return nil
}
// Unmarshal is an implementation to unmarshal the V2 claim from json. Main addition is to check the version.
func (c *V2Claim) Unmarshal(value []byte) error {
err := json.Unmarshal(value, c)
if err != nil {
return err
}
if c.Version != "0.0.2" {
err = errors.Base("Incorrect version, expected 0.0.2 found " + c.Version)
return err
}
return nil
}
// Unmarshal is an implementation to unmarshal the V3 claim from json. Main addition is to check the version.
func (c *V3Claim) Unmarshal(value []byte) error {
err := json.Unmarshal(value, c)
if err != nil {
return err
}
if c.Version != "0.0.3" {
err = errors.Base("Incorrect version, expected 0.0.3 found " + c.Version)
return err
}
return nil
}

View file

@ -1,105 +0,0 @@
package stake
import (
"encoding/hex"
"github.com/lbryio/lbry.go/v2/extras/errors"
legacy "github.com/lbryio/types/v1/go"
pb "github.com/lbryio/types/v2/go"
"github.com/golang/protobuf/proto"
)
func (c *StakeHelper) serialized() ([]byte, error) {
serialized := c.Claim.String() + c.Support.String()
if serialized == "" {
return nil, errors.Err("not initialized")
}
if c.LegacyClaim != nil {
return proto.Marshal(c.getLegacyProtobuf())
} else if c.IsSupport() {
return proto.Marshal(c.getSupportProtobuf())
}
return proto.Marshal(c.getClaimProtobuf())
}
func (c *StakeHelper) getClaimProtobuf() *pb.Claim {
claim := &pb.Claim{
Title: c.Claim.GetTitle(),
Description: c.Claim.GetDescription(),
Thumbnail: c.Claim.GetThumbnail(),
Tags: c.Claim.GetTags(),
Languages: c.Claim.GetLanguages(),
Locations: c.Claim.GetLocations(),
}
if c.Claim.GetChannel() != nil {
claim.Type = &pb.Claim_Channel{Channel: c.Claim.GetChannel()}
} else if c.GetStream() != nil {
claim.Type = &pb.Claim_Stream{Stream: c.GetStream()}
} else if c.Claim.GetCollection() != nil {
claim.Type = &pb.Claim_Collection{Collection: c.Claim.GetCollection()}
} else if c.Claim.GetRepost() != nil {
claim.Type = &pb.Claim_Repost{Repost: c.Claim.GetRepost()}
}
return claim
}
func (c *StakeHelper) getSupportProtobuf() *pb.Support {
return &pb.Support{
Emoji: c.Support.GetEmoji(),
XXX_NoUnkeyedLiteral: struct{}{},
XXX_unrecognized: nil,
XXX_sizecache: 0,
}
}
func (c *StakeHelper) getLegacyProtobuf() *legacy.Claim {
v := c.LegacyClaim.GetVersion()
t := c.LegacyClaim.GetClaimType()
return &legacy.Claim{
Version: &v,
ClaimType: &t,
Stream: c.LegacyClaim.GetStream(),
Certificate: c.LegacyClaim.GetCertificate(),
PublisherSignature: c.LegacyClaim.GetPublisherSignature()}
}
func (c *StakeHelper) serializedHexString() (string, error) {
serialized, err := c.serialized()
if err != nil {
return "", err
}
serialized_hex := hex.EncodeToString(serialized)
return serialized_hex, nil
}
func (c *StakeHelper) serializedNoSignature() ([]byte, error) {
if c.Claim.String() == "" && c.Support.String() == "" {
return nil, errors.Err("not initialized")
}
if c.Signature == nil {
serialized, err := c.serialized()
if err != nil {
return nil, err
}
return serialized, nil
} else {
if c.LegacyClaim != nil {
clone := &legacy.Claim{}
proto.Merge(clone, c.getLegacyProtobuf())
proto.ClearAllExtensions(clone.PublisherSignature)
clone.PublisherSignature = nil
return proto.Marshal(clone)
} else if c.IsSupport() {
clone := &pb.Support{}
proto.Merge(clone, c.getSupportProtobuf())
return proto.Marshal(clone)
}
clone := &pb.Claim{}
proto.Merge(clone, c.getClaimProtobuf())
return proto.Marshal(clone)
}
}

View file

@ -1,99 +0,0 @@
package stake
import (
"crypto/sha256"
"encoding/hex"
"github.com/lbryio/lbry.go/v2/extras/errors"
"github.com/lbryio/lbry.go/v2/schema/address"
"github.com/lbryio/lbry.go/v2/schema/keys"
"github.com/btcsuite/btcd/btcec"
)
func Sign(privKey btcec.PrivateKey, channel StakeHelper, claim StakeHelper, k string) (*keys.Signature, error) {
if channel.Claim.GetChannel() == nil {
return nil, errors.Err("claim as channel is not of type channel")
}
if claim.LegacyClaim != nil {
return claim.signV1(privKey, channel, k)
}
return claim.sign(privKey, channel, k)
}
func (c *StakeHelper) sign(privKey btcec.PrivateKey, channel StakeHelper, firstInputTxID string) (*keys.Signature, error) {
txidBytes, err := hex.DecodeString(firstInputTxID)
if err != nil {
return nil, errors.Err(err)
}
metadataBytes, err := c.serialized()
if err != nil {
return nil, errors.Err(err)
}
var digest []byte
digest = append(digest, txidBytes...)
digest = append(digest, c.ClaimID...)
digest = append(digest, metadataBytes...)
hash := sha256.Sum256(digest)
hashBytes := make([]byte, len(hash))
for i, b := range hash {
hashBytes[i] = b
}
sig, err := privKey.Sign(hashBytes)
if err != nil {
return nil, errors.Err(err)
}
return &keys.Signature{*sig}, nil
}
func (c *StakeHelper) signV1(privKey btcec.PrivateKey, channel StakeHelper, claimAddress string) (*keys.Signature, error) {
metadataBytes, err := c.serializedNoSignature()
if err != nil {
return nil, errors.Err(err)
}
addressBytes, err := address.DecodeAddress(claimAddress, "lbrycrd_main")
if err != nil {
return nil, errors.Prefix("V1 signing requires claim address and the decode failed with: ", err)
}
var digest []byte
address := make([]byte, len(addressBytes))
for i, b := range addressBytes {
address[i] = b
}
digest = append(digest, address...)
digest = append(digest, metadataBytes...)
digest = append(digest, channel.ClaimID...)
hash := sha256.Sum256(digest)
hashBytes := make([]byte, len(hash))
for i, b := range hash {
hashBytes[i] = b
}
sig, err := privKey.Sign(hashBytes)
if err != nil {
return nil, errors.Err(err)
}
return &keys.Signature{Signature: *sig}, nil
}
// rev reverses a byte slice. useful for switching endian-ness
func reverseBytes(b []byte) []byte {
r := make([]byte, len(b))
for left, right := 0, len(b)-1; left < right; left, right = left+1, right-1 {
r[left], r[right] = b[right], b[left]
}
return r
}

View file

@ -1,196 +0,0 @@
package stake
import (
"encoding/hex"
"testing"
pb "github.com/lbryio/types/v2/go"
"github.com/lbryio/lbry.go/v2/schema/keys"
"github.com/btcsuite/btcd/btcec"
"gotest.tools/assert"
)
func TestSign(t *testing.T) {
privateKey, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
t.Error(err)
return
}
channel := &StakeHelper{newChannelClaim(), nil, nil, nil, NoSig, nil, nil}
pubkeyBytes, err := keys.PublicKeyToDER(privateKey.PubKey())
if err != nil {
t.Error(err)
return
}
channel.Claim.GetChannel().PublicKey = pubkeyBytes
claimID := "cf3f7c898af87cc69b06a6ac7899efb9a4878fdb" //Fake
txid := "4c1df9e022e396859175f9bfa69b38e444db10fb53355fa99a0989a83bcdb82f" //Fake
claimIDHexBytes, err := hex.DecodeString(claimID)
if err != nil {
t.Error(err)
return
}
claim := &StakeHelper{newStreamClaim(), nil, nil, reverseBytes(claimIDHexBytes), WithSig, nil, nil}
claim.Claim.Title = "Test title"
claim.Claim.Description = "Test description"
sig, err := Sign(*privateKey, *channel, *claim, txid)
if err != nil {
t.Error(err)
return
}
signatureBytes, err := sig.LBRYSDKEncode()
if err != nil {
t.Error(err)
return
}
claim.Signature = signatureBytes
rawChannel, err := channel.CompileValue()
if err != nil {
t.Error(err)
return
}
rawClaim, err := claim.CompileValue()
if err != nil {
t.Error(err)
return
}
channel, err = DecodeClaimBytes(rawChannel, "lbrycrd_main")
if err != nil {
t.Error(err)
return
}
claim, err = DecodeClaimBytes(rawClaim, "lbrycrd_main")
if err != nil {
t.Error(err)
return
}
valid, err := claim.ValidateClaimSignature(channel, txid, claimID, "lbrycrd_main")
if err != nil {
t.Error(err)
return
}
assert.Assert(t, valid, "could not verify signature")
}
func TestSignSupportWithChannel(t *testing.T) {
cert_claim_hex := "08011002225e0801100322583056301006072a8648ce3d020106052b8104000a03420004d015365a40f3e5c03c87227168e5851f44659837bcf6a3398ae633bc37d04ee19baeb26dc888003bd728146dbea39f5344bf8c52cedaf1a3a1623a0166f4a367"
channel, err := DecodeClaimHex(cert_claim_hex, "lbrycrd_main")
if err != nil {
t.Error(err)
}
privateKey, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
t.Error(err)
return
}
pubkeyBytes, err := keys.PublicKeyToDER(privateKey.PubKey())
if err != nil {
t.Error(err)
return
}
channel.Claim.GetChannel().PublicKey = pubkeyBytes
claimID := "251305ca93d4dbedb50dceb282ebcb7b07b7ac64"
txid := "4c1df9e022e396859175f9bfa69b38e444db10fb53355fa99a0989a83bcdb82f" //Fake
claimIDHexBytes, err := hex.DecodeString(claimID)
if err != nil {
t.Error(err)
return
}
support := &StakeHelper{nil, &pb.Support{}, nil, reverseBytes(claimIDHexBytes), WithSig, nil, nil}
sig, err := Sign(*privateKey, *channel, *support, txid)
if err != nil {
t.Error(err)
return
}
signatureBytes, err := sig.LBRYSDKEncode()
if err != nil {
t.Error(err)
return
}
support.Signature = signatureBytes
compiledSupport, err := support.CompileValue()
if err != nil {
t.Error(err)
}
support, err = DecodeSupportBytes(compiledSupport, "lbrycrd_main")
valid, err := support.ValidateClaimSignature(channel, txid, claimID, "lbrycrd_main")
if err != nil {
t.Error(err)
return
}
assert.Assert(t, valid, "could not verify signature")
}
func TestSignWithV1Channel(t *testing.T) {
cert_claim_hex := "08011002225e0801100322583056301006072a8648ce3d020106052b8104000a03420004d015365a40f3e5c03c87227168e5851f44659837bcf6a3398ae633bc37d04ee19baeb26dc888003bd728146dbea39f5344bf8c52cedaf1a3a1623a0166f4a367"
channel, err := DecodeClaimHex(cert_claim_hex, "lbrycrd_main")
if err != nil {
t.Error(err)
}
privateKey, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
t.Error(err)
return
}
pubkeyBytes, err := keys.PublicKeyToDER(privateKey.PubKey())
if err != nil {
t.Error(err)
return
}
channel.Claim.GetChannel().PublicKey = pubkeyBytes
claimID := "251305ca93d4dbedb50dceb282ebcb7b07b7ac64"
txid := "4c1df9e022e396859175f9bfa69b38e444db10fb53355fa99a0989a83bcdb82f" //Fake
claimIDHexBytes, err := hex.DecodeString(claimID)
if err != nil {
t.Error(err)
return
}
claim := &StakeHelper{newStreamClaim(), nil, nil, reverseBytes(claimIDHexBytes), WithSig, nil, nil}
claim.Claim.Title = "Test title"
claim.Claim.Description = "Test description"
sig, err := Sign(*privateKey, *channel, *claim, txid)
if err != nil {
t.Error(err)
return
}
signatureBytes, err := sig.LBRYSDKEncode()
if err != nil {
t.Error(err)
return
}
claim.Signature = signatureBytes
compiledClaim, err := claim.CompileValue()
if err != nil {
t.Error(err)
}
claim, err = DecodeClaimBytes(compiledClaim, "lbrycrd_main")
valid, err := claim.ValidateClaimSignature(channel, txid, claimID, "lbrycrd_main")
if err != nil {
t.Error(err)
return
}
assert.Assert(t, valid, "could not verify signature")
}

View file

@ -1,317 +0,0 @@
package stake
import (
"encoding/hex"
"strconv"
"github.com/lbryio/lbry.go/v2/extras/errors"
"github.com/lbryio/lbry.go/v2/schema/address"
"github.com/lbryio/lbry.go/v2/schema/keys"
legacy_pb "github.com/lbryio/types/v1/go"
pb "github.com/lbryio/types/v2/go"
"github.com/btcsuite/btcd/btcec"
"github.com/golang/protobuf/proto"
)
type version byte
func (v version) byte() byte {
return byte(v)
}
const (
NoSig = version(byte(0))
//Signature using ECDSA SECP256k1 key and SHA-256 hash.
WithSig = version(byte(1))
UNKNOWN = version(byte(2))
)
type StakeHelper struct {
Claim *pb.Claim
Support *pb.Support
LegacyClaim *legacy_pb.Claim
ClaimID []byte
Version version
Signature []byte
Payload []byte
}
const migrationErrorMessage = "migration from v1 to v2 protobuf failed with: "
func (c *StakeHelper) ValidateAddresses(blockchainName string) error {
if c.Claim != nil { // V2
// check the validity of a fee address
if c.Claim.GetStream() != nil {
fee := c.GetStream().GetFee()
if fee != nil {
return validateAddress(fee.GetAddress(), blockchainName)
} else {
return nil
}
} else if c.Claim.GetChannel() != nil {
return nil
}
}
return errors.Err("claim helper created with migrated v2 protobuf claim 'invalid state'")
}
func validateAddress(tmp_addr []byte, blockchainName string) error {
if len(tmp_addr) != 25 {
return errors.Err("invalid address length: " + strconv.FormatInt(int64(len(tmp_addr)), 10) + "!")
}
addr := [25]byte{}
for i := range addr {
addr[i] = tmp_addr[i]
}
_, err := address.EncodeAddress(addr, blockchainName)
if err != nil {
return errors.Err(err)
}
return nil
}
func getVersionFromByte(versionByte byte) version {
if versionByte == byte(0) {
return NoSig
} else if versionByte == byte(1) {
return WithSig
}
return UNKNOWN
}
func (c *StakeHelper) ValidateCertificate() error {
if !c.IsClaim() || c.Claim.GetChannel() == nil {
return nil
}
_, err := c.GetPublicKey()
if err != nil {
return errors.Err(err)
}
return nil
}
func (c *StakeHelper) IsClaim() bool {
return c.Claim != nil && c.Claim.String() != ""
}
func (c *StakeHelper) IsSupport() bool {
return c.Support != nil
}
func (c *StakeHelper) LoadFromBytes(raw_claim []byte, blockchainName string) error {
return c.loadFromBytes(raw_claim, false, blockchainName)
}
func (c *StakeHelper) LoadSupportFromBytes(raw_claim []byte, blockchainName string) error {
return c.loadFromBytes(raw_claim, true, blockchainName)
}
func (c *StakeHelper) loadFromBytes(raw_claim []byte, isSupport bool, blockchainName string) error {
if c.Claim.String() != "" && !isSupport {
return errors.Err("already initialized")
}
if len(raw_claim) < 1 {
return errors.Err("there is nothing to decode")
}
var claim_pb *pb.Claim
var legacy_claim_pb *legacy_pb.Claim
var support_pb *pb.Support
version := getVersionFromByte(raw_claim[0]) //First byte = version
pbPayload := raw_claim[1:]
var claimID []byte
var signature []byte
if version == WithSig {
if len(raw_claim) < 85 {
return errors.Err("signature version indicated by 1st byte but not enough bytes for valid format")
}
claimID = raw_claim[1:21] // channel claimid = next 20 bytes
signature = raw_claim[21:85] // signature = next 64 bytes
pbPayload = raw_claim[85:] // protobuf payload = remaining bytes
}
var err error
if !isSupport {
claim_pb = &pb.Claim{}
err = proto.Unmarshal(pbPayload, claim_pb)
} else {
support := &pb.Support{}
err = proto.Unmarshal(pbPayload, support)
if err == nil {
support_pb = support
}
}
if err != nil {
legacy_claim_pb = &legacy_pb.Claim{}
legacyErr := proto.Unmarshal(raw_claim, legacy_claim_pb)
if legacyErr == nil {
claim_pb, err = migrateV1PBClaim(*legacy_claim_pb)
if err != nil {
return errors.Prefix(migrationErrorMessage, err)
}
if legacy_claim_pb.GetPublisherSignature() != nil {
version = WithSig
claimID = legacy_claim_pb.GetPublisherSignature().GetCertificateId()
signature = legacy_claim_pb.GetPublisherSignature().GetSignature()
}
if legacy_claim_pb.GetCertificate() != nil {
version = NoSig
}
} else {
return err
}
}
*c = StakeHelper{
Claim: claim_pb,
Support: support_pb,
LegacyClaim: legacy_claim_pb,
ClaimID: claimID,
Version: version,
Signature: signature,
Payload: pbPayload,
}
// Commenting out because of a bug in SDK release allowing empty addresses.
//err = c.ValidateAddresses(blockchainName)
//if err != nil {
// return err
//}
err = c.ValidateCertificate()
if err != nil {
return err
}
return nil
}
func (c *StakeHelper) LoadFromHexString(claim_hex string, blockchainName string) error {
buf, err := hex.DecodeString(claim_hex)
if err != nil {
return err
}
return c.LoadFromBytes(buf, blockchainName)
}
func (c *StakeHelper) LoadSupportFromHexString(claim_hex string, blockchainName string) error {
buf, err := hex.DecodeString(claim_hex)
if err != nil {
return err
}
return c.LoadSupportFromBytes(buf, blockchainName)
}
func DecodeClaimProtoBytes(serialized []byte, blockchainName string) (*StakeHelper, error) {
claim := &StakeHelper{&pb.Claim{}, &pb.Support{}, nil, nil, NoSig, nil, nil}
err := claim.LoadFromBytes(serialized, blockchainName)
if err != nil {
return nil, err
}
return claim, nil
}
func DecodeSupportProtoBytes(serialized []byte, blockchainName string) (*StakeHelper, error) {
claim := &StakeHelper{nil, &pb.Support{}, nil, nil, NoSig, nil, nil}
err := claim.LoadSupportFromBytes(serialized, blockchainName)
if err != nil {
return nil, err
}
return claim, nil
}
func DecodeClaimHex(serialized string, blockchainName string) (*StakeHelper, error) {
claim_bytes, err := hex.DecodeString(serialized)
if err != nil {
return nil, errors.Err(err)
}
return DecodeClaimBytes(claim_bytes, blockchainName)
}
// DecodeClaimBytes take a byte array and tries to decode it to a protobuf claim or migrate it from either json v1,2,3 or pb v1
func DecodeClaimBytes(serialized []byte, blockchainName string) (*StakeHelper, error) {
helper, err := DecodeClaimProtoBytes(serialized, blockchainName)
if err == nil {
return helper, nil
}
helper = &StakeHelper{}
//If protobuf fails, try json versions before returning an error.
v1Claim := new(V1Claim)
err = v1Claim.Unmarshal(serialized)
if err != nil {
v2Claim := new(V2Claim)
err := v2Claim.Unmarshal(serialized)
if err != nil {
v3Claim := new(V3Claim)
err := v3Claim.Unmarshal(serialized)
if err != nil {
return nil, errors.Prefix("Claim value has no matching version", err)
}
helper.Claim, err = migrateV3Claim(*v3Claim)
if err != nil {
return nil, errors.Prefix("V3 Metadata Migration Error", err)
}
return helper, nil
}
helper.Claim, err = migrateV2Claim(*v2Claim)
if err != nil {
return nil, errors.Prefix("V2 Metadata Migration Error ", err)
}
return helper, nil
}
helper.Claim, err = migrateV1Claim(*v1Claim)
if err != nil {
return nil, errors.Prefix("V1 Metadata Migration Error ", err)
}
return helper, nil
}
// DecodeSupportBytes take a byte array and tries to decode it to a protobuf support
func DecodeSupportBytes(serialized []byte, blockchainName string) (*StakeHelper, error) {
helper, err := DecodeSupportProtoBytes(serialized, blockchainName)
if err != nil {
return nil, errors.Err(err)
}
return helper, nil
}
func (c *StakeHelper) GetStream() *pb.Stream {
if c != nil {
return c.Claim.GetStream()
}
return nil
}
func (c *StakeHelper) CompileValue() ([]byte, error) {
payload, err := c.serialized()
if err != nil {
return nil, err
}
var value []byte
value = append(value, c.Version.byte())
if c.Version == WithSig {
value = append(value, c.ClaimID...)
value = append(value, c.Signature...)
}
value = append(value, payload...)
return value, nil
}
func (c *StakeHelper) GetPublicKey() (*btcec.PublicKey, error) {
if c.IsClaim() {
if c.Claim.GetChannel() == nil {
return nil, errors.Err("claim is not of type channel, so there is no public key to get")
}
} else if c.IsSupport() {
return nil, errors.Err("stake is a support and does not come with a public key to get")
}
return keys.GetPublicKeyFromBytes(c.Claim.GetChannel().PublicKey)
}

View file

@ -1,33 +0,0 @@
package stake
import "testing"
func TestClaimHelper(t *testing.T) {
for _, rawClaim := range raw_claims {
helper, err := DecodeClaimHex(rawClaim, "lbrycrd_main")
if err != nil {
t.Error(err)
}
_, err = helper.RenderJSON()
if err != nil {
t.Error(err)
}
_, err = helper.serialized()
if err != nil {
t.Error(err)
}
_, err = helper.serializedHexString()
if err != nil {
t.Error(err)
}
_, err = helper.serializedNoSignature()
if err != nil {
t.Error(err)
}
err = helper.ValidateAddresses("lbrycrd_main")
if err != nil {
t.Error(err)
}
}
}

View file

@ -1,120 +0,0 @@
package stake
import (
"crypto/ecdsa"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"math/big"
"github.com/lbryio/lbry.go/v2/extras/errors"
"github.com/lbryio/lbry.go/v2/schema/address"
)
const SECP256k1 = "SECP256k1"
//const NIST256p = "NIST256p"
//const NIST384p = "NIST384p"
func getClaimSignatureDigest(bytes ...[]byte) [32]byte {
var combined []byte
for _, b := range bytes {
combined = append(combined, b...)
}
digest := sha256.Sum256(combined)
return [32]byte(digest)
}
func (c *StakeHelper) VerifyDigest(certificate *StakeHelper, signature [64]byte, digest [32]byte) bool {
if certificate == nil {
return false
}
R := &big.Int{}
S := &big.Int{}
R.SetBytes(signature[0:32])
S.SetBytes(signature[32:64])
pk, err := certificate.GetPublicKey()
if err != nil {
return false
}
return ecdsa.Verify(pk.ToECDSA(), digest[:], R, S)
}
func (c *StakeHelper) ValidateClaimSignature(certificate *StakeHelper, k string, certificateId string, blockchainName string) (bool, error) {
if c.LegacyClaim != nil {
return c.validateV1ClaimSignature(certificate, k, certificateId, blockchainName)
}
return c.validateClaimSignature(certificate, k, certificateId, blockchainName)
}
func (c *StakeHelper) validateClaimSignature(certificate *StakeHelper, firstInputTxHash, certificateId string, blockchainName string) (bool, error) {
certificateIdSlice, err := hex.DecodeString(certificateId)
if err != nil {
return false, errors.Err(err)
}
certificateIdSlice = reverseBytes(certificateIdSlice)
firstInputTxIDBytes, err := hex.DecodeString(firstInputTxHash)
if err != nil {
return false, errors.Err(err)
}
signature := c.Signature
if signature == nil {
return false, errors.Err("claim does not have a signature")
}
signatureBytes := [64]byte{}
for i, b := range signature {
signatureBytes[i] = b
}
claimDigest := getClaimSignatureDigest(firstInputTxIDBytes, certificateIdSlice, c.Payload)
return c.VerifyDigest(certificate, signatureBytes, claimDigest), nil
}
func (c *StakeHelper) validateV1ClaimSignature(certificate *StakeHelper, claimAddy string, certificateId string, blockchainName string) (bool, error) {
addressBytes, err := address.DecodeAddress(claimAddy, blockchainName)
if err != nil {
return false, err
}
//For V1 claim_id was incorrectly stored for claim signing.
// So the bytes are not reversed like they are supposed to be (Endianess)
certificateIdSlice, err := hex.DecodeString(certificateId)
if err != nil {
return false, err
}
signature := c.Signature
if signature == nil {
return false, errors.Err("claim does not have a signature")
}
signatureBytes := [64]byte{}
for i := range signatureBytes {
signatureBytes[i] = signature[i]
}
claimAddress, err := address.ValidateAddress(addressBytes, blockchainName)
if err != nil {
return false, errors.Err("invalid address")
}
serializedNoSig, err := c.serializedNoSignature()
if err != nil {
return false, errors.Err("serialization error")
}
claimDigest := getClaimSignatureDigest(claimAddress[:], serializedNoSig, certificateIdSlice)
return c.VerifyDigest(certificate, signatureBytes, claimDigest), nil
}
func GetOutpointHash(txid string, vout uint32) (string, error) {
txidBytes, err := hex.DecodeString(txid)
if err != nil {
return "", errors.Err(err)
}
var voutBytes = make([]byte, 4)
binary.LittleEndian.PutUint32(voutBytes, vout)
return hex.EncodeToString(append(reverseBytes(txidBytes), voutBytes...)), nil
}

View file

@ -1,94 +0,0 @@
package stake
import (
"testing"
"gotest.tools/assert"
)
func TestV1ValidateClaimSignature(t *testing.T) {
cert_claim_hex := "08011002225e0801100322583056301006072a8648ce3d020106052b8104000a03420004d015365a40f3e5c03c87227168e5851f44659837bcf6a3398ae633bc37d04ee19baeb26dc888003bd728146dbea39f5344bf8c52cedaf1a3a1623a0166f4a367"
signed_claim_hex := "080110011ad7010801128f01080410011a0c47616d65206f66206c696665221047616d65206f66206c696665206769662a0b4a6f686e20436f6e776179322e437265617469766520436f6d6d6f6e73204174747269627574696f6e20342e3020496e7465726e6174696f6e616c38004224080110011a195569c917f18bf5d2d67f1346aa467b218ba90cdbf2795676da250000803f4a0052005a001a41080110011a30b6adf6e2a62950407ea9fb045a96127b67d39088678d2f738c359894c88d95698075ee6203533d3c204330713aa7acaf2209696d6167652f6769662a5c080110031a40c73fe1be4f1743c2996102eec6ce0509e03744ab940c97d19ddb3b25596206367ab1a3d2583b16c04d2717eeb983ae8f84fee2a46621ffa5c4726b30174c6ff82214251305ca93d4dbedb50dceb282ebcb7b07b7ac65"
signed_claim, err := DecodeClaimHex(signed_claim_hex, "lbrycrd_main")
if err != nil {
t.Error(err)
}
cert_claim, err := DecodeClaimHex(cert_claim_hex, "lbrycrd_main")
if err != nil {
t.Error(err)
}
claim_addr := "bSkUov7HMWpYBiXackDwRnR5ishhGHvtJt"
cert_id := "251305ca93d4dbedb50dceb282ebcb7b07b7ac65"
result, err := signed_claim.ValidateClaimSignature(cert_claim, claim_addr, cert_id, "lbrycrd_main")
if err != nil {
t.Error(err)
}
if result != true {
t.Error("failed to validate signature:", result)
}
}
func TestV1FailToValidateClaimSignature(t *testing.T) {
cert_claim_hex := "08011002225e0801100322583056301006072a8648ce3d020106052b8104000a03420004d015365a40f3e5c03c87227168e5851f44659837bcf6a3398ae633bc37d04ee19baeb26dc888003bd728146dbea39f5344bf8c52cedaf1a3a1623a0166f4a367"
signed_claim_hex := "080110011ad7010801128f01080410011a0c47616d65206f66206c696665221047616d65206f66206c696665206769662a0b4a6f686e20436f6e776179322e437265617469766520436f6d6d6f6e73204174747269627574696f6e20342e3020496e7465726e6174696f6e616c38004224080110011a195569c917f18bf5d2d67f1346aa467b218ba90cdbf2795676da250000803f4a0052005a001a41080110011a30b6adf6e2a62950407ea9fb045a96127b67d39088678d2f738c359894c88d95698075ee6203533d3c204330713aa7acaf2209696d6167652f6769662a5c080110031a40c73fe1be4f1743c2996102eec6ce0509e03744ab940c97d19ddb3b25596206367ab1a3d2583b16c04d2717eeb983ae8f84fee2a46621ffa5c4726b30174c6ff82214251305ca93d4dbedb50dceb282ebcb7b07b7ac65"
signed_claim, err := DecodeClaimHex(signed_claim_hex, "lbrycrd_main")
if err != nil {
t.Error(err)
}
cert_claim, err := DecodeClaimHex(cert_claim_hex, "lbrycrd_main")
if err != nil {
t.Error(err)
}
claim_addr := "bSkUov7HMWpYBiXackDwRnR5ishhGHvtJt"
cert_id := "251305ca93d4dbedb50dceb282ebcb7b07b7ac64"
result, err := signed_claim.ValidateClaimSignature(cert_claim, claim_addr, cert_id, "lbrycrd_main")
if err != nil {
t.Error(err)
}
if result != false {
t.Error("failed to validate signature:", result)
}
}
func TestV2ValidateClaimSignature(t *testing.T) {
cert_claim_hex := "00125a0a583056301006072a8648ce3d020106052b8104000a034200045a0343c155302280da01ae0001b7295241eb03c42a837acf92ccb9680892f7db50fd1d3c14b28bb594e304f05fc4ae7c1f222a85d1d1a3461b3cfb9906f66cb5"
signed_claim_hex := "015cb78e424a34fbf79b67f9107430427aa62373e69b4998a29ecec8f14a9e0a213a043ced8064c069d7e464b5fd3ccb92b45bd59b15c0e1bb27e3c366d43f86a9a6b5ad42647a1aad69a73ac50b19ae3ec978c2c70aa2010a99010a301c662f19abc461e7eddecf165adfa7fca569e209773f3db31241c1e297f0a8d5b3e4768828b065fbeb1d6776f61073f6121b3031202d20556e6d6173746572656420496d70756c7365732e377a187a22146170706c69636174696f6e2f782d6578742d377a32302eb61ea475017e28c013616a56c1219ba90dc35fffff453d9675146f648f66634e0d1516528d37aba9f5801229d9f2181a044e6f6e6542087465737420707562520062020801"
signed_claim, err := DecodeClaimHex(signed_claim_hex, "lbrycrd_main")
if err != nil {
t.Error(err)
}
cert_claim, err := DecodeClaimHex(cert_claim_hex, "lbrycrd_main")
if err != nil {
t.Error(err)
}
firstInputTxHash, err := GetOutpointHash("becb96a4a2e66bd24f083772fe9da904654ea9b5f07cc5bfbee233355911ddb1", uint32(0))
if err != nil {
t.Error(err)
}
cert_id := "e67323a67a42307410f9679bf7fb344a428eb75c"
result, err := signed_claim.ValidateClaimSignature(cert_claim, firstInputTxHash, cert_id, "lbrycrd_main")
if err != nil {
t.Error(err)
}
if result != true {
t.Error("failed to validate signature:", result)
}
}
func TestGetOutpointHash(t *testing.T) {
hash, err := GetOutpointHash("dc3dcf2f94d3c91e454ac2474802e20f26b30705372dda43890c811d918aef64", 1)
if err != nil {
t.Error(err)
}
assert.Assert(t, hash == "64ef8a911d810c8943da2d370507b3260fe2024847c24a451ec9d3942fcf3ddc01000000", uint(1))
}

View file

@ -1 +0,0 @@
go test ./... -v

View file

@ -1,5 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
go install github.com/caarlos0/svu@latest GO111MODULE=off go get github.com/caarlos0/svu
git tag `svu "$1"` git tag `svu "$1"`
git push --tags git push --tags

View file

@ -35,7 +35,7 @@ func (b Blob) Hash() []byte {
return hashBytes[:] return hashBytes[:]
} }
// HashHex returns the blob hash as a hex string // HashHex returns th blob hash as a hex string
func (b Blob) HashHex() string { func (b Blob) HashHex() string {
return hex.EncodeToString(b.Hash()) return hex.EncodeToString(b.Hash())
} }

View file

@ -8,7 +8,6 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"strconv" "strconv"
"strings"
) )
const streamTypeLBRYFile = "lbryfile" const streamTypeLBRYFile = "lbryfile"
@ -45,39 +44,10 @@ type SDBlob struct {
StreamHash []byte `json:"-"` StreamHash []byte `json:"-"`
} }
// Hash returns a hash of the SD blob data
func (s SDBlob) Hash() []byte {
hashBytes := sha512.Sum384(s.ToBlob())
return hashBytes[:]
}
// HashHex returns the SD blob hash as a hex string
func (s SDBlob) HashHex() string {
return hex.EncodeToString(s.Hash())
}
// ToJson returns the SD blob hash as JSON
func (s SDBlob) ToJson() string {
j, err := json.MarshalIndent(s, "", " ")
if err != nil {
panic(err)
}
return string(j)
}
// ToBlob converts the SDBlob to a normal data Blob // ToBlob converts the SDBlob to a normal data Blob
func (s SDBlob) ToBlob() Blob { func (s SDBlob) ToBlob() (Blob, error) {
jsonSD, err := json.Marshal(s) b, err := json.Marshal(s)
if err != nil { return Blob(b), err
panic(err)
}
// COMPATIBILITY HACK to make json output match python's json. this can be
// removed when we implement canonical JSON encoding
jsonSD = []byte(strings.Replace(string(jsonSD), ",", ", ", -1))
jsonSD = []byte(strings.Replace(string(jsonSD), ":", ": ", -1))
return jsonSD
} }
// FromBlob unmarshals a data Blob that should contain SDBlob data // FromBlob unmarshals a data Blob that should contain SDBlob data
@ -85,6 +55,30 @@ func (s *SDBlob) FromBlob(b Blob) error {
return json.Unmarshal(b, s) return json.Unmarshal(b, s)
} }
func newSdBlob(blobs []Blob, key []byte, ivs [][]byte, streamName, suggestedFilename string) *SDBlob {
if len(ivs) != len(blobs)+1 { // +1 for terminating 0-length blob
panic("wrong number of IVs provided")
}
sd := &SDBlob{
StreamType: streamTypeLBRYFile,
StreamName: streamName,
SuggestedFileName: suggestedFilename,
Key: key,
}
for i, b := range blobs {
sd.addBlob(b, ivs[i])
}
// terminating blob
sd.addBlob(Blob{}, ivs[len(ivs)-1])
sd.updateStreamHash()
return sd
}
// addBlob adds the blob's info to stream // addBlob adds the blob's info to stream
func (s *SDBlob) addBlob(b Blob, iv []byte) { func (s *SDBlob) addBlob(b Blob, iv []byte) {
if len(iv) == 0 { if len(iv) == 0 {

View file

@ -2,10 +2,8 @@ package stream
import ( import (
"bytes" "bytes"
"crypto/sha512"
"hash"
"io"
"math" "math"
"strings"
"github.com/lbryio/lbry.go/v2/extras/errors" "github.com/lbryio/lbry.go/v2/extras/errors"
) )
@ -15,22 +13,66 @@ type Stream []Blob
// -1 to leave room for padding, since there must be at least one byte of pkcs7 padding // -1 to leave room for padding, since there must be at least one byte of pkcs7 padding
const maxBlobDataSize = MaxBlobSize - 1 const maxBlobDataSize = MaxBlobSize - 1
// New creates a new Stream from a stream of bytes. // New creates a new Stream from a byte slice
func New(src io.Reader) (Stream, error) { func New(data []byte) (Stream, error) {
return NewEncoder(src).Stream() key := randIV()
ivs := make([][]byte, numContentBlobs(data)+1) // +1 for terminating 0-length blob
for i := range ivs {
ivs[i] = randIV()
}
return makeStream(data, key, ivs, "", "")
}
// Reconstruct creates a stream from the given data using predetermined IVs and key from the SD blob
// NOTE: this will assume that all blobs except the last one are at max length. in theory this is not
// required, but in practice this is always true. if this is false, streams may not match exactly
func Reconstruct(data []byte, sdBlob SDBlob) (Stream, error) {
ivs := make([][]byte, len(sdBlob.BlobInfos))
for i := range ivs {
ivs[i] = sdBlob.BlobInfos[i].IV
}
return makeStream(data, sdBlob.Key, ivs, sdBlob.StreamName, sdBlob.SuggestedFileName)
}
func makeStream(data, key []byte, ivs [][]byte, streamName, suggestedFilename string) (Stream, error) {
var err error
numBlobs := numContentBlobs(data)
if len(ivs) != numBlobs+1 { // +1 for terminating 0-length blob
return nil, errors.Err("incorrect number of IVs provided")
}
s := make(Stream, numBlobs+1) // +1 for sd blob
for i := 0; i < numBlobs; i++ {
start := i * maxBlobDataSize
end := start + maxBlobDataSize
if end > len(data) {
end = len(data)
}
s[i+1], err = NewBlob(data[start:end], key, ivs[i])
if err != nil {
return nil, err
}
}
sd := newSdBlob(s[1:], key, ivs, streamName, suggestedFilename)
jsonSD, err := sd.ToBlob()
if err != nil {
return nil, err
}
// COMPATIBILITY HACK to make json output match python's json. this can be
// removed when we implement canonical JSON encoding
jsonSD = []byte(strings.Replace(string(jsonSD), ",", ", ", -1))
jsonSD = []byte(strings.Replace(string(jsonSD), ":", ": ", -1))
s[0] = jsonSD
return s, nil
} }
// Data returns the file data that a stream encapsulates.
//
// Deprecated: use Decode() instead. It's a more accurate name. Data() will be removed in the future.
func (s Stream) Data() ([]byte, error) { func (s Stream) Data() ([]byte, error) {
return s.Decode()
}
// Decode returns the file data that a stream encapsulates
//
// TODO: this should use io.Writer instead of returning bytes
func (s Stream) Decode() ([]byte, error) {
if len(s) < 2 { if len(s) < 2 {
return nil, errors.Err("stream must be at least 2 blobs long") // sd blob and content blob return nil, errors.Err("stream must be at least 2 blobs long") // sd blob and content blob
} }
@ -82,161 +124,7 @@ func (s Stream) Decode() ([]byte, error) {
return file, nil return file, nil
} }
// Encoder reads bytes from a source and returns blobs of the stream //numContentBlobs returns the number of content blobs required to store the data
type Encoder struct { func numContentBlobs(data []byte) int {
// source data to be encoded into a stream return int(math.Ceil(float64(len(data)) / float64(maxBlobDataSize)))
src io.Reader
// preset IVs to use for encrypting blobs
ivs [][]byte
// an optionals hint about the total size of the source data
// encoder will use this to preallocate space for blobs
srcSizeHint int
// buffer for reading bytes from reader
buf []byte
// sd blob that gets built as stream is encoded
sd *SDBlob
// number of bytes read from src
srcLen int
// running hash bytes read from src
srcHash hash.Hash
}
// NewEncoder creates a new stream encoder
func NewEncoder(src io.Reader) *Encoder {
return &Encoder{
src: src,
buf: make([]byte, maxBlobDataSize),
sd: &SDBlob{
StreamType: streamTypeLBRYFile,
Key: randIV(),
},
srcHash: sha512.New384(),
}
}
// NewEncoderWithIVs creates a new encoder that uses preset cryptographic material
func NewEncoderWithIVs(src io.Reader, key []byte, ivs [][]byte) *Encoder {
e := NewEncoder(src)
e.sd.Key = key
e.ivs = ivs
return e
}
// NewEncoderFromSD creates a new encoder that reuses cryptographic material from an sd blob
// This can be used to reconstruct a stream exactly from a file
// NOTE: this will assume that all blobs except the last one are at max length. in theory this is not
// required, but in practice this is always true. if this is false, streams may not match exactly
func NewEncoderFromSD(src io.Reader, sdBlob *SDBlob) *Encoder {
ivs := make([][]byte, len(sdBlob.BlobInfos))
for i := range ivs {
ivs[i] = sdBlob.BlobInfos[i].IV
}
e := NewEncoderWithIVs(src, sdBlob.Key, ivs)
e.sd.StreamName = sdBlob.StreamName
e.sd.SuggestedFileName = sdBlob.SuggestedFileName
return e
}
// TODO: consider making a NewPartialEncoder that also copies blobinfos from sdBlobs and seeks forward in the data
// this would avoid re-creating blobs that were created in the past
// Next reads the next chunk of data, encodes it into a blob, and adds it to the stream
// When the source is fully consumed, Next() makes sure the stream is terminated (i.e. the sd blob
// ends with an empty terminating blob) and returns io.EOF
func (e *Encoder) Next() (Blob, error) {
n, err := e.src.Read(e.buf)
if err != nil {
if errors.Is(err, io.EOF) {
e.ensureTerminated()
}
return nil, err
}
e.srcLen += n
e.srcHash.Write(e.buf[:n])
iv := e.nextIV()
blob, err := NewBlob(e.buf[:n], e.sd.Key, iv)
if err != nil {
return nil, err
}
e.sd.addBlob(blob, iv)
return blob, nil
}
// Stream creates the whole stream in one call
func (e *Encoder) Stream() (Stream, error) {
s := make(Stream, 1, 1+int(math.Ceil(float64(e.srcSizeHint)/maxBlobDataSize))) // len starts at 1 and cap is +1 to leave room for sd blob
for {
blob, err := e.Next()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, err
}
s = append(s, blob)
}
s[0] = e.SDBlob().ToBlob()
if cap(s) > len(s) {
// size hint was too big. copy stream to smaller underlying array to free memory
// this might be premature optimization...
s = append(Stream(nil), s[:]...)
}
return s, nil
}
// SDBlob returns the sd blob so far
func (e *Encoder) SDBlob() *SDBlob {
e.sd.updateStreamHash()
return e.sd
}
// SourceLen returns the number of bytes read from source
func (e *Encoder) SourceLen() int {
return e.srcLen
}
// SourceLen returns a hash of the bytes read from source
func (e *Encoder) SourceHash() []byte {
return e.srcHash.Sum(nil)
}
// SourceSizeHint sets a hint about the total size of the source
// This helps allocate RAM more efficiently.
// If the hint is wrong, it still works fine but there will be a small performance penalty.
func (e *Encoder) SourceSizeHint(size int) *Encoder {
e.srcSizeHint = size
return e
}
func (e *Encoder) isTerminated() bool {
return len(e.sd.BlobInfos) >= 1 && e.sd.BlobInfos[len(e.sd.BlobInfos)-1].Length == 0
}
func (e *Encoder) ensureTerminated() {
if !e.isTerminated() {
e.sd.addBlob(Blob{}, e.nextIV())
}
}
// nextIV returns the next preset IV if there is one
func (e *Encoder) nextIV() []byte {
if len(e.ivs) == 0 {
return randIV()
}
iv := e.ivs[0]
e.ivs = e.ivs[1:]
return iv
} }

View file

@ -2,31 +2,26 @@ package stream
import ( import (
"bytes" "bytes"
"crypto/rand"
"crypto/sha256" "crypto/sha256"
"crypto/sha512"
"encoding/hex" "encoding/hex"
"io"
"testing" "testing"
"github.com/lbryio/lbry.go/v2/extras/errors"
) )
var testdataBlobHashes = []string{
"1bf7d39c45d1a38ffa74bff179bf7f67d400ff57fa0b5a0308963f08d01712b3079530a8c188e8c89d9b390c6ee06f05", // sd hash
"a2f1841bb9c5f3b583ac3b8c07ee1a5bf9cc48923721c30d5ca6318615776c284e8936d72fa4db7fdda2e4e9598b1e6c",
"0c9675ad7f40f29dcd41883ed9cf7e145bbb13976d9b83ab9354f4f61a87f0f7771a56724c2aa7a5ab43c68d7942e5cb",
"a4d07d442b9907036c75b6c92db316a8b8428733bf5ec976627a48a7c862bf84db33075d54125a7c0b297bd2dc445f1c",
"dcd2093f4a3eca9f6dd59d785d0bef068fee788481986aa894cf72ed4d992c0ff9d19d1743525de2f5c3c62f5ede1c58",
}
func TestStreamToFile(t *testing.T) { func TestStreamToFile(t *testing.T) {
stream := make(Stream, len(testdataBlobHashes)) blobHashes := []string{
for i, hash := range testdataBlobHashes { "1bf7d39c45d1a38ffa74bff179bf7f67d400ff57fa0b5a0308963f08d01712b3079530a8c188e8c89d9b390c6ee06f05", // sd hash
"a2f1841bb9c5f3b583ac3b8c07ee1a5bf9cc48923721c30d5ca6318615776c284e8936d72fa4db7fdda2e4e9598b1e6c",
"0c9675ad7f40f29dcd41883ed9cf7e145bbb13976d9b83ab9354f4f61a87f0f7771a56724c2aa7a5ab43c68d7942e5cb",
"a4d07d442b9907036c75b6c92db316a8b8428733bf5ec976627a48a7c862bf84db33075d54125a7c0b297bd2dc445f1c",
"dcd2093f4a3eca9f6dd59d785d0bef068fee788481986aa894cf72ed4d992c0ff9d19d1743525de2f5c3c62f5ede1c58",
}
stream := make(Stream, len(blobHashes))
for i, hash := range blobHashes {
stream[i] = testdata(t, hash) stream[i] = testdata(t, hash)
} }
data, err := stream.Decode() data, err := stream.Data()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -38,8 +33,6 @@ func TestStreamToFile(t *testing.T) {
t.Errorf("file length mismatch. got %d, expected %d", actualLen, expectedLen) t.Errorf("file length mismatch. got %d, expected %d", actualLen, expectedLen)
} }
expectedFileHash := sha512.Sum384(data)
expectedSha256 := unhex(t, "51e4d03bd6d69ea17d1be3ce01fdffa44ffe053f2dbce8d42a50283b2890fea2") expectedSha256 := unhex(t, "51e4d03bd6d69ea17d1be3ce01fdffa44ffe053f2dbce8d42a50283b2890fea2")
actualSha256 := sha256.Sum256(data) actualSha256 := sha256.Sum256(data)
@ -53,150 +46,22 @@ func TestStreamToFile(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
enc := NewEncoderFromSD(bytes.NewBuffer(data), sdBlob) newStream, err := Reconstruct(data, *sdBlob)
newStream, err := enc.Stream() if err != nil {
t.Fatal(err)
if len(newStream) != len(testdataBlobHashes) {
t.Fatalf("stream length mismatch. got %d blobs, expected %d", len(newStream), len(testdataBlobHashes))
} }
if enc.SourceLen() != expectedLen { if len(newStream) != len(blobHashes) {
t.Errorf("reconstructed file length mismatch. got %d, expected %d", enc.SourceLen(), expectedLen) t.Fatalf("stream length mismatch. got %d blobs, expected %d", len(newStream), len(blobHashes))
} }
if !bytes.Equal(enc.SourceHash(), expectedFileHash[:]) { for i, hash := range blobHashes {
t.Errorf("reconstructed file hash mismatch. got %s, expected %s", hex.EncodeToString(enc.SourceHash()), hex.EncodeToString(expectedFileHash[:]))
}
for i, hash := range testdataBlobHashes {
if newStream[i].HashHex() != hash { if newStream[i].HashHex() != hash {
t.Errorf("blob %d hash mismatch. got %s, expected %s", i, newStream[i].HashHex(), hash) t.Errorf("blob %d hash mismatch. got %s, expected %s", i, newStream[i].HashHex(), hash)
} }
} }
} }
func TestMakeStream(t *testing.T) {
blobsToRead := 3
totalBlobs := blobsToRead + 3
data := make([]byte, ((totalBlobs-1)*maxBlobDataSize)+1000) // last blob is partial
_, err := rand.Read(data)
if err != nil {
t.Fatal(err)
}
buf := bytes.NewBuffer(data)
enc := NewEncoder(buf)
stream := make(Stream, blobsToRead+1) // +1 for sd blob
for i := 1; i < blobsToRead+1; i++ { // start at 1 to skip sd blob
stream[i], err = enc.Next()
if err != nil {
t.Fatal(err)
}
}
sdBlob := enc.SDBlob()
if len(sdBlob.BlobInfos) != blobsToRead {
t.Errorf("expected %d blobs in partial sdblob, got %d", blobsToRead, len(sdBlob.BlobInfos))
}
if enc.SourceLen() != maxBlobDataSize*blobsToRead {
t.Errorf("expected length of %d , got %d", maxBlobDataSize*blobsToRead, enc.SourceLen())
}
// now finish the stream, reusing key and IVs
buf = bytes.NewBuffer(data) // rewind to the beginning of the data
enc = NewEncoderFromSD(buf, sdBlob)
reconstructedStream, err := enc.Stream()
if err != nil {
t.Fatal(err)
}
if len(reconstructedStream) != totalBlobs+1 { // +1 for the terminating blob at the end
t.Errorf("expected %d blobs in stream, got %d", totalBlobs+1, len(reconstructedStream))
}
if enc.SourceLen() != len(data) {
t.Errorf("expected length of %d , got %d", len(data), enc.SourceLen())
}
reconstructedSDBlob := enc.SDBlob()
for i := 0; i < len(sdBlob.BlobInfos); i++ {
if !bytes.Equal(sdBlob.BlobInfos[i].IV, reconstructedSDBlob.BlobInfos[i].IV) {
t.Errorf("blob info %d of reconstructed sd blobd does not match original sd blob", i)
}
}
for i := 1; i < len(stream); i++ { // start at 1 to skip sd blob
if !bytes.Equal(stream[i], reconstructedStream[i]) {
t.Errorf("blob %d of reconstructed stream does not match original stream", i)
}
}
}
func TestEmptyStream(t *testing.T) {
enc := NewEncoder(bytes.NewBuffer(nil))
_, err := enc.Next()
if !errors.Is(err, io.EOF) {
t.Errorf("expected io.EOF, got %v", err)
}
sd := enc.SDBlob()
if len(sd.BlobInfos) != 1 {
t.Errorf("expected 1 blobinfos in sd blob, got %d", len(sd.BlobInfos))
}
if sd.BlobInfos[0].Length != 0 {
t.Errorf("first and only blob to be the terminator blob")
}
}
func TestTermination(t *testing.T) {
b := make([]byte, 12)
enc := NewEncoder(bytes.NewBuffer(b))
_, err := enc.Next()
if err != nil {
t.Error(err)
}
if enc.isTerminated() {
t.Errorf("stream should not terminate until after EOF")
}
_, err = enc.Next()
if !errors.Is(err, io.EOF) {
t.Errorf("expected io.EOF, got %v", err)
}
if !enc.isTerminated() {
t.Errorf("stream should be terminated after EOF")
}
_, err = enc.Next()
if !errors.Is(err, io.EOF) {
t.Errorf("expected io.EOF on all subsequent reads, got %v", err)
}
sd := enc.SDBlob()
if len(sd.BlobInfos) != 2 {
t.Errorf("expected 2 blobinfos in sd blob, got %d", len(sd.BlobInfos))
}
}
func TestSizeHint(t *testing.T) {
b := make([]byte, 12)
newStream, err := NewEncoder(bytes.NewBuffer(b)).SourceSizeHint(5 * maxBlobDataSize).Stream()
if err != nil {
t.Fatal(err)
}
if cap(newStream) != 2 { // 1 for sd blob, 1 for the 12 bytes of the actual stream
t.Fatalf("expected 2 blobs allocated, got %d", cap(newStream))
}
}
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
t.Skip("TODO: test new stream creation and decryption") t.Skip("TODO: test new stream creation and decryption")
} }

View file

@ -1,347 +0,0 @@
package url
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
)
const regexPartProtocol = "^((?:lbry://|https://)?)"
const regexPartHost = "((?:open.lbry.com/|lbry.tv/|lbry.lat/|lbry.fr/|lbry.in/)?)"
const regexPartStreamOrChannelName = "([^:$#/]*)"
const regexPartModifierSeparator = "([:$#]?)([^/]*)"
const regexQueryStringBreaker = "^([\\S]+)([?][\\S]*)"
const urlComponentsSize = 9
const ChannelNameMinLength = 1
const ClaimIdMaxLength = 40
const ProtoDefault = "lbry://"
const RegexClaimId = "(?i)^[0-9a-f]+$"
const RegexInvalidUri = "(?i)[ =&#:$@%?;/\\\\\\\\\\\"<>%\\\\{\\\\}|^~\\\\[\\\\]`\\u0000-\\u0008\\u000b-\\u000c\\u000e-\\u001F\\uD800-\\uDFFF\\uFFFE-\\uFFFF]"
type LbryUri struct {
Path string
IsChannel bool
StreamName string
StreamClaimId string
ChannelName string
ChannelClaimId string
PrimaryClaimSequence int
SecondaryClaimSequence int
PrimaryBidPosition int
SecondaryBidPosition int
ClaimName string
ClaimId string
ContentName string
QueryString string
}
type UriModifier struct {
ClaimId string
ClaimSequence int
BidPosition int
}
func (uri LbryUri) IsChannelUrl() bool {
return (!isEmpty(uri.ChannelName) && isEmpty(uri.StreamName)) || (!isEmpty(uri.ClaimName) && strings.HasPrefix(uri.ClaimName, "@"))
}
func (uri LbryUri) IsNameValid(name string) bool {
return !regexp.MustCompile(RegexInvalidUri).MatchString(name)
}
func (uri LbryUri) String() string {
return uri.Build(true, ProtoDefault, false)
}
func (uri LbryUri) VanityString() string {
return uri.Build(true, ProtoDefault, true)
}
func (uri LbryUri) TvString() string {
return uri.Build(true, "https://lbry.tv/", false)
}
func (uri LbryUri) Build(includeProto bool, protocol string, vanity bool) string {
formattedChannelName := ""
if !isEmpty(uri.ChannelName) {
formattedChannelName = uri.ChannelName
if !strings.HasPrefix(formattedChannelName, "@") {
formattedChannelName = fmt.Sprintf("@%s", formattedChannelName)
}
}
primaryClaimName := uri.ClaimName
if isEmpty(primaryClaimName) {
primaryClaimName = uri.ContentName
}
if isEmpty(primaryClaimName) {
primaryClaimName = formattedChannelName
}
if isEmpty(primaryClaimName) {
primaryClaimName = uri.StreamName
}
primaryClaimId := uri.ClaimId
if isEmpty(primaryClaimId) {
if !isEmpty(formattedChannelName) {
primaryClaimId = uri.ChannelClaimId
} else {
primaryClaimId = uri.StreamClaimId
}
}
var sb strings.Builder
if includeProto {
sb.WriteString(protocol)
}
sb.WriteString(primaryClaimName)
if vanity {
return sb.String()
}
secondaryClaimName := ""
if isEmpty(uri.ClaimName) && !isEmpty(uri.ContentName) {
secondaryClaimName = uri.ContentName
}
if isEmpty(secondaryClaimName) {
if !isEmpty(formattedChannelName) {
secondaryClaimName = uri.StreamName
}
}
secondaryClaimId := ""
if !isEmpty(secondaryClaimName) {
secondaryClaimId = uri.StreamClaimId
}
if !isEmpty(primaryClaimId) {
sb.WriteString("#")
sb.WriteString(primaryClaimId)
} else if uri.PrimaryClaimSequence > 0 {
sb.WriteString(":")
sb.WriteString(strconv.Itoa(uri.PrimaryClaimSequence))
} else if uri.PrimaryBidPosition > 0 {
sb.WriteString("$")
sb.WriteString(strconv.Itoa(uri.PrimaryBidPosition))
}
if !isEmpty(secondaryClaimName) {
sb.WriteString("/")
sb.WriteString(secondaryClaimName)
}
if !isEmpty(secondaryClaimId) {
sb.WriteString("#")
sb.WriteString(secondaryClaimId)
} else if uri.SecondaryClaimSequence > 0 {
sb.WriteString(":")
sb.WriteString(strconv.Itoa(uri.SecondaryClaimSequence))
} else if uri.SecondaryBidPosition > 0 {
sb.WriteString("$")
sb.WriteString(strconv.Itoa(uri.SecondaryBidPosition))
}
return sb.String()
}
func Parse(url string, requireProto bool) (*LbryUri, error) {
if isEmpty(url) {
return nil, errors.New("invalid url parameter")
}
reComponents := regexp.MustCompile(
fmt.Sprintf("(?i)%s%s%s%s(/?)%s%s",
regexPartProtocol,
regexPartHost,
regexPartStreamOrChannelName,
regexPartModifierSeparator,
regexPartStreamOrChannelName,
regexPartModifierSeparator))
reSeparateQueryString := regexp.MustCompile(regexQueryStringBreaker)
cleanUrl := url
queryString := ""
qsMatches := reSeparateQueryString.FindStringSubmatch(url)
if len(qsMatches) == 3 {
cleanUrl = qsMatches[1]
queryString = qsMatches[2][1:]
}
var components []string
componentMatches := reComponents.FindStringSubmatch(cleanUrl)
for _, component := range componentMatches[1:] {
components = append(components, component)
}
if len(components) != urlComponentsSize {
return nil, errors.New("regular expression error occurred while trying to Parse the value")
}
/*
* components[0] = proto
* components[1] = host
* components[2] = streamName or channelName
* components[3] = primaryModSeparator
* components[4] = primaryModValue
* components[5] = path separator
* components[6] = possibleStreamName
* components[7] = secondaryModSeparator
* components[8] = secondaryModValue
*/
if requireProto && isEmpty(components[0]) {
return nil, errors.New("url must include a protocol prefix (lbry://)")
}
if isEmpty(components[2]) {
return nil, errors.New("url does not include a name")
}
for _, component := range components[2:] {
if strings.Index(component, " ") > -1 {
return nil, errors.New("url cannot include a space")
}
}
streamOrChannelName := components[2]
primaryModSeparator := components[3]
primaryModValue := components[4]
possibleStreamName := components[6]
secondaryModSeparator := components[7]
secondaryModValue := components[8]
primaryClaimId := ""
primaryClaimSequence := -1
primaryBidPosition := -1
secondaryClaimSequence := -1
secondaryBidPosition := -1
includesChannel := strings.HasPrefix(streamOrChannelName, "@")
isChannel := includesChannel && isEmpty(possibleStreamName)
channelName := ""
if includesChannel && len(streamOrChannelName) > 1 {
channelName = streamOrChannelName[1:]
}
// Convert the mod separators when parsing with protocol https://lbry.tv/ or similar
// [https://] uses ':', [lbry://] expects #
if !isEmpty(components[1]) {
if primaryModSeparator == ":" {
primaryModSeparator = "#"
}
if secondaryModSeparator == ":" {
secondaryModSeparator = "#"
}
}
if includesChannel {
if isEmpty(channelName) {
// I wonder if this check is really necessary, considering the subsequent min length check
return nil, errors.New("no channel name after @")
}
if len(channelName) < ChannelNameMinLength {
return nil, errors.New(fmt.Sprintf("Channel names must be at least %d character long.", ChannelNameMinLength))
}
}
var err error
var primaryMod *UriModifier
var secondaryMod *UriModifier
if !isEmpty(primaryModSeparator) && !isEmpty(primaryModValue) {
primaryMod, err = parseModifier(primaryModSeparator, primaryModValue)
if err != nil {
return nil, err
}
primaryClaimId = primaryMod.ClaimId
primaryClaimSequence = primaryMod.ClaimSequence
primaryBidPosition = primaryMod.BidPosition
}
if !isEmpty(secondaryModSeparator) && !isEmpty(secondaryModValue) {
secondaryMod, err = parseModifier(secondaryModSeparator, secondaryModValue)
if err != nil {
return nil, err
}
secondaryClaimSequence = secondaryMod.ClaimSequence
secondaryBidPosition = secondaryMod.BidPosition
}
streamName := streamOrChannelName
if includesChannel {
streamName = possibleStreamName
}
streamClaimId := ""
if includesChannel && secondaryMod != nil {
streamClaimId = secondaryMod.ClaimId
} else if primaryMod != nil {
streamClaimId = primaryMod.ClaimId
}
channelClaimId := ""
if includesChannel && primaryMod != nil {
channelClaimId = primaryMod.ClaimId
}
return &LbryUri{
Path: strings.Join(components[2:], ""),
IsChannel: isChannel,
StreamName: streamName,
StreamClaimId: streamClaimId,
ChannelName: channelName,
ChannelClaimId: channelClaimId,
PrimaryClaimSequence: primaryClaimSequence,
SecondaryClaimSequence: secondaryClaimSequence,
PrimaryBidPosition: primaryBidPosition,
SecondaryBidPosition: secondaryBidPosition,
ClaimName: streamOrChannelName,
ClaimId: primaryClaimId,
ContentName: streamName,
QueryString: queryString,
}, nil
}
func parseModifier(modSeparator string, modValue string) (*UriModifier, error) {
claimId := ""
claimSequence := 0
bidPosition := 0
if !isEmpty(modSeparator) {
if isEmpty(modValue) {
return nil, errors.New(fmt.Sprintf("No modifier provided after separator %s", modSeparator))
}
if modSeparator == "#" {
claimId = modValue
} else if modSeparator == ":" {
claimId = modValue
} else if modSeparator == "$" {
bidPosition = parseInt(modValue, -1)
}
}
if !isEmpty(claimId) && (len(claimId) > ClaimIdMaxLength || !regexp.MustCompile(RegexClaimId).MatchString(claimId)) {
return nil, errors.New(fmt.Sprintf("Invalid claim ID %s", claimId))
}
if claimSequence == -1 {
return nil, errors.New("claim sequence must be a number")
}
if bidPosition == -1 {
return nil, errors.New("bid position must be a number")
}
return &UriModifier{
ClaimId: claimId,
ClaimSequence: claimSequence,
BidPosition: bidPosition,
}, nil
}
func parseInt(value string, defaultValue int) int {
v, err := strconv.ParseInt(value, 10, 32)
if err != nil {
return defaultValue
}
return int(v)
}
func isEmpty(s string) bool {
return len(strings.TrimSpace(s)) == 0
}