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  }