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