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