github.com/vchain-us/vcn@v0.9.11-0.20210921212052-a2484d23c0b3/pkg/api/sign.go (about) 1 /* 2 * Copyright (c) 2018-2020 vChain, Inc. All Rights Reserved. 3 * This software is released under GPL3. 4 * The full license information can be found under: 5 * https://www.gnu.org/licenses/gpl-3.0.en.html 6 * 7 */ 8 9 package api 10 11 import ( 12 "bytes" 13 "context" 14 goErr "errors" 15 "fmt" 16 "math/big" 17 "math/rand" 18 "strings" 19 "time" 20 21 "github.com/ethereum/go-ethereum/accounts/abi/bind" 22 "github.com/ethereum/go-ethereum/common" 23 "github.com/ethereum/go-ethereum/ethclient" 24 "github.com/sirupsen/logrus" 25 "github.com/vchain-us/vcn/internal/blockchain" 26 "github.com/vchain-us/vcn/internal/errors" 27 "github.com/vchain-us/vcn/pkg/meta" 28 ) 29 30 // 31 var WrongPassphraseErr = goErr.New("incorrect notarization password") 32 33 // Sign is invoked by the User to notarize an artifact using the given functional options, 34 // if successful a BlockchainVerification is returned. 35 // By default, the artifact is notarized using status = meta.StatusTrusted, visibility meta.VisibilityPrivate. 36 // At least the key (secret) must be provided using SignWithKey(). 37 func (u User) Sign(artifact Artifact, options ...SignOption) (*BlockchainVerification, error) { 38 if artifact.Hash == "" { 39 return nil, makeError("hash is missing", nil) 40 } 41 if artifact.Size < 0 { 42 return nil, makeError("invalid size", nil) 43 } 44 45 hasAuth, err := u.IsAuthenticated() 46 if err != nil { 47 return nil, err 48 } 49 if !hasAuth { 50 return nil, makeAuthRequiredError() 51 } 52 53 trialExpired, err := u.trialExpired() 54 if err != nil { 55 return nil, err 56 } 57 if trialExpired { 58 return nil, fmt.Errorf(errors.TrialExpired) 59 } 60 61 opsLeft, err := u.RemainingSignOps() 62 if err != nil { 63 return nil, err 64 } 65 66 if opsLeft < 1 { 67 return nil, fmt.Errorf(errors.NoRemainingSignOps) 68 } 69 70 // In order to handle parallel calls here there is a retry mechanism if another transaction is already in place. 71 // This is a workaround. Need a proper solution to handle parallel signing 72 var verification *BlockchainVerification 73 for i := uint64(0); i < meta.TxVerificationRounds(); i++ { 74 verification, err := u.commitTransaction(artifact, options...) 75 if err != nil { 76 if err.Error() == errors.BlockchainPermission || 77 err.Error() == errors.BlockchainSameHashAlreadyImported || 78 err.Error() == errors.BlockchainTxNonceTooLow || 79 err.Error() == "request failed: MetaHashAlreadyExistsException (409)" { 80 rand.Seed(time.Now().UnixNano()) 81 sleepTime := time.Second * time.Duration(int64(rand.Intn(6))) 82 time.Sleep(sleepTime) 83 continue 84 } 85 } 86 return verification, err 87 } 88 return verification, err 89 } 90 91 func (u User) commitTransaction( 92 artifact Artifact, 93 opts ...SignOption, 94 ) (verification *BlockchainVerification, err error) { 95 96 o, err := makeSignOpts(opts...) 97 if err != nil { 98 return 99 } 100 transactor, err := bind.NewTransactor(bytes.NewReader([]byte(o.keyin)), o.passphrase) 101 if err != nil { 102 if err.Error() == "could not decrypt key with given passphrase" { 103 err = WrongPassphraseErr 104 } 105 return nil, err 106 } 107 108 transactor.GasLimit = meta.GasLimit() 109 transactor.GasPrice = meta.GasPrice() 110 client, err := ethclient.Dial(meta.MainNet()) 111 if err != nil { 112 err = makeError( 113 errors.BlockchainCannotConnect, 114 logrus.Fields{ 115 "error": err, 116 "network": meta.MainNet(), 117 }) 118 return 119 } 120 address := common.HexToAddress(meta.AssetsRelayContractAddress()) 121 instance, err := blockchain.NewAssetsRelay(address, client) 122 if err != nil { 123 err = makeFatal( 124 errors.BlockchainContractErr, 125 logrus.Fields{ 126 "error": err, 127 "contract": meta.AssetsRelayContractAddress(), 128 }, 129 ) 130 return 131 } 132 tx, err := instance.Sign(transactor, artifact.Hash, big.NewInt(int64(o.status))) 133 if err != nil { 134 if err.Error() == "Transaction with the same hash was already imported." { 135 return nil, makeError( 136 errors.BlockchainTxNonceTooLow, 137 logrus.Fields{ 138 "error": err, 139 "hash": artifact.Hash, 140 }, 141 ) 142 } 143 if err.Error() == "Transaction nonce is too low. Try incrementing the nonce." { 144 return nil, makeError( 145 errors.BlockchainSameHashAlreadyImported, 146 logrus.Fields{ 147 "error": err, 148 "hash": artifact.Hash, 149 }, 150 ) 151 } 152 err = makeFatal( 153 errors.SignFailed, 154 logrus.Fields{ 155 "error": err, 156 "hash": artifact.Hash, 157 }, 158 ) 159 return nil, err 160 } 161 timeout, err := waitForTx(client, tx.Hash(), meta.TxVerificationRounds(), meta.PollInterval()) 162 if err != nil { 163 err = makeFatal( 164 errors.BlockchainPermission, 165 logrus.Fields{ 166 "error": err, 167 }, 168 ) 169 return 170 } 171 if timeout { 172 err = makeFatal( 173 errors.BlockchainTimeout, 174 logrus.Fields{ 175 "error": err, 176 }, 177 ) 178 return 179 } 180 181 signerID := transactor.From.Hex() 182 verification, err = VerifyMatchingSignerID(artifact.Hash, signerID) 183 if err != nil { 184 return 185 } 186 187 err = u.createArtifact(verification, strings.ToLower(signerID), artifact, o.visibility, o.status, tx.Hash()) 188 return 189 } 190 191 func waitForTx(client *ethclient.Client, tx common.Hash, maxRounds uint64, pollInterval time.Duration) (timeout bool, err error) { 192 if err != nil { 193 return false, err 194 } 195 for i := uint64(0); i < maxRounds; i++ { 196 _, pending, err := client.TransactionByHash(context.Background(), tx) 197 if err != nil { 198 return false, err 199 } 200 if !pending { 201 return false, nil 202 } 203 time.Sleep(pollInterval) 204 } 205 return true, nil 206 }