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 }