lbryio/chainquery

View on GitHub
daemon/processing/claim.go

Summary

Maintainability
D
1 day
Test Coverage
package processing

import (
    "encoding/hex"
    "encoding/json"
    "fmt"
    "strings"
    "time"
    "unicode/utf8"

    "github.com/lbryio/chainquery/datastore"
    "github.com/lbryio/chainquery/global"
    "github.com/lbryio/chainquery/lbrycrd"
    "github.com/lbryio/chainquery/metrics"
    "github.com/lbryio/chainquery/model"
    "github.com/lbryio/chainquery/notifications"
    "github.com/lbryio/chainquery/sockety"
    util2 "github.com/lbryio/chainquery/util"

    "github.com/lbryio/lbry.go/v2/extras/errors"
    util "github.com/lbryio/lbry.go/v2/lbrycrd"
    "github.com/lbryio/lbry.go/v2/schema/address/base58"
    c "github.com/lbryio/lbry.go/v2/schema/stake"
    legacy_pb "github.com/lbryio/types/v1/go"
    pb "github.com/lbryio/types/v2/go"

    "github.com/OdyseeTeam/sockety/socketyapi"
    "github.com/sirupsen/logrus"
    "github.com/volatiletech/null/v8"
    "github.com/volatiletech/sqlboiler/v4/boil"
)

func processAsClaim(script []byte, vout model.Output, tx model.Transaction, blockHeight uint64) (address *string, claimID *string, err error) {
    defer metrics.Processing(time.Now(), "claim")
    var pubkeyscript []byte
    var name string
    var claimid string
    if lbrycrd.IsClaimNameScript(script) {
        name, claimid, pubkeyscript, err = processClaimNameScript(&script, vout, tx, blockHeight)
        if err != nil {
            return nil, nil, err
        }
    } else if lbrycrd.IsClaimSupportScript(script) {
        name, claimid, pubkeyscript, err = processClaimSupportScript(&script, vout, tx)
        if err != nil {
            return nil, nil, err
        }
    } else if lbrycrd.IsClaimUpdateScript(script) {
        name, claimid, pubkeyscript, err = processClaimUpdateScript(&script, vout, tx, blockHeight)
        if err != nil {
            return nil, nil, err
        }
    } else {
        return nil, nil, errors.Base("Not a claim -- " + hex.EncodeToString(script))
    }
    pksAddress := lbrycrd.GetAddressFromPublicKeyScript(pubkeyscript)
    address = &pksAddress
    logrus.Debugf("Handled Claim - claimID: %s - name: %s", claimid, name)
    return address, &claimid, nil
}

func processClaimNameScript(script *[]byte, vout model.Output, tx model.Transaction, blockHeight uint64) (name string, claimid string, pkscript []byte, err error) {
    claimid, err = util.ClaimIDFromOutpoint(vout.TransactionHash, int(vout.Vout))
    if err != nil {
        return name, "", pkscript, err
    }
    name, value, pkscript, err := lbrycrd.ParseClaimNameScript(*script)
    if err != nil {
        err := errors.Prefix("Claim name script parsing error", err)
        return name, claimid, pkscript, err
    }
    helper, err := c.DecodeClaimBytes(value, global.BlockChainName)
    if err != nil {
        logrus.Debug("saving non-conforming claim - Name: ", name, " ClaimID: ", claimid)
        saveUnknownClaim(name, claimid, false, value, vout, tx)
        return name, claimid, pkscript, nil
    }
    if helper.Claim == nil {
        err := errors.Base("Produced null pbClaim-> " + name + " " + claimid)
        return name, claimid, pkscript, err
    }
    claim := datastore.GetClaim(claimid)
    if claim == nil {
        claim = &model.Claim{ClaimID: claimid, TransactionHashID: null.NewString(tx.Hash, true), Vout: vout.Vout}
        err := datastore.PutClaim(claim)
        if err != nil {
            return name, claimid, pkscript, err
        }
    }

    claim.ClaimID = claimid
    claim.Name = name
    claim.TransactionTime = tx.TransactionTime
    claim.ClaimAddress = lbrycrd.GetAddressFromPublicKeyScript(pkscript)
    claim.TransactionHashUpdate.SetValid(tx.Hash)
    claim.VoutUpdate.SetValid(vout.Vout)
    if blockHeight > 0 {
        claim.Height = uint(blockHeight)
    } else {
        logrus.Debug("ClaimNew: No blockheight!")
    }
    claim, err = processClaim(helper, claim, value, vout, tx)
    if err != nil {
        return name, claimid, pkscript, err
    }
    err = datastore.PutClaim(claim)
    if err == nil {
        IDs := []string{"claims", claim.Name, claimid}
        if !claim.PublisherID.IsZero() {
            IDs = append(IDs, "channel-"+claim.PublisherID.String)
        }
        go sockety.SendNotification(socketyapi.SendNotificationArgs{
            Service: socketyapi.BlockChain,
            Type:    "new_claim",
            IDs:     IDs,
            Data:    map[string]interface{}{"claim": claim},
        })
        if claim.Height > 0 {
            notifications.ClaimEvent(claim, tx, helper)
        }
    }

    return name, claimid, pkscript, err
}

func processClaimSupportScript(script *[]byte, vout model.Output, tx model.Transaction) (name string, claimid string, pubkeyscript []byte, err error) {
    var value []byte
    name, claimid, value, pubkeyscript, err = lbrycrd.ParseClaimSupportScript(*script)
    if err != nil {
        err := errors.Prefix("Claim support processing error", err)
        return name, claimid, pubkeyscript, err
    }
    support := datastore.GetSupport(tx.Hash, vout.Vout)
    support, err = processSupport(claimid, value, support, vout, tx)
    if err != nil {
        err = nil
        //logrus.Error(fmt.Sprintf("[outpoint:%s:%d]", tx.Hash, vout.Vout), "could not decode support value: ", err)
    }
    if err := datastore.PutSupport(support); err != nil {
        logrus.Debugf("error while adding support for claim_id %s: %s", claimid, err.Error())
    } else {
        go sockety.SendNotification(socketyapi.SendNotificationArgs{
            Service: socketyapi.BlockChain,
            Type:    "support",
            IDs:     []string{"supports", claimid, name},
            Data:    map[string]interface{}{"support": support},
        })
    }

    return name, claimid, pubkeyscript, err
}

func processClaimUpdateScript(script *[]byte, vout model.Output, tx model.Transaction, blockHeight uint64) (name string, claimID string, pubkeyscript []byte, err error) {
    name, claimID, value, pubkeyscript, err := lbrycrd.ParseClaimUpdateScript(*script)
    if err != nil {
        err := errors.Prefix("Claim update processing error", err)
        return name, claimID, pubkeyscript, err
    }
    helper, err := c.DecodeClaimBytes(value, global.BlockChainName)
    if err != nil {
        logrus.Debug("saving non-conforming claim - Update: ", name, " ClaimID: ", claimID)
        saveUnknownClaim(name, claimID, true, value, vout, tx)
        return name, claimID, pubkeyscript, nil
    }
    if helper.Claim != nil && err == nil {
        claim := datastore.GetClaim(claimID)
        claim, err := processUpdateClaim(helper, claim, value)
        if err != nil {
            return name, claimID, pubkeyscript, err
        }
        if claim == nil {
            claim = &model.Claim{Name: name, ClaimID: claimID, TransactionHashID: null.NewString(tx.Hash, true), Vout: vout.Vout}
            err := datastore.PutClaim(claim)
            if err != nil {
                return name, claimID, pubkeyscript, err
            }
        }
        claim.TransactionTime = tx.TransactionTime
        claim.ClaimAddress = lbrycrd.GetAddressFromPublicKeyScript(pubkeyscript)
        if blockHeight > 0 {
            claim.Height = uint(blockHeight)
        } else {
            logrus.Debug("ClaimUpdate: No blockheight!")
        }
        claim.TransactionHashUpdate.SetValid(tx.Hash)
        claim.VoutUpdate.SetValid(vout.Vout)
        if claim.BidState == "Spent" {
            claim.BidState = "Accepted"
        }
        if err := datastore.PutClaim(claim); err != nil {
            logrus.Debug("Claim updates to invalid certificate claim. ", claim.PublisherID)
            if logrus.GetLevel() == logrus.DebugLevel {
                logrus.WithError(err)
            }
        } else {
            go sockety.SendNotification(socketyapi.SendNotificationArgs{
                Service: socketyapi.BlockChain,
                Type:    "claim_update",
                IDs:     []string{"claims", "claimupdates", claim.ClaimID, name},
                Data:    map[string]interface{}{"claim": claim},
            })
        }
    }
    return name, claimID, pubkeyscript, err
}

func processClaim(helper *c.StakeHelper, claim *model.Claim, value []byte, output model.Output, tx model.Transaction) (*model.Claim, error) {
    claim.ValueAsHex = hex.EncodeToString(value)
    if helper.GetStream() != nil {
        claim.ClaimType = 1
    } else if helper.Claim.GetChannel() != nil {
        claim.ClaimType = 2
    }

    // pbClaim JSON
    if claimHelper, err := c.DecodeClaimHex(claim.ValueAsHex, global.BlockChainName); err == nil && claimHelper != nil {
        claimAsJSON, err := GetValueAsJSON(*claimHelper)
        if err != nil {
            logrus.Error(err)
        } else {
            claim.ValueAsJSON.SetValid(claimAsJSON)
        }
    }

    setSourceInfo(claim, helper)
    err := setMetaDataInfo(claim, helper)
    if err != nil {
        return nil, err
    }
    setPublisherInfo(claim, helper)
    setCertificateInfo(claim, helper)

    if helper.LegacyClaim != nil && helper.LegacyClaim.GetVersion().String() != "" {
        claim.Version.SetValid(helper.LegacyClaim.GetVersion().String())
    }

    return claim, nil
}

func processSupport(claimID string, value []byte, support *model.Support, output model.Output, tx model.Transaction) (*model.Support, error) {
    if support == nil {
        support = &model.Support{}
    }
    var err error
    support.TransactionHashID.SetValid(tx.Hash)
    support.Vout = output.Vout
    support.SupportAmount = output.Value.Float64
    if len(value) > 0 {
        var s *c.StakeHelper
        s, err = c.DecodeSupportBytes(value, global.BlockChainName)
        if err == nil {
            support.SupportedByClaimID.SetValid(hex.EncodeToString(util2.ReverseBytes(s.ClaimID)))
        }
    }

    if claim := datastore.GetClaim(claimID); claim != nil {
        support.SupportedClaimID = claimID
        return support, err
    }
    logrus.Debug("Claim Support for claim ", claimID, " is a non-existent claim.")
    return support, err

}

func processUpdateClaim(helper *c.StakeHelper, claim *model.Claim, value []byte) (*model.Claim, error) {
    if claim == nil {
        return nil, nil
    }
    claim.ValueAsHex = hex.EncodeToString(value)

    err := UpdateClaimData(helper, claim)
    if err != nil {
        return nil, err
    }

    return claim, nil
}

// UpdateClaimData updates the claim information from the blockchain
func UpdateClaimData(helper *c.StakeHelper, claim *model.Claim) error {
    // pbClaim JSON
    if claimHelper, err := c.DecodeClaimHex(claim.ValueAsHex, global.BlockChainName); err == nil && claimHelper != nil {
        json, err := GetValueAsJSON(*claimHelper)
        if err != nil {
            logrus.Error(err)
        } else {
            claim.ValueAsJSON.SetValid(json)
        }
    }

    setSourceInfo(claim, helper)
    err := setMetaDataInfo(claim, helper)
    if err != nil {
        return err
    }
    setPublisherInfo(claim, helper)
    setCertificateInfo(claim, helper)

    if helper.LegacyClaim != nil && helper.LegacyClaim.GetVersion().String() != "" {
        claim.Version.SetValid(helper.LegacyClaim.GetVersion().String())
    }
    return nil
}

func setPublisherInfo(claim *model.Claim, helper *c.StakeHelper) {
    claim.IsCertProcessed = true
    claim.IsCertValid = false
    claim.PublisherID = null.NewString("", false)
    claim.PublisherSig = null.NewString("", false)
    if helper.Signature != nil {
        claim.IsCertProcessed = false
        if helper.LegacyClaim == nil {
            claim.PublisherID.SetValid(hex.EncodeToString(util2.ReverseBytes(helper.ClaimID)))
        } else {
            claim.PublisherID.SetValid(hex.EncodeToString(helper.ClaimID))
        }
        claim.PublisherSig.SetValid(hex.EncodeToString(helper.Signature))
    }
}

func setCertificateInfo(claim *model.Claim, helper *c.StakeHelper) {
    claim.Certificate = null.NewString("", false)
    if helper.Claim.GetChannel() != nil {
        claim.IsCertProcessed = true
        var certificate *legacy_pb.Certificate
        if helper.LegacyClaim != nil {
            certificate = helper.LegacyClaim.GetCertificate()
        } else {
            unknown := legacy_pb.Certificate_UNKNOWN_VERSION
            SECP256k1 := legacy_pb.KeyType_SECP256k1
            certificate = &legacy_pb.Certificate{
                Version:   &unknown,
                KeyType:   &SECP256k1,
                PublicKey: helper.Claim.GetChannel().PublicKey,
            }
        }
        certBytes, err := json.Marshal(certificate)
        if err != nil {
            logrus.Error("Could not form json from certificate")
        }
        claim.Certificate.SetValid(string(certBytes))
    }
}

func setMetaDataInfo(claim *model.Claim, helper *c.StakeHelper) error {
    err := resetMetadata(claim)
    if err != nil {
        return err
    }
    claim.Title.SetValid(helper.Claim.GetTitle())
    if helper.Claim.GetDescription() != "" {
        claim.Description.SetValid(helper.Claim.GetDescription())
    }
    if helper.Claim.GetThumbnail().GetUrl() != "" {
        claim.ThumbnailURL.SetValid(helper.Claim.GetThumbnail().GetUrl())
    }
    if len(helper.Claim.GetTags()) > 0 {
        err := setTags(claim, helper.Claim.GetTags())
        if err != nil {
            return err
        }
    }
    if len(helper.Claim.GetLanguages()) > 0 {
        claim.Language.SetValid(helper.Claim.GetLanguages()[0].Language.String())
    }
    stream := helper.GetStream()
    if stream != nil {
        setStreamMetadata(claim, *stream)
    }
    channel := helper.Claim.GetChannel()
    if channel != nil {
        setChannelMetadata(claim, *channel)
    }
    list := helper.Claim.GetCollection()
    if list != nil {
        setCollectionMetadata(claim, *list)
    }
    reference := helper.Claim.GetRepost()
    if reference != nil {
        claim.Type.SetValid(global.ClaimReferenceClaimType)
        if len(reference.GetClaimHash()) > 0 {
            claim.ClaimReference.SetValid(hex.EncodeToString(util2.ReverseBytes(reference.GetClaimHash())))
        }
    }

    return nil
}

func setTags(claim *model.Claim, tags []string) error {
    maxTagLength := 255
    for _, tag := range tags {
        if len(tag) > maxTagLength {
            tag = tag[0:maxTagLength]
        }
        if tag == "mature" {
            claim.IsNSFW = true
        }
        t := &model.Tag{Tag: tag}
        err := datastore.PutTag(t)
        if err != nil {
            logrus.Error(errors.Prefix(fmt.Sprintf("Could not save tag %s, skipping", tag), err))
            return nil
        }
        tagIDNotFound := t.ID == 0
        if tagIDNotFound {
            t = datastore.GetTag(tag)
        }
        tagIDNotFound = t.ID == 0
        if tagIDNotFound {
            logrus.Error(errors.Prefix(fmt.Sprintf("Could not get tag %s, skipping", tag), err))
            return nil
        }

        ct := &model.ClaimTag{ClaimID: claim.ClaimID, TagID: null.NewUint64(t.ID, true)}
        err = datastore.PutClaimTag(ct)
        if err != nil {
            return err
        }
    }
    return nil
}

func setLicense(claim *model.Claim, stream pb.Stream) {
    license := stream.GetLicense()
    if len([]rune(license)) > 500 {
        license = string([]rune(license)[:500])
    }
    if utf8.ValidString(license) {
        claim.License.SetValid(strings.ToValidUTF8(license, " "))
    }

    liscenseURL := stream.GetLicenseUrl()
    if len([]rune(liscenseURL)) > 255 {
        liscenseURL = string([]rune(liscenseURL)[0:255])
    }

    if utf8.ValidString(liscenseURL) {
        //claim.LicenseURL.SetValid(strings.ToValidUTF8(liscenseURL, " "))
    }
}

func setStreamMetadata(claim *model.Claim, stream pb.Stream) {
    claim.Type.SetValid(global.StreamClaimType)
    if stream.GetAuthor() != "" {
        claim.Author.SetValid(stream.GetAuthor())
    }
    setLicense(claim, stream)

    fee := stream.GetFee()
    if fee != nil {
        claim.FeeCurrency.SetValid(fee.GetCurrency().String())
        claim.Fee = float64(fee.GetAmount())
        claim.FeeAddress.SetValid(base58.EncodeBase58(fee.GetAddress()))
    }
    s := stream.GetSource()
    if s != nil {
        setSourceMetadata(claim, s)
    }
    if stream.GetReleaseTime() > 0 {
        claim.ReleaseTime.SetValid(uint64(stream.GetReleaseTime()))
    }
    if stream.GetImage() != nil {
        i := stream.GetImage()
        if i.GetHeight() > 0 {
            claim.FrameHeight.SetValid(uint64(i.GetHeight()))
        }
        if i.GetWidth() > 0 {
            claim.FrameWidth.SetValid(uint64(i.GetWidth()))
        }
    }
    if stream.GetVideo() != nil {
        v := stream.GetVideo()
        if v.GetHeight() > 0 {
            claim.FrameHeight.SetValid(uint64(v.GetHeight()))
        }
        if v.GetWidth() > 0 {
            claim.FrameWidth.SetValid(uint64(v.GetWidth()))
        }
        if v.GetDuration() > 0 {
            claim.Duration.SetValid(uint64(v.GetDuration()))
        }
        if v.GetAudio() != nil {
            if v.GetAudio().GetDuration() > 0 {
                claim.AudioDuration.SetValid(uint64(v.GetAudio().GetDuration()))
            }
        }
    }
    if stream.GetAudio() != nil {
        if stream.GetAudio().GetDuration() > 0 {
            claim.AudioDuration.SetValid(uint64(stream.GetAudio().GetDuration()))
        }
    }
}

func setChannelMetadata(claim *model.Claim, channel pb.Channel) {
    claim.Type.SetValid(global.ChannelClaimType)
    if channel.GetCover() != nil {
        c := channel.GetCover()
        if c.GetName() != "" {
            claim.SourceName.SetValid(c.GetName())
        }
        if c.GetSize() > 0 {
            claim.SourceSize.SetValid(c.GetSize())
        }
        if c.GetUrl() != "" {
            const maxSourceURLLength = 255
            sourceURL := c.GetUrl()
            if len(sourceURL) > maxSourceURLLength {
                sourceURL = sourceURL[:252] + "..."
            }
            claim.SourceURL.SetValid(sourceURL)
        }
        if len(c.GetHash()) > 0 {
            claim.SourceHash.SetValid(hex.EncodeToString(c.GetHash()))
        }
        if c.GetMediaType() != "" {
            const maxSourceMediaType = 254
            sourceMediaType := c.GetMediaType()
            if len([]rune(sourceMediaType)) > maxSourceMediaType {
                sourceMediaType = string([]rune(sourceMediaType)[:maxSourceMediaType])
            }
            claim.SourceMediaType.SetValid(sourceMediaType)
        }
    }
    if channel.GetEmail() != "" {
        const maxEmailLength = 255
        email := channel.GetEmail()
        if len(email) > maxEmailLength {
            email = email[:252] + "..."
        }
        claim.Email.SetValid(email)
    }

    if channel.GetFeatured() != nil {
        claim.HasClaimList.SetValid(true)
        claim.ListType.SetValid(int16(channel.GetFeatured().GetListType()))
        claimList := make([]string, len(channel.GetFeatured().GetClaimReferences()))
        for i, c := range channel.GetFeatured().GetClaimReferences() {
            // No need to reverse bytes as lbrynet is fixed and should do this now
            claimList[i] = hex.EncodeToString(c.ClaimHash)
        }
        jsonList, err := json.Marshal(claimList)
        if err == nil {
            claim.ClaimIDList.SetValid(jsonList)
        } else {
            logrus.Error("could not process claim list of channel [", claim.ClaimID, "]")
        }
        //ToDo - Create NM Table Entry for each
    }
}

func setCollectionMetadata(claim *model.Claim, list pb.ClaimList) {
    claim.Type.SetValid(global.ClaimListClaimType)
    claim.HasClaimList.SetValid(true)
    claim.ListType.SetValid(int16(list.GetListType()))
    claimList := make([]string, len(list.GetClaimReferences()))
    for i, c := range list.GetClaimReferences() {
        // No need to reverse bytes as lbrynet is fixed and should do this now
        claimList[i] = hex.EncodeToString(c.ClaimHash)
    }
    jsonList, err := json.Marshal(claimList)
    if err == nil {
        claim.ClaimIDList.SetValid(jsonList)
    } else {
        logrus.Error("could not process claim list of channel [", claim.ClaimID, "]")
    }
    //ToDo - Create NM Table Entry for each
}

func setSourceMetadata(claim *model.Claim, s *pb.Source) {
    if s.GetUrl() != "" {
        const maxSourceURLLength = 255
        sourceURL := s.GetUrl()
        if len(sourceURL) > maxSourceURLLength {
            sourceURL = sourceURL[:252] + "..."
        }
        claim.SourceURL.SetValid(sourceURL)
    }
    if len(s.GetHash()) > 0 {
        claim.SourceHash.SetValid(hex.EncodeToString(s.GetHash()))
    }
    if s.GetSize() > 0 {
        claim.SourceSize.SetValid(s.GetSize())
    }
    if s.GetName() != "" {
        claim.SourceName.SetValid(s.GetName())
    }
    if s.GetMediaType() != "" {
        const maxSourceMediaType = 254
        sourceMediaType := s.GetMediaType()
        if len([]rune(sourceMediaType)) > maxSourceMediaType {
            sourceMediaType = string([]rune(sourceMediaType)[:maxSourceMediaType])
        }
        claim.SourceMediaType.SetValid(sourceMediaType)
    }
}

func resetMetadata(claim *model.Claim) error {
    claim.Title = null.NewString("", false)
    claim.Description = null.NewString("", false)
    claim.Language = null.NewString("", false)
    claim.Author = null.NewString("", false)
    claim.ThumbnailURL = null.NewString("", false)
    claim.IsNSFW = false
    claim.FeeCurrency = null.NewString("", false)
    claim.Fee = 0.0
    claim.FeeAddress = null.NewString("", false)
    claim.License = null.NewString("", false)
    claim.Type = null.NewString("", false)
    claim.ReleaseTime = null.NewUint64(0, false)
    claim.SourceHash = null.NewString("", false)
    claim.SourceName = null.NewString("", false)
    claim.SourceSize = null.NewUint64(0, false)
    claim.SourceMediaType = null.NewString("", false)
    claim.SourceURL = null.NewString("", false)
    claim.FrameWidth = null.NewUint64(0, false)
    claim.FrameHeight = null.NewUint64(0, false)
    claim.Duration = null.NewUint64(0, false)
    claim.AudioDuration = null.NewUint64(0, false)
    claim.Email = null.NewString("", false)
    claim.HasClaimList = null.NewBool(false, false)
    claim.ClaimReference = null.NewString("", false)
    claim.ListType = null.NewInt16(0, false)
    claim.ClaimIDList = null.NewJSON(nil, false)

    err := claim.ListClaimClaimInLists().DeleteAll(boil.GetDB())
    if err != nil {
        return err
    }
    err = claim.ClaimTags().DeleteAll(boil.GetDB())
    if err != nil {
        return err
    }

    return nil
}

func setSourceInfo(claim *model.Claim, helper *c.StakeHelper) {
    claim.ContentType = null.NewString("", false)
    claim.SDHash = null.NewString("", false)
    stream := helper.GetStream()
    if stream != nil {
        source := stream.GetSource()
        if source != nil {
            const maxContentTypeLength = 162
            mediaType := source.GetMediaType()
            if len(mediaType) > maxContentTypeLength {
                mediaType = mediaType[:158] + "..."
            }
            claim.ContentType.SetValid(mediaType)
            sdHash := hex.EncodeToString(stream.GetSource().GetSdHash())
            const maxSDHashColLength = 120
            if len(sdHash) > maxSDHashColLength {
                sdHash = sdHash[:116] + "..."
            }
            claim.SDHash.SetValid(sdHash)
        }
    }
}

func saveUnknownClaim(name string, claimid string, isUpdate bool, value []byte, vout model.Output, tx model.Transaction) {
    abnormalClaim := model.AbnormalClaim{}
    abnormalClaim.Vout = vout.Vout
    abnormalClaim.Name = name
    abnormalClaim.ClaimID = claimid
    abnormalClaim.IsUpdate = isUpdate
    abnormalClaim.TransactionHash.SetValid(vout.TransactionHash)
    abnormalClaim.ValueAsHex = hex.EncodeToString(value)
    abnormalClaim.BlockHash = tx.BlockHashID

    var js map[string]interface{} //JSON Map
    if json.Unmarshal(value, &js) == nil {
        abnormalClaim.ValueAsJSON.SetValid(string(value))
    }

    abnormalClaim.OutputID = vout.ID
    if err := abnormalClaim.InsertG(boil.Infer()); err != nil {
        logrus.Error("UnknownClaim Saving Error: ", err)
    }

}