github.com/jpmorganchase/quorum@v21.1.0+incompatible/private/engine/tessera/tessera.go (about)

     1  package tessera
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"net/url"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/ethereum/go-ethereum/common"
    15  	"github.com/ethereum/go-ethereum/log"
    16  	"github.com/ethereum/go-ethereum/params"
    17  	"github.com/ethereum/go-ethereum/private/cache"
    18  	"github.com/ethereum/go-ethereum/private/engine"
    19  	gocache "github.com/patrickmn/go-cache"
    20  )
    21  
    22  type tesseraPrivateTxManager struct {
    23  	features *engine.FeatureSet
    24  	client   *engine.Client
    25  	cache    *gocache.Cache
    26  }
    27  
    28  func Is(ptm interface{}) bool {
    29  	_, ok := ptm.(*tesseraPrivateTxManager)
    30  	return ok
    31  }
    32  
    33  func New(client *engine.Client, version []byte) *tesseraPrivateTxManager {
    34  	ptmVersion, err := parseVersion(version)
    35  	if err != nil {
    36  		log.Error("Error parsing version components from the tessera version: %s. Unable to extract transaction manager features.", version)
    37  	}
    38  	return &tesseraPrivateTxManager{
    39  		features: engine.NewFeatureSet(tesseraVersionFeatures(ptmVersion)...),
    40  		client:   client,
    41  		cache:    gocache.New(cache.DefaultExpiration, cache.CleanupInterval),
    42  	}
    43  }
    44  
    45  func (t *tesseraPrivateTxManager) submitJSON(method, path string, request interface{}, response interface{}) (int, error) {
    46  	apiVersion := ""
    47  	if t.features.HasFeature(engine.MultiTenancy) {
    48  		apiVersion = "vnd.tessera-2.1+"
    49  	}
    50  	req, err := newOptionalJSONRequest(method, t.client.FullPath(path), request, apiVersion)
    51  	if err != nil {
    52  		return -1, fmt.Errorf("unable to build json request for (method:%s,path:%s). Cause: %v", method, path, err)
    53  	}
    54  	res, err := t.client.HttpClient.Do(req)
    55  	if err != nil {
    56  		return -1, fmt.Errorf("unable to submit request (method:%s,path:%s). Cause: %v", method, path, err)
    57  	}
    58  	defer res.Body.Close()
    59  	if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated {
    60  		body, _ := ioutil.ReadAll(res.Body)
    61  		return res.StatusCode, fmt.Errorf("%d status: %s", res.StatusCode, string(body))
    62  	}
    63  	if err := json.NewDecoder(res.Body).Decode(response); err != nil {
    64  		return res.StatusCode, fmt.Errorf("unable to decode response body for (method:%s,path:%s). Cause: %v", method, path, err)
    65  	}
    66  	return res.StatusCode, nil
    67  }
    68  
    69  func (t *tesseraPrivateTxManager) submitJSONOld(method, path string, request interface{}, response interface{}) (int, error) {
    70  	apiVersion := ""
    71  	req, err := newOptionalJSONRequest(method, t.client.FullPath(path), request, apiVersion)
    72  	if err != nil {
    73  		return -1, fmt.Errorf("unable to build json request for (method:%s,path:%s). Cause: %v", method, path, err)
    74  	}
    75  	res, err := t.client.HttpClient.Do(req)
    76  	if err != nil {
    77  		return -1, fmt.Errorf("unable to submit request (method:%s,path:%s). Cause: %v", method, path, err)
    78  	}
    79  	defer res.Body.Close()
    80  	if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated {
    81  		body, _ := ioutil.ReadAll(res.Body)
    82  		return res.StatusCode, fmt.Errorf("%d status: %s", res.StatusCode, string(body))
    83  	}
    84  	if err := json.NewDecoder(res.Body).Decode(response); err != nil {
    85  		return res.StatusCode, fmt.Errorf("unable to decode response body for (method:%s,path:%s). Cause: %v", method, path, err)
    86  	}
    87  	return res.StatusCode, nil
    88  }
    89  
    90  func (t *tesseraPrivateTxManager) Send(data []byte, from string, to []string, extra *engine.ExtraMetadata) (string, []string, common.EncryptedPayloadHash, error) {
    91  	if extra.PrivacyFlag.IsNotStandardPrivate() && !t.features.HasFeature(engine.PrivacyEnhancements) {
    92  		return "", nil, common.EncryptedPayloadHash{}, engine.ErrPrivateTxManagerDoesNotSupportPrivacyEnhancements
    93  	}
    94  	response := new(sendResponse)
    95  	acMerkleRoot := ""
    96  	if !common.EmptyHash(extra.ACMerkleRoot) {
    97  		acMerkleRoot = extra.ACMerkleRoot.ToBase64()
    98  	}
    99  	if _, err := t.submitJSON("POST", "/send", &sendRequest{
   100  		Payload:                      data,
   101  		From:                         from,
   102  		To:                           to,
   103  		AffectedContractTransactions: extra.ACHashes.ToBase64s(),
   104  		ExecHash:                     acMerkleRoot,
   105  		PrivacyFlag:                  extra.PrivacyFlag,
   106  	}, response); err != nil {
   107  		return "", nil, common.EncryptedPayloadHash{}, err
   108  	}
   109  
   110  	eph, err := common.Base64ToEncryptedPayloadHash(response.Key)
   111  	if err != nil {
   112  		return "", nil, common.EncryptedPayloadHash{}, fmt.Errorf("unable to decode encrypted payload hash: %s. Cause: %v", response.Key, err)
   113  	}
   114  
   115  	cacheKey := eph.Hex()
   116  	t.cache.Set(cacheKey, cache.PrivateCacheItem{
   117  		Payload: data,
   118  		Extra: engine.ExtraMetadata{
   119  			ACHashes:       extra.ACHashes,
   120  			ACMerkleRoot:   extra.ACMerkleRoot,
   121  			PrivacyFlag:    extra.PrivacyFlag,
   122  			ManagedParties: response.ManagedParties,
   123  			Sender:         response.SenderKey,
   124  		},
   125  	}, gocache.DefaultExpiration)
   126  
   127  	return response.SenderKey, response.ManagedParties, eph, nil
   128  }
   129  
   130  func (t *tesseraPrivateTxManager) EncryptPayload(data []byte, from string, to []string, extra *engine.ExtraMetadata) ([]byte, error) {
   131  	response := new(encryptPayloadResponse)
   132  	acMerkleRoot := ""
   133  	if !common.EmptyHash(extra.ACMerkleRoot) {
   134  		acMerkleRoot = extra.ACMerkleRoot.ToBase64()
   135  	}
   136  
   137  	if _, err := t.submitJSON("POST", "/encodedpayload/create", &sendRequest{
   138  		Payload:                      data,
   139  		From:                         from,
   140  		To:                           to,
   141  		AffectedContractTransactions: extra.ACHashes.ToBase64s(),
   142  		ExecHash:                     acMerkleRoot,
   143  		PrivacyFlag:                  extra.PrivacyFlag,
   144  	}, response); err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	output, _ := json.Marshal(response)
   149  	return output, nil
   150  }
   151  
   152  func (t *tesseraPrivateTxManager) StoreRaw(data []byte, from string) (common.EncryptedPayloadHash, error) {
   153  
   154  	response := new(sendResponse)
   155  
   156  	if _, err := t.submitJSON("POST", "/storeraw", &storerawRequest{
   157  		Payload: data,
   158  		From:    from,
   159  	}, response); err != nil {
   160  		return common.EncryptedPayloadHash{}, err
   161  	}
   162  
   163  	eph, err := common.Base64ToEncryptedPayloadHash(response.Key)
   164  	if err != nil {
   165  		return common.EncryptedPayloadHash{}, fmt.Errorf("unable to decode encrypted payload hash: %s. Cause: %v", response.Key, err)
   166  	}
   167  
   168  	cacheKey := eph.Hex()
   169  	var extra engine.ExtraMetadata
   170  	cacheKeyTemp := fmt.Sprintf("%s-incomplete", cacheKey)
   171  	t.cache.Set(cacheKeyTemp, cache.PrivateCacheItem{
   172  		Payload: data,
   173  		Extra:   extra,
   174  	}, gocache.DefaultExpiration)
   175  
   176  	return eph, nil
   177  }
   178  
   179  // allow new quorum to send raw transactions when connected to an old tessera
   180  func (c *tesseraPrivateTxManager) sendSignedPayloadOctetStream(signedPayload []byte, b64To []string) (string, []string, []byte, error) {
   181  	buf := bytes.NewBuffer(signedPayload)
   182  	req, err := http.NewRequest("POST", c.client.FullPath("/sendsignedtx"), buf)
   183  	if err != nil {
   184  		return "", nil, nil, err
   185  	}
   186  
   187  	req.Header.Set("c11n-to", strings.Join(b64To, ","))
   188  	req.Header.Set("Content-Type", "application/octet-stream")
   189  	res, err := c.client.HttpClient.Do(req)
   190  	if err != nil {
   191  		return "", nil, nil, err
   192  	}
   193  	defer res.Body.Close()
   194  
   195  	if res.StatusCode != 200 {
   196  		return "", nil, nil, fmt.Errorf("Non-200 status code: %+v", res)
   197  	}
   198  	data, err := ioutil.ReadAll(res.Body)
   199  	if err != nil {
   200  		return "", nil, nil, err
   201  	}
   202  	sender := ""
   203  	if len(res.Header["Tesserasender"]) > 0 {
   204  		sender = res.Header["Tesserasender"][0]
   205  	}
   206  	return sender, res.Header["Tesseramanagedparties"], data, nil
   207  }
   208  
   209  // also populate cache item with additional extra metadata
   210  func (t *tesseraPrivateTxManager) SendSignedTx(data common.EncryptedPayloadHash, to []string, extra *engine.ExtraMetadata) (string, []string, []byte, error) {
   211  	if extra.PrivacyFlag.IsNotStandardPrivate() && !t.features.HasFeature(engine.PrivacyEnhancements) {
   212  		return "", nil, nil, engine.ErrPrivateTxManagerDoesNotSupportPrivacyEnhancements
   213  	}
   214  	response := new(sendSignedTxResponse)
   215  	acMerkleRoot := ""
   216  	if !common.EmptyHash(extra.ACMerkleRoot) {
   217  		acMerkleRoot = extra.ACMerkleRoot.ToBase64()
   218  	}
   219  	// The /sendsignedtx has been updated as part of privacy enhancements to support a json payload.
   220  	// If an older tessera is used - invoke the octetstream version of the /sendsignedtx
   221  	if t.features.HasFeature(engine.PrivacyEnhancements) {
   222  		if _, err := t.submitJSON("POST", "/sendsignedtx", &sendSignedTxRequest{
   223  			Hash:                         data.Bytes(),
   224  			To:                           to,
   225  			AffectedContractTransactions: extra.ACHashes.ToBase64s(),
   226  			ExecHash:                     acMerkleRoot,
   227  			PrivacyFlag:                  extra.PrivacyFlag,
   228  		}, response); err != nil {
   229  			return "", nil, nil, err
   230  		}
   231  	} else {
   232  		sender, managedParties, returnedHash, err := t.sendSignedPayloadOctetStream(data.Bytes(), to)
   233  		if err != nil {
   234  			return "", nil, nil, err
   235  		}
   236  		response.Key = string(returnedHash)
   237  		response.ManagedParties = managedParties
   238  		response.SenderKey = sender
   239  	}
   240  
   241  	hashBytes, err := base64.StdEncoding.DecodeString(response.Key)
   242  	if err != nil {
   243  		return "", nil, nil, err
   244  	}
   245  	// pull incomplete cache item and inject new cache item with complete information
   246  	cacheKey := data.Hex()
   247  	cacheKeyTemp := fmt.Sprintf("%s-incomplete", cacheKey)
   248  	if item, found := t.cache.Get(cacheKeyTemp); found {
   249  		if incompleteCacheItem, ok := item.(cache.PrivateCacheItem); ok {
   250  			t.cache.Set(cacheKey, cache.PrivateCacheItem{
   251  				Payload: incompleteCacheItem.Payload,
   252  				Extra: engine.ExtraMetadata{
   253  					ACHashes:       extra.ACHashes,
   254  					ACMerkleRoot:   extra.ACMerkleRoot,
   255  					PrivacyFlag:    extra.PrivacyFlag,
   256  					ManagedParties: response.ManagedParties,
   257  					Sender:         response.SenderKey,
   258  				},
   259  			}, gocache.DefaultExpiration)
   260  			t.cache.Delete(cacheKeyTemp)
   261  		}
   262  	}
   263  	return response.SenderKey, response.ManagedParties, hashBytes, err
   264  }
   265  
   266  func (t *tesseraPrivateTxManager) Receive(hash common.EncryptedPayloadHash) (string, []string, []byte, *engine.ExtraMetadata, error) {
   267  	return t.receive(hash, false)
   268  }
   269  
   270  // retrieve raw will not return information about medata.
   271  // Related to SendSignedTx
   272  func (t *tesseraPrivateTxManager) ReceiveRaw(hash common.EncryptedPayloadHash) ([]byte, string, *engine.ExtraMetadata, error) {
   273  	sender, _, data, extra, err := t.receive(hash, true)
   274  	return data, sender, extra, err
   275  }
   276  
   277  // retrieve raw will not return information about medata
   278  func (t *tesseraPrivateTxManager) receive(data common.EncryptedPayloadHash, isRaw bool) (string, []string, []byte, *engine.ExtraMetadata, error) {
   279  	if common.EmptyEncryptedPayloadHash(data) {
   280  		return "", nil, nil, nil, nil
   281  	}
   282  	cacheKey := data.Hex()
   283  	if isRaw {
   284  		// indicate the cache item is incomplete, this will be fulfilled in SendSignedTx
   285  		cacheKey = fmt.Sprintf("%s-incomplete", cacheKey)
   286  	}
   287  	if item, found := t.cache.Get(cacheKey); found {
   288  		cacheItem, ok := item.(cache.PrivateCacheItem)
   289  		if !ok {
   290  			return "", nil, nil, nil, fmt.Errorf("unknown cache item. expected type PrivateCacheItem")
   291  		}
   292  		return cacheItem.Extra.Sender, cacheItem.Extra.ManagedParties, cacheItem.Payload, &cacheItem.Extra, nil
   293  	}
   294  
   295  	response := new(receiveResponse)
   296  	if statusCode, err := t.submitJSON("GET", fmt.Sprintf("/transaction/%s?isRaw=%v", url.PathEscape(data.ToBase64()), isRaw), nil, response); err != nil {
   297  		if statusCode == http.StatusNotFound {
   298  			return "", nil, nil, nil, nil
   299  		} else {
   300  			return "", nil, nil, nil, err
   301  		}
   302  	}
   303  	var extra engine.ExtraMetadata
   304  	if !isRaw {
   305  		acHashes, err := common.Base64sToEncryptedPayloadHashes(response.AffectedContractTransactions)
   306  		if err != nil {
   307  			return "", nil, nil, nil, fmt.Errorf("unable to decode ACOTHs %v. Cause: %v", response.AffectedContractTransactions, err)
   308  		}
   309  		acMerkleRoot, err := common.Base64ToHash(response.ExecHash)
   310  		if err != nil {
   311  			return "", nil, nil, nil, fmt.Errorf("unable to decode execution hash %s. Cause: %v", response.ExecHash, err)
   312  		}
   313  		extra = engine.ExtraMetadata{
   314  			ACHashes:       acHashes,
   315  			ACMerkleRoot:   acMerkleRoot,
   316  			PrivacyFlag:    response.PrivacyFlag,
   317  			ManagedParties: response.ManagedParties,
   318  			Sender:         response.SenderKey,
   319  		}
   320  	}
   321  
   322  	t.cache.Set(cacheKey, cache.PrivateCacheItem{
   323  		Payload: response.Payload,
   324  		Extra:   extra,
   325  	}, gocache.DefaultExpiration)
   326  
   327  	return response.SenderKey, response.ManagedParties, response.Payload, &extra, nil
   328  }
   329  
   330  // retrieve raw will not return information about medata
   331  func (t *tesseraPrivateTxManager) DecryptPayload(payload common.DecryptRequest) ([]byte, *engine.ExtraMetadata, error) {
   332  	response := new(receiveResponse)
   333  	if _, err := t.submitJSON("POST", "/encodedpayload/decrypt", &decryptPayloadRequest{
   334  		SenderKey:       payload.SenderKey,
   335  		CipherText:      payload.CipherText,
   336  		CipherTextNonce: payload.CipherTextNonce,
   337  		RecipientBoxes:  payload.RecipientBoxes,
   338  		RecipientNonce:  payload.RecipientNonce,
   339  		RecipientKeys:   payload.RecipientKeys,
   340  	}, response); err != nil {
   341  		return nil, nil, err
   342  	}
   343  
   344  	var extra engine.ExtraMetadata
   345  	acHashes, err := common.Base64sToEncryptedPayloadHashes(response.AffectedContractTransactions)
   346  	if err != nil {
   347  		return nil, nil, fmt.Errorf("unable to decode ACOTHs %v. Cause: %v", response.AffectedContractTransactions, err)
   348  	}
   349  	acMerkleRoot, err := common.Base64ToHash(response.ExecHash)
   350  	if err != nil {
   351  		return nil, nil, fmt.Errorf("unable to decode execution hash %s. Cause: %v", response.ExecHash, err)
   352  	}
   353  	extra = engine.ExtraMetadata{
   354  		ACHashes:     acHashes,
   355  		ACMerkleRoot: acMerkleRoot,
   356  		PrivacyFlag:  response.PrivacyFlag,
   357  	}
   358  
   359  	return response.Payload, &extra, nil
   360  }
   361  
   362  func (t *tesseraPrivateTxManager) IsSender(txHash common.EncryptedPayloadHash) (bool, error) {
   363  	req, err := http.NewRequest("GET", "http+unix://c/transaction/"+url.PathEscape(txHash.ToBase64())+"/isSender", nil)
   364  	if err != nil {
   365  		return false, err
   366  	}
   367  
   368  	res, err := t.client.HttpClient.Do(req)
   369  
   370  	if res != nil {
   371  		defer res.Body.Close()
   372  	}
   373  
   374  	if err != nil {
   375  		return false, err
   376  	}
   377  
   378  	if res.StatusCode != 200 {
   379  		return false, fmt.Errorf("non-200 status code: %+v", res)
   380  	}
   381  
   382  	out, err := ioutil.ReadAll(res.Body)
   383  	if err != nil {
   384  		return false, err
   385  	}
   386  
   387  	return strconv.ParseBool(string(out))
   388  }
   389  
   390  func (t *tesseraPrivateTxManager) GetParticipants(txHash common.EncryptedPayloadHash) ([]string, error) {
   391  	requestUrl := "http+unix://c/transaction/" + url.PathEscape(txHash.ToBase64()) + "/participants"
   392  	req, err := http.NewRequest("GET", requestUrl, nil)
   393  	if err != nil {
   394  		return nil, err
   395  	}
   396  
   397  	res, err := t.client.HttpClient.Do(req)
   398  
   399  	if res != nil {
   400  		defer res.Body.Close()
   401  	}
   402  
   403  	if err != nil {
   404  		return nil, err
   405  	}
   406  
   407  	if res.StatusCode != 200 {
   408  		return nil, fmt.Errorf("Non-200 status code: %+v", res)
   409  	}
   410  
   411  	out, err := ioutil.ReadAll(res.Body)
   412  	if err != nil {
   413  		return nil, err
   414  	}
   415  
   416  	split := strings.Split(string(out), ",")
   417  
   418  	return split, nil
   419  }
   420  
   421  func (t *tesseraPrivateTxManager) Name() string {
   422  	return "Tessera"
   423  }
   424  
   425  func (t *tesseraPrivateTxManager) HasFeature(f engine.PrivateTransactionManagerFeature) bool {
   426  	return t.features.HasFeature(f)
   427  }
   428  
   429  // don't serialize body if nil
   430  func newOptionalJSONRequest(method string, path string, body interface{}, apiVersion string) (*http.Request, error) {
   431  	buf := new(bytes.Buffer)
   432  	if body != nil {
   433  		err := json.NewEncoder(buf).Encode(body)
   434  		if err != nil {
   435  			return nil, err
   436  		}
   437  	}
   438  	request, err := http.NewRequest(method, path, buf)
   439  	if err != nil {
   440  		return nil, err
   441  	}
   442  	request.Header.Set("User-Agent", fmt.Sprintf("quorum-v%s", params.QuorumVersion))
   443  	request.Header.Set("Content-type", fmt.Sprintf("application/%sjson", apiVersion))
   444  	request.Header.Set("Accept", fmt.Sprintf("application/%sjson", apiVersion))
   445  	return request, nil
   446  }