diff --git a/claim/claim.go b/claim/claim.go index 204c759..30b5b65 100644 --- a/claim/claim.go +++ b/claim/claim.go @@ -2,16 +2,18 @@ package claim import ( "encoding/hex" - "errors" "github.com/golang/protobuf/jsonpb" "github.com/golang/protobuf/proto" + + "github.com/lbryio/lbry.go/errors" "github.com/lbryio/lbryschema.go/address" "github.com/lbryio/types/go" ) type ClaimHelper struct { *pb.Claim + migratedFrom []byte } func (c *ClaimHelper) ValidateAddresses(blockchainName string) error { @@ -21,7 +23,7 @@ func (c *ClaimHelper) ValidateAddresses(blockchainName string) error { if fee != nil { tmp_addr := fee.GetAddress() if len(tmp_addr) != 25 { - return errors.New("invalid address length: " + string(len(tmp_addr)) + "!") + return errors.Err("invalid address length: " + string(len(tmp_addr)) + "!") } addr := [25]byte{} for i := range addr { @@ -29,7 +31,7 @@ func (c *ClaimHelper) ValidateAddresses(blockchainName string) error { } _, err := address.EncodeAddress(addr, blockchainName) if err != nil { - return err + return errors.Err(err) } } } @@ -44,20 +46,20 @@ func (c *ClaimHelper) ValidateCertificate() error { keyType := certificate.GetKeyType() _, err := c.GetCertificatePublicKey() if err != nil { - return err + return errors.Err(err) } if keyType.String() != SECP256k1 { - return errors.New("wrong curve: " + keyType.String()) + return errors.Err("wrong curve: " + keyType.String()) } return nil } func (c *ClaimHelper) LoadFromBytes(raw_claim []byte, blockchainName string) error { if c.String() != "" { - return errors.New("already initialized") + return errors.Err("already initialized") } if len(raw_claim) < 1 { - return errors.New("there is nothing to decode") + return errors.Err("there is nothing to decode") } claim_pb := &pb.Claim{} @@ -65,7 +67,7 @@ func (c *ClaimHelper) LoadFromBytes(raw_claim []byte, blockchainName string) err if err != nil { return err } - *c = ClaimHelper{claim_pb} + *c = ClaimHelper{claim_pb, raw_claim} err = c.ValidateAddresses(blockchainName) if err != nil { return err @@ -86,8 +88,8 @@ func (c *ClaimHelper) LoadFromHexString(claim_hex string, blockchainName string) return c.LoadFromBytes(buf, blockchainName) } -func DecodeClaimBytes(serialized []byte, blockchainName string) (*ClaimHelper, error) { - claim := &ClaimHelper{&pb.Claim{}} +func DecodeClaimProtoBytes(serialized []byte, blockchainName string) (*ClaimHelper, error) { + claim := &ClaimHelper{&pb.Claim{}, serialized} err := claim.LoadFromBytes(serialized, blockchainName) if err != nil { return nil, err @@ -109,7 +111,46 @@ func DecodeClaimJSON(claimJSON string, blockchainName string) (*ClaimHelper, err if err != nil { return nil, err } - return &ClaimHelper{c}, nil + return &ClaimHelper{c, []byte(claimJSON)}, nil +} + +// DecodeClaimBytes take a byte array and tries to decode it to a protobuf claim or migrate it from either json v1,2,3 +func DecodeClaimBytes(serialized []byte, blockchainName string) (*ClaimHelper, error) { + helper, err := DecodeClaimProtoBytes(serialized, blockchainName) + if err == nil { + return helper, nil + } + helper = &ClaimHelper{} + //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 verion", 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 } func (c *ClaimHelper) GetStream() *pb.Stream { diff --git a/claim/migration.go b/claim/migration.go new file mode 100644 index 0000000..79e3a99 --- /dev/null +++ b/claim/migration.go @@ -0,0 +1,146 @@ +package claim + +import ( + "encoding/hex" + + "github.com/btcsuite/btcutil/base58" + "github.com/lbryio/types/go" +) + +const lbrySDHash = "lbry_sd_hash" + +func newClaim() *pb.Claim { + pbClaim := new(pb.Claim) + stream := new(pb.Stream) + metadata := new(pb.Metadata) + source := new(pb.Source) + pubSig := new(pb.Signature) + fee := new(pb.Fee) + metadata.Fee = fee + stream.Metadata = metadata + stream.Source = source + pbClaim.Stream = stream + pbClaim.PublisherSignature = pubSig + + //Fee version + feeVersion := pb.Fee__0_0_1 + pbClaim.GetStream().GetMetadata().GetFee().Version = &feeVersion + //Metadata version + mdVersion := pb.Metadata__0_1_0 + pbClaim.GetStream().GetMetadata().Version = &mdVersion + //Source version + srcVersion := pb.Source__0_0_1 + pbClaim.GetStream().GetSource().Version = &srcVersion + //Stream version + streamVersion := pb.Stream__0_0_1 + pbClaim.GetStream().Version = &streamVersion + //Claim version + clmVersion := pb.Claim__0_0_1 + pbClaim.Version = &clmVersion + //Claim type + clmType := pb.Claim_streamType + pbClaim.ClaimType = &clmType + + return pbClaim +} + +func setMetaData(claim pb.Claim, author string, description string, language pb.Metadata_Language, license string, + licenseURL *string, title string, thumbnail *string, nsfw bool) { + claim.GetStream().GetMetadata().Author = &author + claim.GetStream().GetMetadata().Description = &description + claim.GetStream().GetMetadata().Language = &language + claim.GetStream().GetMetadata().License = &license + claim.GetStream().GetMetadata().Title = &title + claim.GetStream().GetMetadata().Thumbnail = thumbnail + claim.GetStream().GetMetadata().Nsfw = &nsfw + claim.GetStream().GetMetadata().LicenseUrl = licenseURL + +} + +func migrateV1Claim(vClaim V1Claim) (*pb.Claim, error) { + pbClaim := newClaim() + //Not part of json V1 + pbClaim.PublisherSignature = nil + //Stream + // -->Universal + setFee(vClaim.Fee, pbClaim) + // -->MetaData + language := pb.Metadata_Language(pb.Metadata_Language_value[vClaim.Language]) + setMetaData(*pbClaim, vClaim.Author, vClaim.Description, language, + vClaim.License, nil, vClaim.Title, vClaim.Thumbnail, false) + // -->Source + pbClaim.GetStream().GetSource().ContentType = &vClaim.ContentType + sourceType := pb.Source_SourceTypes(pb.Source_SourceTypes_value[lbrySDHash]) + pbClaim.GetStream().GetSource().SourceType = &sourceType + src, err := hex.DecodeString(vClaim.Sources.LbrySDHash) + pbClaim.GetStream().GetSource().Source = src + + return pbClaim, err +} + +func migrateV2Claim(vClaim V2Claim) (*pb.Claim, error) { + pbClaim := newClaim() + //Not part of json V2 + pbClaim.PublisherSignature = nil + //Stream + // -->Fee + setFee(vClaim.Fee, pbClaim) + // -->MetaData + language := pb.Metadata_Language(pb.Metadata_Language_value[vClaim.Language]) + setMetaData(*pbClaim, vClaim.Author, vClaim.Description, language, + vClaim.License, vClaim.LicenseURL, vClaim.Title, vClaim.Thumbnail, vClaim.NSFW) + // -->Source + pbClaim.GetStream().GetSource().ContentType = &vClaim.ContentType + sourceType := pb.Source_SourceTypes(pb.Source_SourceTypes_value[lbrySDHash]) + pbClaim.GetStream().GetSource().SourceType = &sourceType + src, err := hex.DecodeString(vClaim.Sources.LbrySDHash) + pbClaim.GetStream().GetSource().Source = src + + return pbClaim, err +} + +func migrateV3Claim(vClaim V3Claim) (*pb.Claim, error) { + pbClaim := newClaim() + //Not part of json V3 + pbClaim.PublisherSignature = nil + //Stream + // -->Fee + setFee(vClaim.Fee, pbClaim) + // -->MetaData + language := pb.Metadata_Language(pb.Metadata_Language_value[vClaim.Language]) + setMetaData(*pbClaim, vClaim.Author, vClaim.Description, language, + vClaim.License, vClaim.LicenseURL, vClaim.Title, vClaim.Thumbnail, vClaim.NSFW) + // -->Source + pbClaim.GetStream().GetSource().ContentType = &vClaim.ContentType + sourceType := pb.Source_SourceTypes(pb.Source_SourceTypes_value[lbrySDHash]) + pbClaim.GetStream().GetSource().SourceType = &sourceType + src, err := hex.DecodeString(vClaim.Sources.LbrySDHash) + pbClaim.GetStream().GetSource().Source = src + + return pbClaim, err +} + +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 + } + //Fee Settings + pbClaim.GetStream().GetMetadata().GetFee().Amount = &amount + pbClaim.GetStream().GetMetadata().GetFee().Currency = ¤cy + pbClaim.GetStream().GetMetadata().GetFee().Address = base58.Decode(address) + } +} diff --git a/claim/migration_test.go b/claim/migration_test.go new file mode 100644 index 0000000..9f8a561 --- /dev/null +++ b/claim/migration_test.go @@ -0,0 +1,220 @@ +package claim + +import ( + "encoding/hex" + "testing" + + "github.com/btcsuite/btcutil/base58" +) + +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 = []valueTestPair{ + {"7b22666565223a207b22616d6f756e74223a2035302e302c202274797065223a20224c4243222c202261646472657373223a2022625664653744716244345079485a6e6753617742686944534c626e4e76547970467a227d2c20226465736372697074696f6e223a2022466f757220636f75706c6573206d65657420666f722053756e646179206272756e6368206f6e6c7920746f20646973636f76657220746865792061726520737475636b20696e206120686f75736520746f6765746865722061732074686520776f726c64206d61792062652061626f757420746f20656e642e222c20226c6963656e7365223a20224f7363696c6c6f73636f7065204c61626f7261746f72696573222c2022617574686f72223a20225772697474656e20616e6420646972656374656420627920546f646420426572676572222c20226c616e6775616765223a2022656e222c20227469746c65223a2022497427732061204469736173746572222c2022736f7572636573223a207b226c6272795f73645f68617368223a2022646139333636323265333536323633373334343630336135353861373334636539316532653963333734303232353966346365353039626339646666396663313633633532393061663734316165613632373761623161333563376433623731227d2c20227468756d626e61696c223a2022687474703a2f2f69612e6d656469612d696d64622e636f6d2f696d616765732f4d2f4d5635424d5451774e6a597a4d5451304d6c35424d6c3542616e426e586b46745a5463774e44557a4f444d354e7740402e5f56315f5359313030305f4352302c302c3637332c313030305f414c5f2e6a7067227d", + 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", + "bVde7DqbD4PyHZngSawBhiDSLbnNvTypFz", + "application/octet-stream", + "en", + "da936622e3562637344603a558a734ce91e2e9c37402259f4ce509bc9dff9fc163c5290af741aea6277ab1a35c7d3b71", + "http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg", + false, + }, + }, +} + +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", + "UNKNOWN_LANGUAGE", //"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", + "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", + "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", + "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", + "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", + "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", + "en", + "fc0dac5cfc526354963ff1769f6e739c4e42a0790420ffab9fa3b6401e93ae5a0515eff960f8c9c272907eda6fcba254", + "", + true, + }, + }, +} + +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().GetMetadata().GetAuthor() != pair.Claim.Author { + t.Error("Author mismatch: expected", pair.Claim.Author, "got", helper.Claim.GetStream().GetMetadata().GetAuthor()) + } + if helper.Claim.GetStream().GetMetadata().GetTitle() != pair.Claim.Title { + t.Error("Title mismatch: expected", pair.Claim.Title, "got", helper.Claim.GetStream().GetMetadata().GetTitle()) + } + if helper.Claim.GetStream().GetMetadata().GetDescription() != pair.Claim.Description { + t.Error("Description mismatch: expected", pair.Claim.Description, "got", helper.Claim.GetStream().GetMetadata().GetDescription()) + } + if helper.Claim.GetStream().GetMetadata().GetLicense() != pair.Claim.License { + t.Error("License mismatch: expected", pair.Claim.License, "got", helper.Claim.GetStream().GetMetadata().GetLicense()) + } + if helper.Claim.GetStream().GetMetadata().GetLicenseUrl() != pair.Claim.LicenseURL { + t.Error("LicenseURL mismatch: expected", pair.Claim.LicenseURL, "got", helper.Claim.GetStream().GetMetadata().GetLicenseUrl()) + } + if helper.Claim.GetStream().GetMetadata().GetFee().GetAmount() != pair.Claim.FeeAmount { + t.Error("Fee Amount mismatch: expected", pair.Claim.FeeAmount, "got", helper.Claim.GetStream().GetMetadata().GetFee().GetAmount()) + } + if helper.Claim.GetStream().GetMetadata().GetFee().GetCurrency().String() != pair.Claim.FeeCurrency { + t.Error("Fee Currency mismatch: expected", pair.Claim.FeeCurrency, "got", helper.Claim.GetStream().GetMetadata().GetFee().GetCurrency()) + } + hexaddress := base58.Encode(helper.Claim.GetStream().GetMetadata().GetFee().GetAddress()) + if hexaddress != pair.Claim.FeeAddress { + t.Error("Fee Address mismatch: expected", pair.Claim.FeeAddress, "got", hexaddress) + } + if helper.Claim.GetStream().GetSource().GetContentType() != pair.Claim.ContentType { + t.Error("ContentType mismatch: expected", pair.Claim.ContentType, "got", helper.Claim.GetStream().GetSource().GetContentType()) + } + if helper.Claim.GetStream().GetMetadata().GetLanguage().String() != pair.Claim.Language { + t.Error("Language mismatch: expected ", pair.Claim.Language, " got ", helper.Claim.GetStream().GetMetadata().GetLanguage().String()) + } + content := hex.EncodeToString(helper.Claim.GetStream().GetSource().GetSource()) + if content != pair.Claim.LbrySDHash { + t.Error("Source mismatch: expected", pair.Claim.LbrySDHash, "got", content) + } + } +} diff --git a/claim/schema.go b/claim/schema.go new file mode 100644 index 0000000..eb3f2ea --- /dev/null +++ b/claim/schema.go @@ -0,0 +1,125 @@ +package claim + +import ( + "encoding/json" + + "github.com/lbryio/lbry.go/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 +}